package org.emdev.common.xml.parsers;

import java.util.Arrays;

import org.emdev.common.xml.IContentHandler;
import org.emdev.common.xml.IXmlTagFactory;
import org.emdev.common.xml.TextProvider;
import org.emdev.common.xml.tags.XmlTag;

import com.ximpleware.NavException;
import com.ximpleware.VTDGenEx;
import com.ximpleware.VTDNavEx;

public class VTDExParser {

    public void parse(final VTDGenEx inStream, final IXmlTagFactory factory, final IContentHandler handler) throws NavException {
        final VTDNavEx nav = inStream.getNav();

        final boolean res = nav.toElement(VTDNavEx.ROOT);
        if (!res) {
            return;
        }

        TextProvider text = null;

        XmlTag tag = XmlTag.UNKNOWN;
        int skipUntilSiblingOrParent = -1;

        final int first = nav.getCurrentIndex();
        final int last = nav.getTokenCount();

        final String[] tagAttrs = new String[2];
        final XmlTag[] tags = new XmlTag[1024];

        final int[] range = new int[2];

        int maxDepth = -1;
        for (int ci = first; ci < last;) {
            final int depth = nav.getTokenDepth(ci);
            final int type = nav.getTokenType(ci);

            if (skipUntilSiblingOrParent != -1) {
                if (type == VTDNavEx.TOKEN_STARTING_TAG && depth <= skipUntilSiblingOrParent) {
                    skipUntilSiblingOrParent = -1;
                } else {
                    ci++;
                    continue;
                }
            }

            if (type == VTDNavEx.TOKEN_STARTING_TAG) {
                final char[] buf = nav.toRawString(ci, range);
                tag = factory.getTagByName(buf, range[0], range[1]);

                for (int d = maxDepth; d >= depth; d--) {
                    if (tags[d] != null) {
                        handler.endElement(tags[d]);
                        tags[d] = null;
                    }
                }
                tags[depth] = tag;
                maxDepth = depth;

                if (tag == XmlTag.UNKNOWN) {
                    skipUntilSiblingOrParent = depth;
                    ci++;
                    continue;
                }

                // Process tag attributes
                if (handler.parseAttributes(tag)) {
                    ci = fillAtributes(nav, ci + 1, last, tag, tagAttrs);
                    handler.startElement(tag, tagAttrs);
                } else {
                    ci = skipAtributes(nav, ci + 1, last);
                    handler.startElement(tag);
                }
                continue;
            }

            for (int d = maxDepth; d > depth; d--) {
                if (tags[d] != null) {
                    handler.endElement(tags[d]);
                    tags[d] = null;
                }
            }
            maxDepth = depth;

            if (type == VTDNavEx.TOKEN_CHARACTER_DATA || type == VTDNavEx.TOKEN_CDATA_VAL) {
                if (tag.processText && !handler.skipCharacters()) {
                    final char[] buf = nav.toRawString(ci, range);
                    if (text ==null) {
                        text = new TextProvider(true, buf);
                    }
                    handler.characters(text, range[0], range[1]);
                }
                ci++;
                continue;
            }

            ci++;
        }

        for (int d = maxDepth; d >= 0; d--) {
            if (tags[d] != null) {
                handler.endElement(tags[d]);
                tags[d] = null;
            }
        }

        inStream.clear();
    }

    private int skipAtributes(final VTDNavEx nav, final int first, final int last) throws NavException {
        for (int inner = first; inner < last; inner++) {
            final int innerType = nav.getTokenType(inner);
            switch (innerType) {
                case VTDNavEx.TOKEN_ATTR_NAME:
                case VTDNavEx.TOKEN_ATTR_NS:
                case VTDNavEx.TOKEN_ATTR_VAL:
                    break;
                default:
                    return inner;
            }
        }
        return last;
    }

    private int fillAtributes(final VTDNavEx nav, final int first, final int last, final XmlTag tag,
            final String[] tagAttrs) throws NavException {

        final int[] range = new int[2];

        for (int i = 0; i < tag.attributes.length; i++) {
            tagAttrs[i] = null;
        }

        for (int inner = first; inner < last; inner++) {
            final int innerType = nav.getTokenType(inner);
            switch (innerType) {
                case VTDNavEx.TOKEN_ATTR_NAME:
                    final char[] nameBuf = nav.toRawString(inner, range);
                    final String[] qName = new String(nameBuf, range[0], range[1]).split(":");
                    final String attrName = qName[qName.length - 1];
                    final int attrIndex = Arrays.binarySearch(tag.attributes, attrName);
                    if (attrIndex >= 0) {
                        final char[] valBuf = nav.toRawString(inner + 1, range);
                        final String attrValue = new String(valBuf, range[0], range[1]);
                        tagAttrs[attrIndex] = attrValue;
                    }
                    inner++;
                    break;
                case VTDNavEx.TOKEN_ATTR_NS:
                case VTDNavEx.TOKEN_ATTR_VAL:
                    break;
                default:
                    return inner;
            }
        }

        return last;
    }

}
