// Copyright 2000-2005, FreeHEP.
package hep.graphics.heprep.xml;

import java.io.*;
import java.net.*;
import java.util.*;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import org.freehep.util.VersionComparator;

import hep.graphics.heprep.*;
import hep.graphics.heprep.ref.*;
import hep.graphics.heprep.util.*;

/**
 *
 * @author M.Donszelmann
 *
 * @version $Id: XMLHepRepHandler.java,v 1.35 2005/02/27 22:58:50 duns Exp $
 */

public class XMLHepRepHandler extends DefaultHandler {
    public static final String cvsId = "$Id: XMLHepRepHandler.java,v 1.35 2005/02/27 22:58:50 duns Exp $";

    public static final String expectedVersion = "2.0";
    public boolean isHepRep2 = false;

    private HepRepFactory factory;
    private HepRep heprep;
    private int level = 0;

// NOTE: we could use HepRepTreeID to index this Map, but we need to override equals() and
//       hashCode() in DefaultHepRepTreeID and DefaultHepRepTree...
    private Map/*<name:version, HepRepTypeTree>*/ typeTrees = null;
    private Map/*<name:version,Map<String,HepRepType>>*/ flatTypeTrees = new HashMap();      
                               // if null, typetree does not have flat space

    private HepRepTypeTree currentTypeTree = null;
    private HepRepTreeID currentTypeTreeRef = null;
    private HepRepInstanceTree instanceTree = null;

    private HepRepPoint currentPoint = null;

    private Stack/*<HepRepInstance>*/ instanceStack = new Stack();
    private HepRepInstance currentInstance = null;

    private Stack/*<HepRepType>*/ typeStack = new Stack();
    private HepRepType currentType = null;

    XMLHepRepHandler(HepRep heprep) {
        this.heprep = heprep;
        factory = new XMLHepRepFactory();
    }

    public void startDocument() throws SAXException {
        typeTrees = new HashMap();
        currentTypeTree = null;
        currentTypeTreeRef = null;
        instanceTree = null;
        currentPoint = null;
        flatTypeTrees = new HashMap();
        instanceStack = new Stack();
        currentInstance = null;
        typeStack = new Stack();
        currentType = null;
        level = 0;
    }

