/*
 * 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:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.oxm.record.deferred;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.persistence.internal.oxm.record.ExtendedContentHandler;
import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;
import org.eclipse.persistence.internal.oxm.record.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;

/**
 * <p><b>Purpose</b>: ContentHandler to store events until we know if we are dealing with a simple, complex or empty element.
 * <p><b>Responsibilities</b>:<ul>
 * <li> Store events until will know if the element is simple, complex or empty
 * <li> Return control to the original unmarshalRecord
 * </ul>
 */
public abstract class DeferredContentHandler implements ExtendedContentHandler, LexicalHandler {
    private int levelIndex;
    private List<SAXEvent> events;
    private UnmarshalRecord parent;
    private boolean startOccurred;
    private boolean charactersOccurred;
    private boolean attributesOccurred;

    public DeferredContentHandler(UnmarshalRecord parentRecord) {
        levelIndex = 0;
        events = new ArrayList<SAXEvent>();
        this.parent = parentRecord;
    }


    @Override
    public void setNil(boolean isNil) {}

    protected abstract void processEmptyElement() throws SAXException;

    protected abstract void processComplexElement() throws SAXException;

    protected abstract void processSimpleElement() throws SAXException;

    protected void processEmptyElementWithAttributes() throws SAXException {
        processEmptyElement();
    }

    protected void executeEvents(UnmarshalRecord unmarshalRecord) throws SAXException {
        for (int i = 0; i < events.size(); i++) {
            SAXEvent nextEvent = events.get(i);
            nextEvent.processEvent(unmarshalRecord);
        }
        XMLReader parentXMLReader = parent.getXMLReader();
        if (parentXMLReader.getContentHandler().equals(this)) {
            parentXMLReader.setContentHandler(unmarshalRecord);
            parentXMLReader.setLexicalHandler(unmarshalRecord);
        }
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        StartPrefixMappingEvent event = new StartPrefixMappingEvent(prefix, uri);
        events.add(event);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        EndPrefixMappingEvent event = new EndPrefixMappingEvent(prefix);
        events.add(event);
    }

    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        levelIndex++;

        //Copy attributes because some parsers reuse the Attributes object across start element events
        Attributes copiedAttrs = buildAttributeList(atts);

        StartElementEvent event = new StartElementEvent(uri, localName, qName, copiedAttrs);
        events.add(event);

        if (startOccurred) {
            //we know it's complex and non-null
            processComplexElement();
            return;
        }

