/*
 * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     bdoughan - June 24/2009 - 2.0 - Initial implementation
package org.eclipse.persistence.internal.oxm.record;

import javax.xml.stream.XMLStreamReader;

import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.Unmarshaller;
import org.eclipse.persistence.internal.oxm.record.namespaces.UnmarshalNamespaceContext;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.Locator2;

/**
 * Convert an XMLStreamReader into SAX events.
 */
public class XMLStreamReaderReader extends XMLReaderAdapter {

    private int depth = 0;
    private UnmarshalNamespaceContext unmarshalNamespaceContext;
    private XMLStreamReaderAttributes indexedAttributeList;
    private boolean qNameAware;
    private XMLStreamReader xmlStreamReader;

    public XMLStreamReaderReader() {
        unmarshalNamespaceContext = new UnmarshalNamespaceContext();
        indexedAttributeList = new XMLStreamReaderAttributes();
    }

    public XMLStreamReaderReader(Unmarshaller xmlUnmarshaller) {
        super(xmlUnmarshaller);
        unmarshalNamespaceContext = new UnmarshalNamespaceContext();
        indexedAttributeList = new XMLStreamReaderAttributes();
    }

    @Override
    public Locator getLocator(){
        if(locator == null){
            locator = new StreamReaderLocator(xmlStreamReader);
        }
        return locator;
    }

    @Override
    public void setContentHandler (ContentHandler handler) {
        if(handler instanceof UnmarshalRecord){
            ((UnmarshalRecord)handler).setUnmarshalNamespaceResolver(unmarshalNamespaceContext);
            qNameAware = false;
            if(null == validatingContentHandler) {
                this.contentHandler = (UnmarshalRecord) handler;
            }else{
                 validatingContentHandler.setContentHandler(handler);
            }
        }else if(handler instanceof SAXUnmarshallerHandler){
            ((SAXUnmarshallerHandler)handler).setUnmarshalNamespaceResolver(unmarshalNamespaceContext);
            qNameAware = true;
            if(null == validatingContentHandler) {
                this.contentHandler = (SAXUnmarshallerHandler) handler;
            }else{
                 validatingContentHandler.setContentHandler(handler);
            }
        } else {
            super.setContentHandler(handler);
            qNameAware = true;
        }
    }

    @Override
    public void parse(InputSource input) throws SAXException {
        if(null == contentHandler) {
            return;
        }
        if(input instanceof XMLStreamReaderInputSource) {
            XMLStreamReader xmlStreamReader = ((XMLStreamReaderInputSource) input).getXmlStreamReader();
            parse(xmlStreamReader);
        }
    }

    public void parse(XMLStreamReader xmlStreamReader) throws SAXException {
        this.xmlStreamReader = xmlStreamReader;
        unmarshalNamespaceContext.setXmlStreamReader(xmlStreamReader);
        indexedAttributeList.setXmlStreamReader(xmlStreamReader);
        try {
            contentHandler.startDocument();
            parseEvent(xmlStreamReader, xmlStreamReader.getEventType());
            while(depth > 0) {
                int eventType = xmlStreamReader.next();
                parseEvent(xmlStreamReader, eventType);
            }
            if(xmlStreamReader.hasNext()) {
                xmlStreamReader.next();
            }
            contentHandler.endDocument();
        } catch(SAXException e ) {
            throw e;
        } catch(Exception e) {
            throw new SAXException(e);
        }
    }

    private void parseEvent(XMLStreamReader xmlStreamReader, int eventType) throws SAXException {
        switch (eventType) {
            case XMLStreamReader.START_ELEMENT: {
                depth++;
                int namespaceCount = xmlStreamReader.getNamespaceCount();
                if(namespaceCount > 0) {
                    for(int x=0; x<namespaceCount; x++) {
                        contentHandler.startPrefixMapping(xmlStreamReader.getNamespacePrefix(x), xmlStreamReader.getNamespaceURI(x));
                    }
                }
                String localName = xmlStreamReader.getLocalName();
                String namespaceURI = xmlStreamReader.getNamespaceURI();
                if(Constants.EMPTY_STRING.equals(namespaceURI)) {
                    namespaceURI = null;
                }
                if(qNameAware) {
                    String prefix = xmlStreamReader.getPrefix();
                    if(null == prefix || prefix.length() == 0) {
                        contentHandler.startElement(namespaceURI, localName, localName, indexedAttributeList.reset());
                    } else {
                        contentHandler.startElement(namespaceURI, localName, prefix + Constants.COLON + localName, indexedAttributeList.reset());
                    }
                } else {
                    contentHandler.startElement(namespaceURI, localName, localName, indexedAttributeList.reset());
                }
                break;
            }
            case XMLStreamReader.END_ELEMENT: {
                depth--;
                String localName = xmlStreamReader.getLocalName();
                String namespaceURI = xmlStreamReader.getNamespaceURI();
                if(Constants.EMPTY_STRING.equals(namespaceURI)) {
                    namespaceURI = null;
                }
                if(qNameAware) {
                    String prefix = xmlStreamReader.getPrefix();
                    if(null == prefix || prefix.length() == 0) {
                        contentHandler.endElement(namespaceURI, localName, localName);
                    } else {
                        contentHandler.endElement(namespaceURI, localName, prefix + Constants.COLON + localName);
                    }
                } else {
                    contentHandler.endElement(namespaceURI, localName, localName);
                }
                int namespaceCount = xmlStreamReader.getNamespaceCount();
                if(namespaceCount > 0) {
                    for(int x=0; x<namespaceCount; x++) {
                        contentHandler.endPrefixMapping(xmlStreamReader.getNamespacePrefix(x));
                    }
                }
               break;
            }
            case XMLStreamReader.PROCESSING_INSTRUCTION: {
                contentHandler.processingInstruction(xmlStreamReader.getPITarget(), xmlStreamReader.getPIData());
                break;
            }
            case XMLStreamReader.CHARACTERS: {
                parseCharactersEvent(xmlStreamReader);
                break;
            }
            case XMLStreamReader.COMMENT: {
                if(null != lexicalHandler) {
                    lexicalHandler.comment(xmlStreamReader.getTextCharacters(), xmlStreamReader.getTextStart(), xmlStreamReader.getTextLength());
                }
                break;
            }
            case XMLStreamReader.SPACE: {
                contentHandler.characters(xmlStreamReader.getTextCharacters(), xmlStreamReader.getTextStart(), xmlStreamReader.getTextLength());
                break;
            }
            case XMLStreamReader.START_DOCUMENT: {
                depth++;
                break;
            }
            case XMLStreamReader.END_DOCUMENT: {
                depth--;
                return;
            }
            case XMLStreamReader.ENTITY_REFERENCE: {
                break;
            }
            case XMLStreamReader.ATTRIBUTE: {
                break;
            }
            case XMLStreamReader.DTD: {
                break;
            }
            case XMLStreamReader.CDATA: {
                if(null == lexicalHandler) {
                    parseCharactersEvent(xmlStreamReader);
                } else {
                    lexicalHandler.startCDATA();
                    parseCharactersEvent(xmlStreamReader);
                    lexicalHandler.endCDATA();
                }
                break;
            }
        }
    }