    public void startElement(String namespace, String tag, String qName, Attributes atts) throws SAXException {
        level++;
        tag = tag.intern();
//               System.out.println(namespace+", "+tag+", "+qName);
        if (tag == "point") {
            double x = HepRepUtil.decodeNumber(atts.getValue("x"));
            double y = HepRepUtil.decodeNumber(atts.getValue("y"));
            double z = HepRepUtil.decodeNumber(atts.getValue("z"));
            currentPoint = factory.createHepRepPoint(currentInstance, x, y, z);

        } else if (tag == "instancetree") {
            String name = atts.getValue("name");
            String version = atts.getValue("version");
            currentTypeTreeRef = factory.createHepRepTreeID(atts.getValue("typetreename"), atts.getValue("typetreeversion"));
            instanceTree = factory.createHepRepInstanceTree(name, version, currentTypeTreeRef);
            heprep.addInstanceTree(instanceTree);
//                System.out.println("Created Instance Tree: "+name+", "+version);

        } else if (tag == "treeid") {
            String name = atts.getValue("name");
            String version = atts.getValue("version");
            String qualifier = atts.getValue("qualier");
            instanceTree.addInstanceTree(factory.createHepRepTreeID(name, version, qualifier));

        } else if (tag == "action") {
            String name = atts.getValue("name");
            String expression = atts.getValue("expression");
            // FIXME: FREEHEP-369
            factory.createHepRepAction(name, expression);

        } else if (tag == "layer") {
            String order = atts.getValue("order");
            StringTokenizer st = new StringTokenizer(order,",");
            while (st.hasMoreTokens()) {
                heprep.addLayer(st.nextToken().trim());
            }

        } else if (tag == "instance") {
            String typeName = atts.getValue("type");
            if (typeName == null) {
                throw new SAXException("[XMLHepRepReader] Instance cannot exist without referring to a type.");
            }
            HepRepType type = getType(typeName, currentInstance);
            if (type == null) {
                throw new SAXException("[XMLHepRepReader] Cannot find type: '"+typeName+"' "+
                                       "in tree: '"+getID(currentTypeTreeRef)+"'");
            }
            instanceStack.push(currentInstance);
            if (currentInstance != null) {
                currentInstance = factory.createHepRepInstance(currentInstance, type);
            } else {
                currentInstance = factory.createHepRepInstance(instanceTree, type);
            }

        } else if (tag == "typetree") {
            isHepRep2 = true;
            String name = atts.getValue("name");
            String version = atts.getValue("version");
            HepRepTreeID id = factory.createHepRepTreeID(name, version);
            currentTypeTree = factory.createHepRepTypeTree(id);
            heprep.addTypeTree(currentTypeTree);
            
            typeTrees.put(getID(id), currentTypeTree);
            flatTypeTrees.put(getID(id), new HashMap());
//                System.out.println("Created Type Tree: "+name+", "+version);

        } else if (tag == "type") {
            // if we did not get a typetree, no heprep2.
            if (!isHepRep2) {
                throw new SAXException(
                    new HepRepVersionException(getClass()+": Could not deduce heprep version, expected version '"+expectedVersion+"'.")
                ); 
            }

            String name = atts.getValue("name");
            typeStack.push(currentType);
            if (currentType != null) {
                currentType = factory.createHepRepType(currentType, name);
            } else {
                currentType = factory.createHepRepType(currentTypeTree, name);
            }
            Map/*<String, HepRepType>*/ types = (Map)flatTypeTrees.get(getID(currentTypeTree));
            if (types != null) {
                if (types.get(name) == null) {
                    types.put(name, currentType);
                } else {
                    flatTypeTrees.put(getID(currentTypeTree), null);
                }
            }
            

        } else if (tag == "heprep") {
            // heprep already created, check version
            String version = atts.getValue("version");
            if (version != null) {
                isHepRep2 = true;
                VersionComparator comparator = new VersionComparator();
                if (comparator.versionNumberCompare(version,expectedVersion) < 0) {
                    throw new SAXException(
                        new HepRepVersionException(getClass()+": Found version '"+version+"' while expected version '"+expectedVersion+"'.")
                    );
                }
            } else {
                // check for hepreps with no versions attribute
                String xmlns = atts.getValue("xmlns");
                if ((xmlns != null) && xmlns.startsWith("http://java.freehep.org/schemas/heprep/2")) {
                    isHepRep2 = true;
                }
            }
        } else if (tag == "attvalue") {
            String name = atts.getValue("name");
            String value = atts.getValue("value");
            String type = DefaultHepRepAttValue.guessType(name, value, atts.getValue("type"));
            // Here for backward compatibility with G4.5.0 we keep the cased showLabel around
            String showLabelString = atts.getValue("showlabel");
            if (showLabelString == null) showLabelString = atts.getValue("showLabel");
            int showLabel = DefaultHepRepAttValue.toShowLabel(showLabelString);

            HepRepAttValue attValue = new DefaultHepRepAttValue(name, value, type, showLabel);

            if (currentPoint != null) {
                currentPoint.addAttValue(attValue);
            } else if (currentInstance != null) {
                currentInstance.addAttValue(attValue);
            } else {
                if (currentType == null) {
                    throw new SAXException("[XMLHepRepReader] Cannot use 'attvalue' outside 'type' tag.");
                }
                currentType.addAttValue(attValue);
            }

        } else if (tag == "attdef") {
            if (!isHepRep2) {
                throw new SAXException(
                    new HepRepVersionException(getClass()+": Found a probable HepRep1 version while expected version '"+expectedVersion+"'.")
                );
            }
        
            if (currentType == null) {
                throw new SAXException("[XMLHepRepReader] Cannot use 'attdef' outside 'type' tag.");
            }

            String name = atts.getValue("name");
            String desc = atts.getValue("desc");
            String category = atts.getValue("category");
            String extra = atts.getValue("extra");
            currentType.addAttDef(name, desc, category, extra);

        } else {
            throw new SAXException("[XMLHepRepReader] Unknown tag: <"+
                                   ((namespace != null) ? namespace+":" : "")+
                                   tag+"> qualfiedName: '"+qName+"'");
        }

        if (Thread.interrupted()) throw new SAXException(new InterruptedException());
    }

