package com.limegroup.gnutella.xml;


import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.xerces.parsers.SAXParser;

import com.limegroup.gnutella.ErrorService;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides just enough functionality for our simple schemas,
 * based on SAX
 * @author  tjones
 */
public class XMLParsingUtils {
    
    private static final Log LOG = LogFactory.getLog(XMLParsingUtils.class);
    

    static final private String XML_START = "<?xml";
    
    /**
     * a ThreadLocal to contain the instance of the Lime parser
     */
    private static ThreadLocal _parserContainer = new ThreadLocal() {
        protected Object initialValue() {
            return new LimeParser();
        }
    };
    
    /**
     * Parses our simplified XML
     */
    public static ParseResult parse(String xml, int responseCount) 
      throws IOException, SAXException {
        return parse(new InputSource(new StringReader(xml)),responseCount);
    }
    
    public static ParseResult parse(InputSource inputSource) 
      throws IOException,SAXException {
        return parse(inputSource, 8);
    }
    
    /**
     * Parses our simplified XML
     */
    public static ParseResult parse(InputSource inputSource, int responseCount) 
      throws IOException, SAXException {
        ParseResult result = new ParseResult(responseCount);
        LimeParser parser = (LimeParser)_parserContainer.get();
        parser.parse(result,inputSource);
        return result;
    }

    /**
     * Splits an aggregated XML string into individual XML strings
     * @param aggregatedXmlDocuments
     * @return List of Strings
     */    
    public static List split(String aggregatedXmlDocuments) {
        List results = new ArrayList();
        
        int begin=aggregatedXmlDocuments.indexOf(XML_START);
        int end=aggregatedXmlDocuments.indexOf(XML_START,begin+1);
        
        while(end!=-1) {
            results.add(aggregatedXmlDocuments.substring(begin,end));
            begin = end;
            end = aggregatedXmlDocuments.indexOf(XML_START,begin+1);
        }
        
        if(begin!=-1) 
            results.add(aggregatedXmlDocuments.substring(begin));
        
        return results;
    }
    
    /**
     * A list of maps, also containing the Schema URI, the type and
     * the canonical key prefix
     */
    public static class ParseResult extends ArrayList {
        
        public ParseResult(int size) {
            super(size*2/3);
        }
        
        public String schemaURI;            //like http://www.limewire.com/schemas/audio.xsd
        public String type;                 //e.g. audio, video, etc.
        public String canonicalKeyPrefix;   //like audios__audio__
    }
    
    /**
     * this class does the actual parsing of the document.  It is a reusable
     * DocumentHandler.
     */
    private static class LimeParser extends DefaultHandler {
        private final XMLReader _reader;
        private ParseResult _result;
        
        boolean _isFirstElement=true;
        
        LimeParser() {
            XMLReader reader;
            try {
                reader = new SAXParser();
                reader.setContentHandler(this);
                reader.setFeature("http://xml.org/sax/features/namespaces", false);
            }catch(SAXException bad) {
                ErrorService.error(bad);
                reader = null; 
            }
            _reader=reader;
        }
        
        /**
         * parses the given document input.  Any state from previous parsing is
         * discarded.
         */
        public void parse(ParseResult dest, InputSource input) 
        	throws SAXException, IOException {
            
            //if parser creation failed, do not try to parse.
            if (_reader==null)
                return;
            
            _isFirstElement=true;
            _result = dest;

            _reader.parse(input);
        }
        
        public void startElement(String namespaceUri, String localName, 
                                 String qualifiedName, Attributes attributes) {
            if(_isFirstElement) {
                _isFirstElement=false; 
                _result.canonicalKeyPrefix = qualifiedName;
                return;
            }
            
            if(_result.type==null) {
                _result.type = qualifiedName;
                _result.schemaURI = "http://www.limewire.com/schemas/"+_result.type+".xsd";
                _result.canonicalKeyPrefix += "__"+qualifiedName+"__";
            } 
            
            int attributesLength = attributes.getLength();
            if(attributesLength > 0) {
                Map attributeMap = new HashMap(attributesLength);
                for(int i = 0; i < attributesLength; i++) {
                    attributeMap.put(_result.canonicalKeyPrefix + 
                                     attributes.getQName(i) + "__",
                                     attributes.getValue(i).trim());
                }
                _result.add(attributeMap);
            } else {
                _result.add(Collections.EMPTY_MAP);
            }
        }
    }
}
