/*
 * Copyright (c) 1998, 2018 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:
//     mmacivor - September 14/2009 - 2.0 - Initial implementation
package org.eclipse.persistence.internal.oxm.record;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.Comment;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.ProcessingInstruction;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

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

/**
 * Convert and XMLEventReader into SAX events.
 */
public class XMLEventReaderReader extends XMLReaderAdapter {

    private int depth = 0;
    //Required because the EndElement fails to properly won't give the list of namespaces going out of
    //scope in some STAX implementations
    private Map<Integer, List<Namespace>> namespaces;
    private XMLEventReaderAttributes indexedAttributeList;
    private XMLEvent lastEvent;

    public XMLEventReaderReader() {
        this.namespaces = new HashMap<Integer, List<Namespace>>();
        this.indexedAttributeList = new XMLEventReaderAttributes();
    }

    public XMLEventReaderReader(Unmarshaller xmlUnmarshaller) {
        super(xmlUnmarshaller);
        this.namespaces = new HashMap<Integer, List<Namespace>>();
        this.indexedAttributeList = new XMLEventReaderAttributes();
    }

    @Override
    public void parse(InputSource input) throws SAXException {
        if(null == contentHandler) {
            return;
        }
        if(input instanceof XMLEventReaderInputSource) {
            XMLEventReader xmlEventReader = ((XMLEventReaderInputSource) input).getXmlEventReader();
            parse(xmlEventReader);
        }
    }

    public Locator getLocator(){
        if(locator == null){
            locator = new  EventReaderLocator();
        }
        ((EventReaderLocator)locator).setEvent(lastEvent);
        return locator;
    }

    private void parse(XMLEventReader xmlEventReader) throws SAXException {
        try {
            contentHandler.startDocument();
            parseEvent(xmlEventReader.nextEvent());
            while(depth > 0) {
                parseEvent(xmlEventReader.nextEvent());
            }
            contentHandler.endDocument();
        } catch(XMLStreamException ex) {
            throw new RuntimeException(ex);
        }
    }

    private void parseEvent(XMLEvent xmlEvent) throws SAXException {
        switch (xmlEvent.getEventType()) {
            case XMLEvent.ATTRIBUTE:  {
                break;
            }
            case XMLEvent.CDATA: {
                Characters characters = xmlEvent.asCharacters();
                if(null == lexicalHandler) {
                    contentHandler.characters(characters.getData().toCharArray(), 0, characters.getData().length());
                } else {
                    lexicalHandler.startCDATA();
                    contentHandler.characters(characters.getData().toCharArray(), 0, characters.getData().length());
                    lexicalHandler.endCDATA();
                }
                break;
            }
            case XMLEvent.CHARACTERS: {
                char[] characters = xmlEvent.asCharacters().getData().toCharArray();
                contentHandler.characters(characters, 0, characters.length);
                break;
            }
            case XMLEvent.COMMENT: {
                if(null != lexicalHandler) {
                    char[] comment = ((Comment) xmlEvent).getText().toCharArray();
                    lexicalHandler.comment(comment, 0, comment.length);
                }
                break;
            }
            case XMLEvent.DTD: {
                break;
            }
            case XMLEvent.END_DOCUMENT: {
                depth--;
                return;
            }
            case XMLEvent.END_ELEMENT: {
                List<Namespace> declaredNs = this.namespaces.get(depth);
                depth--;
                EndElement endElement = xmlEvent.asEndElement();

                QName name = endElement.getName();
                String prefix = endElement.getName().getPrefix();
                if(null == prefix || prefix.length() == 0) {
                    contentHandler.endElement(name.getNamespaceURI(), name.getLocalPart(), name.getLocalPart());
                } else {
                    contentHandler.endElement(name.getNamespaceURI(), name.getLocalPart(), prefix + Constants.COLON + name.getLocalPart());
                }
                if(declaredNs != null) {
                    for(Namespace next : declaredNs) {
                        contentHandler.endPrefixMapping(next.getPrefix());
                    }
                }
                break;
            }
            case XMLEvent.ENTITY_DECLARATION: {
                break;
            }
            case XMLEvent.ENTITY_REFERENCE: {
                break;
            }
            case XMLEvent.NAMESPACE: {
                break;
            }
            case XMLEvent.NOTATION_DECLARATION: {
                break;
            }
            case XMLEvent.PROCESSING_INSTRUCTION: {
                ProcessingInstruction pi = (ProcessingInstruction)xmlEvent;
                contentHandler.processingInstruction(pi.getTarget(), pi.getData());
                break;
            }
            case XMLEvent.SPACE: {
                char[] characters = xmlEvent.asCharacters().getData().toCharArray();
                contentHandler.characters(characters, 0, characters.length);
                break;
            }
            case XMLEvent.START_DOCUMENT: {
                depth++;
                break;
            }
            case XMLEvent.START_ELEMENT: {
                lastEvent = xmlEvent;

                depth++;
                StartElement startElement = xmlEvent.asStartElement();
                Iterator namespaces = startElement.getNamespaces();
                List<Namespace> declaredNs = null;
                if(namespaces.hasNext()) {
                    declaredNs = new ArrayList<Namespace>();
                }
                while(namespaces.hasNext()) {
                    Namespace next = (Namespace)namespaces.next();
                    contentHandler.startPrefixMapping(next.getPrefix(), next.getNamespaceURI());
                    declaredNs.add(next);
                }
                if(declaredNs != null) {
                    this.namespaces.put(depth, declaredNs);
                }
                QName qName = startElement.getName();
                String prefix = qName.getPrefix();
                indexedAttributeList.setIterators(startElement.getAttributes(), startElement.getNamespaces());
                if(null == prefix || prefix.length() == 0) {
                    contentHandler.startElement(qName.getNamespaceURI(), qName.getLocalPart(), qName.getLocalPart(), indexedAttributeList);
                } else {
                    contentHandler.startElement(qName.getNamespaceURI(), qName.getLocalPart(), prefix + Constants.COLON + qName.getLocalPart(), indexedAttributeList);
                }
                break;
            }
        }
    }

