/*******************************************************************************
 * Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * 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;      	
        }
        
    }
}