    public void endElement(String namespace, String tag, String qName) throws SAXException {
        try {
//                System.out.println("/"+namespace+", "+tag+", "+qName);
            if (tag.lastIndexOf(':') >= 0) tag = tag.substring(tag.lastIndexOf(':')+1);
            tag = tag.intern();
            if (tag == "point") {
                currentPoint = null;
            } else if (tag == "instancetree") {
                instanceTree = null;
                currentTypeTreeRef = null;
            } else if (tag == "treeid") {
            } else if (tag == "layer") {
            } else if (tag == "instance") {
                if (currentInstance instanceof DefaultHepRepInstance) ((DefaultHepRepInstance)currentInstance).optimize();
                currentInstance = (HepRepInstance)instanceStack.pop();
            } else if (tag == "typetree") {
                currentTypeTree = null;
            } else if (tag == "type") {
                currentType = (HepRepType)typeStack.pop();
            } else if (tag == "heprep") {
                // ignored, toplevel already stored
            } else if (tag == "attvalue") {
            } else if (tag == "attdef") {
            } else {
                throw new SAXException("[XMLHepRepReader] Unknown tag: "+tag);
            }
        } catch (Exception e) {
            throw new SAXException(e);
        }

        if (Thread.interrupted()) throw new SAXException(new InterruptedException());

        // make sure this is at the end, and that heprep is set!
        level--;
//        if (level == 0) throw new DocumentFinishedException();
    }

	public InputSource resolveEntity(String publicId, String 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 = getClass().getResourceAsStream(file);
        }

        return new InputSource(is);
	}

    private String getID(HepRepTreeID id) {
        return id.getName()+":"+id.getVersion();
    }
    
    
    private HepRepType getType(String name, HepRepInstance parent) {
        // check flat namespace
        HepRepType type = getTypeFromFlatNamespace(name);
        if (type != null) return type;
        
        // check hierarchical namespace
        type = getTypeFromHierarchicalNamespace(name);
        if (type != null) return type;

        // (GLAST) check hirearchical namespace by pre-pending parents full typename
        return getTypeFromHierarchicalNamespace(parent.getType().getFullName()+"/"+name);
    }
    
    private HepRepType getTypeFromFlatNamespace(String name) {
        HepRepType type = null;

        // flat types
        Map/*<String, HepRepType>*/ flatTypes = (Map)flatTypeTrees.get(getID(currentTypeTreeRef));
        if (flatTypes == null) return null;
        
        return (HepRepType)flatTypes.get(name);
    }
     
    private HepRepType getTypeFromHierarchicalNamespace(String name) {           
        HepRepType type = null;

        // hierarchical types        
        HepRepTypeTree tree = (HepRepTypeTree)typeTrees.get(getID(currentTypeTreeRef));
        if (tree == null) return null;
        
        // remove leading slash
        if (name.charAt(0) == '/') name = name.substring(1);
        
        Collection types = tree.getTypeList();
            
        // look if all partNames can be found in all types
        int slash;
        do {
            // split name into leading partName and everything else behind the slash
            slash = name.indexOf("/");
            String partName = (slash < 0) ? name : name.substring(0, slash);
            name = (slash < 0) ? "" : name.substring(slash+1);
            
            // look for partName in all types
            if (types == null) return null;
            Iterator typeIterator = types.iterator();
            boolean found = false;
            while (!found && typeIterator.hasNext()) {
                type = (HepRepType)typeIterator.next();
                if (type.getName().equals(partName)) {
                    types = type.getTypeList();
                    found = true;
                }
            }
            if (!found) return null;
        } while (slash >= 0);

        return type;
    }
}