    // Made static final for performance reasons.
    private static final class XMLEventReaderAttributes extends IndexedAttributeList {

        private Iterator namespaces;
        private Iterator attrs;

        public void setIterators(Iterator attrs, Iterator namespaces) {
            reset();
            this.namespaces = namespaces;
            this.attrs = attrs;
        }

        @Override
        protected Attribute[] attributes() {
            if(null == attributes) {
            if(attrs.hasNext() || namespaces.hasNext()) {

                ArrayList<Attribute> attributesList = new ArrayList<Attribute>();

                while(namespaces.hasNext()) {
                    Namespace next = (Namespace)namespaces.next();
                    String uri = javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
                    String localName = next.getPrefix();
                    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 = next.getNamespaceURI();
                    attributesList.add(new Attribute(uri, localName, qName, value));
                }

                while(attrs.hasNext()) {
                    javax.xml.stream.events.Attribute next = (javax.xml.stream.events.Attribute)attrs.next();
                    String uri = next.getName().getNamespaceURI();
                    String localName = next.getName().getLocalPart();
                    String prefix = next.getName().getPrefix();
                    String qName;
                    if(null == prefix || prefix.length() == 0) {
                        qName = localName;
                    } else {
                        qName = prefix + Constants.COLON + localName;
                    }
                    String value = next.getValue();
                    attributesList.add(new Attribute(uri, localName, qName, value));
                }

                attributes = attributesList.toArray(new Attribute[attributesList.size()]);
            }else{
                attributes = NO_ATTRIBUTES;

                }
            }
            return attributes;
        }

    }

    // Made static final for performance reasons.
    /**
     * <p>An implementation of Locator, with location data provided by an existing XMLEvent.</p>
     *
     * @see org.xml.sax.Locator
     * @see javax.xml.stream.events.XMLEvent
     */
    private static final class EventReaderLocator implements Locator2 {

        private XMLEvent event;

        /**
         * Instantiates a new EventReaderLocator.
         */
        public EventReaderLocator() {
        }

        /**
         * Set the XMLEvent for this EventReaderLocator.
         *
         * @param e the XMLEvent object from which to copy location information.
         */
        public void setEvent(XMLEvent e) {
            this.event = e;
        }

        /**
         * Returns the public ID of this Locator.
         */
        public String getPublicId() {
            if (this.event == null) {
                return null;
            }
            return this.event.getLocation().getPublicId();
        }

        /**
         * Returns the system ID of this Locator.
         */
        public String getSystemId() {
            if (this.event == null) {
                return null;
            }
            return this.event.getLocation().getSystemId();
        }

        /**
         * Returns the line number of this Locator.
         */
        public int getLineNumber() {
            if (this.event == null) {
                return -1;
            }
            return this.event.getLocation().getLineNumber();
        }

        /**
         * Returns the column number of this Locator.
         */
        public int getColumnNumber() {
            if (this.event == null) {
                return -1;
            }
            return this.event.getLocation().getColumnNumber();
        }

        public String getXMLVersion() {
            return null;
        }

        public String getEncoding() {
            return null;
        }

    }

}
