package de.renew.gui.pnml.parser;

import java.awt.*;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import CH.ifa.draw.figures.TextFigure;
import CH.ifa.draw.framework.FigureWithID;
import CH.ifa.draw.framework.ParentFigure;
import de.renew.gui.pnml.converter.NetConverter;


/**
 * parse and create PNML Objects (Nodes, Labels)
 */
public abstract class ObjectParser extends ElementParser {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(ObjectParser.class);
    // ID regex pattern: one digits or more.
    private static final Pattern NUMERIC_ID_PATTERN = Pattern.compile("\\d+");

    private FigureWithID _figure;
    private String _pnmlId;
    private Integer _mappedId;
    private final List<AnnotationParser> _annotation;
    private final List<AttributeParser> _attributes;
    private int _posAnnotation;
    private boolean _isParsed;
    private GraphicParser _parser;

    public ObjectParser(Element object, NetConverter converter) {
        super(object, converter);
        _isParsed = false;
        _mappedId = null;
        _annotation = new LinkedList<>();
        _attributes = new LinkedList<>();
        _posAnnotation = 0;
    }

    protected FigureWithID figure() {
        return _figure;
    }

    protected String getPNMLId() {
        return _pnmlId;
    }

    public Integer getMappedId() {
        return _mappedId;
    }

    public void setMappedId(Integer value) {
        _mappedId = value;
    }

    protected List<AttributeParser> getAttributes() {
        return _attributes;
    }

    protected GraphicParser getGraphic() {
        return _parser;
    }

    public boolean hasNumericId() {
        return _mappedId != null;
    }

    private void parseId() {
        _pnmlId = getElement().getAttribute("id");
        try {
            _mappedId = parseNumericId(_pnmlId);
        } catch (NumberFormatException e) {
            logger.debug(e.getMessage(), e);
            // Keep _mappedId null.
        }
    }

    protected static int parseNumericId(String stringValue) throws NumberFormatException {
        final Matcher matcher = NUMERIC_ID_PATTERN.matcher(stringValue);
        // Find first sequence of digits.
        if (matcher.find()) {
            // Parse as integer.
            return Integer.parseInt(matcher.group());
        }
        throw new NumberFormatException(
            "No digits found in PNML ID string: \"" + stringValue + "\"");
    }


    private void parseGraphics() {
        NodeList children = getElement().getChildNodes();
        Element element = null;
        for (int pos = 0; pos < children.getLength(); pos++) {
            Node child = children.item(pos);
            if ("graphics".equals(child.getNodeName())) {
                element = (Element) child;
                break;
            }
        }

        GraphicParser parser = new GraphicParser(element, getNetConverter());
        parser.parse();
        _parser = parser;
    }

    private boolean isVirtual(Element anno) {
        NodeList tools = anno.getElementsByTagName("toolspecific");
        for (int pos = 0; pos < tools.getLength(); pos++) {
            Element ele = (Element) tools.item(pos);
            if ("renew".equals(ele.getAttribute("tool"))
                && ele.getElementsByTagName("virtual").getLength() != 0) {
                return true;
            }
        }

        return false;
    }

    private void parseLabels() {
        NodeList children = getElement().getChildNodes();
        for (int pos = 0; pos < children.getLength(); pos++) {
            Node child = children.item(pos);

            if (child.getNodeType() == Node.ELEMENT_NODE) {
                Element childEle = (Element) child;

                if ("toolspecific".equals(childEle.getTagName())
                    && "renew".equals(childEle.getAttribute("tool"))) {
                    // parse inscriptions inside "toolspecific" tag
                    NodeList inscs = childEle.getElementsByTagName("inscription");
                    for (int i = 0; i < inscs.getLength(); i++) {
                        parseSingleLabel((Element) inscs.item(i));
                    }
                } else {
                    parseSingleLabel(childEle);
                }
            }
        }
    }

    private void parseSingleLabel(Element label) {
        NetConverter con = getNetConverter();
        if (con.isAnnotation(label) || con.isInRenewAnnotation(label)) {
            if (!isVirtual(label)) {
                AnnotationParser parser = new AnnotationParser(label, con);
                parser.parse();
                _annotation.add(parser);
            }
        } else if (con.isAttribute(label)) {
            AttributeParser parser = new AttributeParser(label, con);
            parser.parse();
            _attributes.add(parser);
        }
    }

    protected void parseFigure() {}

    @Override
    protected void doParse() {
        parseId();
        parseGraphics();
        parseLabels();
        parseFigure();
        _isParsed = true;
    }

    protected abstract FigureWithID createObject();

    protected abstract void initGraphic();

    protected void doInitFigure() {
        initGraphic();
    }

    public FigureWithID getFigure() {
        if (!_isParsed) {
            parse();
        }
        _figure = createObject();
        _figure.setID(_mappedId);
        doInitFigure();
        return _figure;
    }

    public boolean hasMoreAnnotations() {
        return _annotation.size() > _posAnnotation;
    }

    public TextFigure nextAnnotation() {
        AnnotationParser parser = _annotation.get(_posAnnotation);
        TextFigure result = parser.getFigure();
        if (result != null) {
            Point point = result.displayBox().getLocation();
            result.setParent((ParentFigure) _figure);
            if (point.x != 0 || point.y != 0) {
                result.moveBy(point.x, point.y);
            }
        }

        _posAnnotation++;
        return result;
    }

    protected void moveObject(int x, int y) {
        _figure.moveBy(x, y);
    }
}
