// Copyright 2000-2004, FreeHEP.
package hep.graphics.heprep1.xml;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;
import javax.xml.parsers.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;

import org.freehep.util.io.*;

import hep.graphics.heprep1.*;
import hep.graphics.heprep1.ref.*;

/**
 *
 * @author M.Donszelmann
 *
 * @version $Id: XMLHepRepReader.java,v 1.4 2004/08/08 14:37:36 duns Exp $
 */

public class XMLHepRepReader implements HepRepReader {

    // NOTE: either of input or name is set.
    protected InputStream input;
    protected String name;

    private ZipEntry entry;
    
    private ZipInputStream zip;
    
    protected ZipFile zipFile;
    private Enumeration/*<ZipEntry>*/ zipEntries;
    private int position;

    private XMLSequence sequence;

    public XMLHepRepReader(InputStream input) throws IOException {
        this.input = input;
        reset();
    }

    public XMLHepRepReader(String name) throws IOException {
        this.name = name;
        reset();
    }

    protected HepRep readHepRep(InputStream stream) throws IOException {

        HepRep heprep = new DefaultHepRep();
    	try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true);
            XMLReader reader = factory.newSAXParser().getXMLReader();
    //        reader.setFeature("http://xml.org/sax/features/validation", true);
            Handler handler = new Handler(heprep);

            reader.setContentHandler(handler);
            reader.setDTDHandler(handler);
            reader.setErrorHandler(handler);
            reader.setEntityResolver(handler);

            String attributesFile = "AttributeDefaults.xml";
            InputStream attributes = this.getClass().getResourceAsStream(attributesFile);
            if (attributes == null) throw new IOException(getClass()+": Could not find "+attributesFile);
            InputSource ds = new InputSource(attributes);
            reader.parse(ds);