    /**
     * Subclasses of this class can override this method to provide alternate
     * mechanisms for processing the characters event.  One possibility is
     * obtaining a CharSequence and calling the corresponding characters method
     * on the extended content handler.
     */
    protected void parseCharactersEvent(XMLStreamReader xmlStreamReader) throws SAXException {
        contentHandler.characters(xmlStreamReader.getTextCharacters(), xmlStreamReader.getTextStart(), xmlStreamReader.getTextLength());
    }

    private static class XMLStreamReaderAttributes  extends IndexedAttributeList {

        private XMLStreamReader xmlStreamReader;

        public void setXmlStreamReader(XMLStreamReader xmlStreamReader) {
            this.xmlStreamReader = xmlStreamReader;
        }

        @Override
        protected Attribute[] attributes() {
            if(null == attributes) {
                int namespaceCount = xmlStreamReader.getNamespaceCount();
                int attributeCount = xmlStreamReader.getAttributeCount();
                if(namespaceCount + attributeCount == 0) {
                    attributes = NO_ATTRIBUTES;
                } else {
                    attributes = new Attribute[attributeCount + namespaceCount];
                    for(int x=0; x<attributeCount; x++) {
                        String uri = xmlStreamReader.getAttributeNamespace(x);
                        String localName = xmlStreamReader.getAttributeLocalName(x);
                        String prefix = xmlStreamReader.getAttributePrefix(x);
                        String qName;
                        if(null == prefix || prefix.length() == 0) {
                            qName = localName;
                        } else {
                            qName = prefix + Constants.COLON + localName;
                        }
                        String value = xmlStreamReader.getAttributeValue(x);
                        attributes[x] = new Attribute(uri, localName, qName, value);
                    }
                    for(int x=0; x<namespaceCount; x++) {
                        String uri = javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
                        String localName = xmlStreamReader.getNamespacePrefix(x);
                        String qName;
                        if(null == localName || localName.length() == 0) {
                            localName = javax.xml.XMLConstants.XMLNS_ATTRIBUTE;
                            qName = javax.xml.XMLConstants.XMLNS_ATTRIBUTE;
                        } else {
                            qName = javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + localName;
                        }
                        String value = xmlStreamReader.getNamespaceURI(x);
                        attributes[x + attributeCount] = new Attribute(uri, localName, qName, value);
                    }

                }
            }
            return attributes;
        }

        @Override
        public String getValue(String uri, String localName) {
            if (Constants.EMPTY_STRING.equals(uri)) {
                uri = null;
            }
            return xmlStreamReader.getAttributeValue(uri, localName);
        }

    }

    // Made static for performance reasons.
    /**
     * <p>An implementation of Locator, created from an existing XMLStreamReader.</p>
     *
     * @see org.xml.sax.Locator
     * @see javax.xml.stream.XMLStreamReader
     */
    private static class StreamReaderLocator implements Locator2 {

        private XMLStreamReader reader;

        /**
         * Instantiates a new StreamReaderLocator.
         *
         * @param r the XMLStreamReader object from which to copy location information.
         */
        public StreamReaderLocator(XMLStreamReader r) {
            this.reader = r;
        }

        /**
         * Returns the public ID of this Locator.
         */
        @Override
        public String getPublicId() {
            return this.reader.getLocation().getPublicId();
        }

        /**
         * Returns the system ID of this Locator.
         */
        @Override
        public String getSystemId() {
            return this.reader.getLocation().getSystemId();
        }

        /**
         * Returns the line number of this Locator.
         */
        @Override
        public int getLineNumber() {
            return this.reader.getLocation().getLineNumber();
        }

        /**
         * Returns the column number of this Locator.
         */
        @Override
        public int getColumnNumber() {
            return this.reader.getLocation().getColumnNumber();
        }

        @Override
        public String getXMLVersion() {
            return null;
        }

        @Override
        public String getEncoding() {
            return null;
        }

    }

}
