/* UtilLib - Copyright (c) 2012 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.xml;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import net.mapeadores.util.primitives.io.PrimitivesReader;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;


/**
 *
 * @author Vincent Calame
 */
public class PrimitivesSAXReader implements XMLReader {

    private static final String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
    private static final Attributes EMPTY_ATTRIBUTES = new EmptyAttributes();
    private Map<String, Boolean> featureMap = new HashMap<String, Boolean>();
    private DTDHandler dtdHandler;
    private ErrorHandler errorHandler;
    private EntityResolver entityResolver;
    private ContentHandler contentHandler;
    private PrimitivesReader primitivesReader;
    private Stack<String> elementNameStack = new Stack<String>();

    {
        featureMap.put("http://xml.org/sax/features/namespaces", true);
        featureMap.put("http://xml.org/sax/features/namespace-prefixes", false);
    }

    public PrimitivesSAXReader(PrimitivesReader primitivesReader) {
        this.primitivesReader = primitivesReader;
    }

    public ContentHandler getContentHandler() {
        return contentHandler;
    }

    public DTDHandler getDTDHandler() {
        return dtdHandler;
    }

    public EntityResolver getEntityResolver() {
        return entityResolver;
    }

    public ErrorHandler getErrorHandler() {
        return errorHandler;
    }