        startOccurred = true;
    }

    protected AttributeList buildAttributeList(Attributes attrs) throws SAXException {
        int attrsLength = attrs.getLength();
        AttributeList attributes = new AttributeList(attrsLength);
        for (int i = 0; i < attrsLength; i++) {
            String qName = attrs.getQName(i);
            String uri = attrs.getURI(i);
            attributes.addAttribute(attrs.getLocalName(i), qName, uri, attrs.getType(i), attrs.getValue(i), i);
            if(!javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(uri) && (null != qName && !qName.startsWith(javax.xml.XMLConstants.XMLNS_ATTRIBUTE))) {
                attributesOccurred = true;
            }
        }
        return attributes;
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        levelIndex--;
        EndElementEvent event = new EndElementEvent(uri, localName, qName);
        events.add(event);

        if (charactersOccurred) {
            //we know it is a simple element
            processSimpleElement();
        } else if(startOccurred){
            //we know it is an empty element
            if(attributesOccurred) {
                processEmptyElementWithAttributes();
            } else {
                processEmptyElement();
            }
        }

        if ((levelIndex == 0) && (parent != null)) {
            XMLReader xmlReader = parent.getXMLReader();
            xmlReader.setContentHandler(parent);
            xmlReader.setLexicalHandler(parent);
        }
    }

    public void setDocumentLocator(Locator locator) {
        DocumentLocatorEvent event = new DocumentLocatorEvent(locator);
        events.add(event);
    }

    public void startDocument() throws SAXException {
        StartDocumentEvent event = new StartDocumentEvent();
        events.add(event);
    }

    public void endDocument() throws SAXException {
        EndDocumentEvent event = new EndDocumentEvent();
        events.add(event);
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        charactersOccurred = true;
        CharactersEvent event = new CharactersEvent(ch, start, length);
        events.add(event);
    }

    public void characters(CharSequence characters) {
        charactersOccurred = true;
        CharactersEvent event = new CharactersEvent(characters);
        events.add(event);
    }

    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
        IgnorableWhitespaceEvent event = new IgnorableWhitespaceEvent(ch, start, length);
        events.add(event);
    }

    public void processingInstruction(String target, String data) throws SAXException {
        ProcessingInstructionEvent event = new ProcessingInstructionEvent(target, data);
        events.add(event);
    }

    public void skippedEntity(String name) throws SAXException {
        SkippedEntityEvent event = new SkippedEntityEvent(name);
        events.add(event);
    }

    public void startDTD(String name, String publicId, String systemId) throws SAXException {
        StartDTDEvent event = new StartDTDEvent(name, publicId, systemId);
        events.add(event);
    }

    public void endDTD() throws SAXException {
        EndDTDEvent event = new EndDTDEvent();
        events.add(event);
    }

    public void startEntity(String name) throws SAXException {
        StartEntityEvent event = new StartEntityEvent(name);
        events.add(event);
    }

    public void endEntity(String name) throws SAXException {
        EndEntityEvent event = new EndEntityEvent(name);
        events.add(event);
    }

    public void startCDATA() throws SAXException {
        StartCDATAEvent event = new StartCDATAEvent();
        events.add(event);
    }

    public void endCDATA() throws SAXException {
        EndCDATAEvent event = new EndCDATAEvent();
        events.add(event);
    }

    public void comment(char[] ch, int start, int length) throws SAXException {
        CommentEvent event = new CommentEvent(ch, start, length);
        events.add(event);
    }

    protected UnmarshalRecord getParent() {
        return parent;
    }

    protected List getEvents() {
        return events;
    }

    // Made static final for performance reasons.
    /**
     * Implementation of Attributes - used to pass along a given node's attributes
     * to the startElement method of the reader's content handler.
     */
    private static final class AttributeList implements org.xml.sax.Attributes {

      private String[] localNames;
      private String[] uris;
      private String[] values;
      private String[] types;
      private ArrayList<String> qNames;

        public AttributeList(int size) {
          qNames = new ArrayList(size);
          localNames = new String[size];
          uris = new String[size];
          types= new String[size];
          values = new String[size];
        }

        public void addAttribute(String localName, String qName, String uri, String type, String value, int index) {
          qNames.add(index, qName);
          localNames[index] = localName;
          uris[index] = uri;
          types[index] = type;
          values[index] = value;
        }

        public String getQName(int index) {
            return qNames.get(index);
        }

        public String getType(String namespaceUri, String localName) {

            for(int i=0;i <localNames.length; i++){
                String nextLocalName = localNames[i];
                if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
               String uriAtIndex = uris[i];
               if(uriAtIndex == null) {
                   uriAtIndex="";
               }
               if(uriAtIndex.equals(namespaceUri)){
                  return types[i];
                    }
                }
            }
           return null;

        }

        public String getType(int index) {
          return types[index];
        }

        public String getType(String qname) {
          return types[getIndex(qname)];
        }

        public int getIndex(String qname) {
            return qNames.indexOf(qname);
        }

        public int getIndex(String uri, String localName) {

             for(int i=0;i <localNames.length; i++){
                 String nextLocalName = localNames[i];
                 if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
                     String uriAtIndex = uris[i];
                     if(uriAtIndex == null) {
                         uriAtIndex="";
                     }
                     if(uriAtIndex.equals(uri)){
                         return i;
                     }
                 }
             }
            return -1;
        }

        public int getLength() {
            return localNames.length;
        }

        public String getLocalName(int index) {
            return localNames[index];
        }

        public String getURI(int index) {
            return uris[index];
        }

        public String getValue(int index) {
               return values[index];
        }

        public String getValue(String qname) {
          return values[getIndex(qname)];
        }

        public String getValue(String uri, String localName) {
         for(int i=0;i <localNames.length; i++){
             String nextLocalName = localNames[i];
             if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
                 String uriAtIndex = uris[i];
                 //handle null/empty namespace case
                 if(uriAtIndex == null) {
                     uriAtIndex="";
                 }
                 if(uriAtIndex.equals(uri)){
                     return values[i];
                 }
             }
         }
         return null;
        }

    }
}