            // now read real document
            InputSource is = new InputSource(stream);
            reader.parse(is);
    	} catch (ParserConfigurationException e) {
            IOException exception = new IOException();
            exception.initCause(e);
            throw exception;
        } catch (SAXParseException e) {
            IOException exception = new IOException();
            exception.initCause(new SAXParseException(e.getMessage(), e.getPublicId(), e.getSystemId(), e.getLineNumber(), e.getColumnNumber()) {
                public String getMessage() {
                    return "line "+getLineNumber()+" column "+getColumnNumber()+": "+super.getMessage();
                }
            });
            throw exception;
        } catch (SAXException e) {
            IOException exception = new IOException();
            exception.initCause(e);
            throw exception;
        } 

        return heprep;
    }

    class Handler extends DefaultHandler {

        private Hashtable labelTable;

        {
            labelTable = new Hashtable(4);
            labelTable.put("NONE", new Integer(HepRepAttValue.SHOW_NONE));
        }


        private HepRep heprep;
        private Stack stack = new Stack();      // of parents
        private DefaultHepRepAttribute parent = null;

        public Handler(HepRep heprep) {
            this.heprep = heprep;
        }

        public void startDocument() throws SAXException {
            stack = new Stack();
            parent = (DefaultHepRepAttribute)heprep;
        }

        public void startElement(String namespace, String tag, String qName, Attributes atts) throws SAXException {
//            System.out.println(namespace+", "+tag+", "+qName);
            if (tag.equals("point")) {
                double x = Double.valueOf(atts.getValue("x")).doubleValue();
                double y = Double.valueOf(atts.getValue("y")).doubleValue();
                double z = Double.valueOf(atts.getValue("z")).doubleValue();
                DefaultHepRepPoint node = new DefaultHepRepPoint(parent, x, y, z);
                stack.push(parent);
                parent = node;

            } else if (tag.equals("primitive")) {
                DefaultHepRepPrimitive node = new DefaultHepRepPrimitive(parent);
                stack.push(parent);
                parent = node;

            } else if (tag.equals("instance")) {
                DefaultHepRepInstance node = new DefaultHepRepInstance(parent);
                stack.push(parent);
                parent = node;

            } else if (tag.equals("type")) {
                String name = atts.getValue("name");
                String version = atts.getValue("version");
                DefaultHepRepType node = new DefaultHepRepType(parent, name, version);
                stack.push(parent);
                parent = node;

            } else if (tag.equals("heprep")) {
                // ignored: heprep already created
            } else if (tag.equals("attvalue")) {
                String name = atts.getValue("name");
                String value = atts.getValue("value");
                String labelString = atts.getValue("showLabel");
                int showLabel = HepRepAttValue.SHOW_NONE;
                if (labelString != null) {
                    StringTokenizer st = new StringTokenizer(labelString, ", ");
                    while (st.hasMoreTokens()) {
                        String label = st.nextToken();
                        Integer number = (Integer)labelTable.get(label);
                        if (number != null) {
                            showLabel += number.intValue();
                        } else {
                            showLabel += Integer.parseInt(label);
                        }
                    }
                }
                parent.addValue(name, value, showLabel);

            } else if (tag.equals("attdef")) {
                String name = atts.getValue("name");
                String desc = atts.getValue("desc");
                String type = atts.getValue("type");
                String extra = atts.getValue("extra");
                parent.addDefinition(name, desc, type, extra);

            } else {
                throw new SAXException("[XMLHepRepReader] Unknown tag: "+tag);
            }
        }

        public void endElement(String namespace, String tag, String qName) throws SAXException {
//            System.out.println("/"+namespace+", "+tag+", "+qName);
            // FIXME: Xerces 1.1.1 seems to report qNames for tags in case the element is non empty
            if (tag.lastIndexOf(':') >= 0) tag = tag.substring(tag.lastIndexOf(':')+1);
            if (tag.equals("point")) {
                parent = (DefaultHepRepAttribute)stack.pop();
            } else if (tag.equals("primitive")) {
                parent = (DefaultHepRepAttribute)stack.pop();
            } else if (tag.equals("instance")) {
                parent = (DefaultHepRepAttribute)stack.pop();
            } else if (tag.equals("type")) {
                parent = (DefaultHepRepAttribute)stack.pop();
            } else if (tag.equals("heprep")) {
                // ignored, toplevel already stored
            } else if (tag.equals("attvalue")) {
            } else if (tag.equals("attdef")) {
            } else {
                throw new SAXException("[XMLHepRepReader] Unknown tag: "+tag);
            }
        }

    	public InputSource resolveEntity(String publicId, String systemId) {
    	    System.out.println("Resolving: "+systemId);
    	    if (publicId != null) {
    	        return null;
    	    }

            // try to open systemId directly
            InputStream is = null;
            URL url = null;

            try {
                url = new URL(systemId);
                is = url.openStream();
            } catch (MalformedURLException mfue) {
                return null;
            } catch (IOException ioe) {
                // try to resolve systemId relative to object or class
                String file = url.getFile().substring(url.getFile().lastIndexOf('/')+1);
                is = XMLHepRepReader.this.getClass().getResourceAsStream(file);
                if (is == null) {
                    is = XMLHepRepReader.class.getResourceAsStream(file);
                }
            }

            return new InputSource(is);
    	}

    } // class Handler
    
    //
    // HepRepReader interface (from HepRep2).
    //
    public void close() throws IOException {
        if (zip != null) {
            zip.close();
        }
        if (zipFile != null) {
            zipFile.close();
        }
        if (sequence != null) {
            sequence.close();
        }
    }

    public boolean hasSequentialAccess() throws IOException {
        return true;
    }

    public void reset() throws IOException, UnsupportedOperationException {
        if ((input != null) && !(input instanceof ZipInputStream)) {
            sequence = new XMLSequence(new BufferedInputStream(input));
        } else if ((name != null) && !name.toLowerCase().endsWith(".zip")) {
            if (sequence != null) sequence.close();
            if (name.toLowerCase().endsWith(".gz")) {
                sequence = new XMLSequence(new BufferedInputStream(new GZIPInputStream(new FileInputStream(name))));
            } else {
                sequence = new XMLSequence(new BufferedInputStream(new FileInputStream(name)));
            }    
        } else {
            if (input instanceof ZipInputStream) {
                zip = (ZipInputStream)input;
                zip.reset();
            } else if (name != null) {
                if (name.toLowerCase().endsWith(".zip")) {
                    zipFile = new ZipFile(name);
                    zipEntries = zipFile.entries();
                    position = 0;
                } 
            }
        }
    }
    
    public int size() {
        if (zipFile != null) return zipFile.size();
        return -1;
    }

    public int skip(int n) throws UnsupportedOperationException {
        int i = n;
        try {
            while ((i > 0) && hasNext()) {
                next();
                i--;
            }
        } catch (IOException e) {
        }
        return n-i;
    }

    public boolean hasNext() throws IOException, UnsupportedOperationException {
        if (sequence != null) return sequence.hasNext();
        if (zipFile != null) return (size() - position) > 0;
        // best we can do here, since the zip.available() seems unreliable in an XML context
        return true;
    }

    public HepRep next() throws IOException, UnsupportedOperationException, NoSuchElementException {
        if (!hasNext()) throw new UnsupportedOperationException(getClass()+" no more elements");

        if (sequence != null) {
            return readHepRep(sequence.next());
        }

        if (zip != null) {
            entry = zip.getNextEntry();
            InputStream stream = new BufferedInputStream(new NoCloseInputStream(zip));
            return readHepRep(stream);
        } 
        
        if (zipFile != null) {
            entry = (ZipEntry)zipEntries.nextElement();
            position++;
            InputStream stream = zipFile.getInputStream(entry);
            if (entry.getName().toLowerCase().endsWith(".gz")) {
                stream = new GZIPInputStream(stream);
            }
            stream = new BufferedInputStream(stream);
            HepRep heprep = readHepRep(stream);
            
            return heprep;
        }
                
        return null;
    }

    public boolean hasRandomAccess() {
        return zipFile != null;
    }

    public HepRep read(String name) throws IOException, UnsupportedOperationException, NoSuchElementException {
        if (!hasRandomAccess()) throw new UnsupportedOperationException(getClass()+" does not support random access");

        entry = zipFile.getEntry(name);
        if (entry == null) throw new NoSuchElementException(getClass()+" cannot access entry '"+name+"'");

        InputStream stream = new BufferedInputStream(zipFile.getInputStream(entry));
        return readHepRep(stream);
    }

    public String entryName() {
        return (entry != null) ? entry.getName() : null;
    }

    public List/*<String>*/ entryNames() {
        if (zipFile == null) return null;
        
        List list = new AbstractSequentialList() {
            public int size() {
                return XMLHepRepReader.this.size();
            }
            
            public ListIterator listIterator(int index) {
                final int startIndex = index;
                
                return new ListIterator() {
                    private int position;
                    private Enumeration entries;
                    private ZipEntry entry;
                
                    {
                        entries = zipFile.entries();
                        position = startIndex;
                        for (int i=0; i<=position; i++) {
                            entry = entries.hasMoreElements() ? (ZipEntry)entries.nextElement() : null;
                            if (entry == null) break;
                        }
                        if (entry == null) position = size();
                    }
                                        
                    public void add(Object o) { 
                        throw new UnsupportedOperationException();
                    }
                    
                    public boolean hasNext() {
                        return entry != null;
                    }
                    
                    public boolean hasPrevious() {
                        return false;
                    }
                    
                    public Object next() {
                        if (entry == null) throw new NoSuchElementException();
                        return entry.getName();
                    }
                    
                    public int nextIndex() {
                        return position;
                    }
                    
                    public Object previous() {
                        throw new NoSuchElementException();
                    }
                    
                    public int previousIndex() {
                        return position - 1;
                    }
                    
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }

                    public void set(Object o) {
                        throw new UnsupportedOperationException();
                    }
                }; // ListIterator
            }
        }; // AbstractSequentialList
        return list;
    }
    
}