    public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
        if (featureMap.containsKey(name)) {
            return featureMap.get(name);
        }
        throw new SAXNotRecognizedException(name);
    }

    public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
        throw new SAXNotRecognizedException(name);
    }

    public void parse(InputSource input) throws IOException, SAXException {
        read(primitivesReader, contentHandler);
    }

    public void parse(String systemId) throws IOException, SAXException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setContentHandler(ContentHandler handler) {
        this.contentHandler = handler;
    }

    public void setDTDHandler(DTDHandler handler) {
        this.dtdHandler = handler;
    }

    public void setEntityResolver(EntityResolver resolver) {
        this.entityResolver = resolver;
    }

    public void setErrorHandler(ErrorHandler handler) {
        this.errorHandler = handler;
    }

    public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
        if (featureMap.containsKey(name)) {
            featureMap.put(name, value);
            return;
        }
        throw new SAXNotRecognizedException(name);
    }

    public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
        throw new SAXNotRecognizedException(name);
    }

    private void read(PrimitivesReader primitivesReader, ContentHandler contentHandler) throws IOException, SAXException {
        byte b = primitivesReader.readByte();
        if (b != PrimitivesXMLWriter.ELEMENT_START) {
            throw new IllegalStateException("fist byte != PrimitivesXMLWriter.ELEMENT_START");
        }
        contentHandler.startDocument();
        startElement(primitivesReader, contentHandler);
        while (true) {
            b = primitivesReader.readByte();
            if (b == PrimitivesXMLWriter.ELEMENT_START) {
                startElement(primitivesReader, contentHandler);
            } else if (b == PrimitivesXMLWriter.TEXT) {
                addText(primitivesReader, contentHandler);
            } else if (b == PrimitivesXMLWriter.ELEMENT_END) {
                boolean documentEnd = endElement(contentHandler);
                if (documentEnd) {
                    break;
                }
            }
        }
    }

    private void startElement(PrimitivesReader primitivesReader, ContentHandler contentHandler) throws IOException, SAXException {
        String elementName = primitivesReader.readString();
        elementNameStack.push(elementName);
        int length = primitivesReader.readInt();
        if (length == 0) {
            contentHandler.startElement("", elementName, elementName, EMPTY_ATTRIBUTES);
        } else {
            ArrayAttributes arrayAttributes = new ArrayAttributes(length);
            for (int i = 0; i < length; i++) {
                String name = primitivesReader.readString();
                String value = primitivesReader.readString();
                arrayAttributes.set(i, name, value);
            }
            contentHandler.startElement("", elementName, elementName, arrayAttributes);
        }
    }

    private void addText(PrimitivesReader primitivesReader, ContentHandler contentHandler) throws IOException, SAXException {
        int length = primitivesReader.readInt();
        char[] array = new char[length];
        for (int i = 0; i < length; i++) {
            array[i] = primitivesReader.readChar();
        }
        contentHandler.characters(array, 0, length);
    }

    private boolean endElement(ContentHandler contentHandler) throws IOException, SAXException {
        String elementName = elementNameStack.pop();
        contentHandler.endElement("", elementName, elementName);
        if (elementNameStack.empty()) {
            contentHandler.endDocument();
            return true;
        } else {
            return false;
        }
    }


    private static class EmptyAttributes implements Attributes {

        public int getIndex(String uri, String localName) {
            return -1;
        }

        public int getIndex(String qName) {
            return -1;
        }

        public int getLength() {
            return 0;
        }

        public String getLocalName(int index) {
            return null;
        }

        public String getQName(int index) {
            return null;
        }

        public String getType(int index) {
            return "CDATA";
        }

        public String getType(String uri, String localName) {
            return null;
        }

        public String getType(String qName) {
            return null;
        }

        public String getURI(int index) {
            return null;
        }

        public String getValue(int index) {
            return null;
        }

        public String getValue(String uri, String localName) {
            return null;
        }

        public String getValue(String qName) {
            return null;
        }

    }


    private static class ArrayAttributes implements Attributes {

        private int length;
        private String[] qnameArray;
        private String[] valueArray;
        private boolean[] xmlNamespaceArray;

        private ArrayAttributes(int length) {
            this.length = length;
            qnameArray = new String[length];
            valueArray = new String[length];
            xmlNamespaceArray = new boolean[length];
        }

        private void set(int i, String name, String value) {
            qnameArray[i] = name;
            valueArray[i] = value;
            if (name.startsWith("xml:")) {
                xmlNamespaceArray[i] = true;
            } else {
                xmlNamespaceArray[i] = false;
            }
        }

        public int getIndex(String uri, String localName) {
            if (uri.equals(XML_NAMESPACE)) {
                return getIndex("xml:" + localName);
            }
            if (uri.length() != 0) {
                return -1;
            }
            return getIndex(localName);
        }

        public int getIndex(String qName) {
            for (int i = 0; i < length; i++) {
                if (qnameArray[i].equals(qName)) {
                    return i;
                }
            }
            return -1;
        }

        public int getLength() {
            return length;
        }

        public String getLocalName(int index) {
            if (!testIndex(index)) {
                return null;
            }
            String qname = qnameArray[index];
            if (xmlNamespaceArray[index]) {
                return qname.substring(4);
            } else {
                return qname;
            }
        }

        public String getQName(int index) {
            if (!testIndex(index)) {
                return null;
            }
            return qnameArray[index];
        }

        public String getType(int index) {
            if (!testIndex(index)) {
                return null;
            }
            return "CDATA";
        }

        public String getType(String uri, String localName) {
            int index = getIndex(uri, localName);
            if (index == -1) {
                return null;
            }
            return "CDATA";
        }

        public String getType(String qName) {
            int index = getIndex(qName);
            if (index == -1) {
                return null;
            }
            return "CDATA";
        }

        public String getURI(int index) {
            if (!testIndex(index)) {
                return null;
            }
            if (xmlNamespaceArray[index]) {
                return XML_NAMESPACE;
            } else {
                return "";
            }
        }

        public String getValue(int index) {
            if (!testIndex(index)) {
                return null;
            }
            return valueArray[index];
        }

        public String getValue(String uri, String localName) {
            int index = getIndex(uri, localName);
            if (index == -1) {
                return null;
            }
            return valueArray[index];
        }

        public String getValue(String qName) {
            int index = getIndex(qName);
            if (index == -1) {
                return null;
            }
            return valueArray[index];
        }

        private boolean testIndex(int index) {
            if ((index < 0) || (index >= length)) {
                return false;
            }
            return true;
        }

    }

}
