/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



package org.openoffice.setup.SetupData;

import org.openoffice.setup.InstallData;
import org.openoffice.setup.Util.FileExtensionFilter;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
public class XMLPackageDescription {

    /**
     * fill the package description tree by handling the SAXParser events
     */
    private class PackageDescriptionHandler extends DefaultHandler {
        
        private XMLPackageDescription root;
        private Stack stack;
        
        public PackageDescriptionHandler(XMLPackageDescription base) {
            root  = base;
            stack = new Stack();
        }
        
        private PackageDescriptionHandler() {
            /* forbidden */
        }
        
        public XMLPackageDescription getDescription() {
            return root;
        }
        
        /* implement the DefaultHandler interface */
        
        public void characters(char[] ch, int start, int length) {
            XMLPackageDescription entity = (XMLPackageDescription) stack.peek();
            entity.value = entity.value == null ? new String(ch, start, length)
                                                : entity.value + new String(ch, start, length);
        }
        public void startDocument() {
            stack.push(root);
        }
        public void endDocument() {
            stack.pop();
        }
        public void startElement(String uri, String localName, String qName, Attributes attributes) {
            XMLPackageDescription parent = (XMLPackageDescription) stack.peek();
            XMLPackageDescription entity = new XMLPackageDescription();
            
            entity.key = qName;
            for (int i = 0; i < attributes.getLength(); i++) {
                entity.attributes.put(attributes.getQName(i), attributes.getValue(i));
            }
            
            parent.add(entity);
            stack.push(entity);
        }
        public void endElement(String uri, String localName, String qName) {
            stack.pop();
        }
        public void error(SAXParseException e) {
            System.err.println("Parse Error:" + e);
        }
        public void processingInstruction(String target, String data) {
            /* ignore */
        }
        public void skippedEntity(String name) {
            /* ignore */
        }
        public void warning(SAXParseException e) {
            System.err.println("Warning:" + e);
        }
    }
    
    /**
     * general storage for XML elements
     */
    
    private String key;             /* XML element name       */
    private String value;           /* XML element characters */
    private Hashtable attributes;   /* XML element attributes */
    private Vector children;        /* children are of type XMLPackageDescription */

    protected XMLPackageDescription() {
        key        = "";
        value      = null;
        attributes = new Hashtable();
        children   = new Vector();
    }
    
    private void add(XMLPackageDescription p) {
        children.add(p);
    }
    
    /**
     * helper routines to find content information
     */
    protected String getKey() {
        return key;
    }
    protected String getAttribute(String key) {
        return (String) attributes.get(key);        
    }
    protected String getValue() {
        return value;
    }
    protected XMLPackageDescription getElement(String key) {        
        return getElement(key, null, null);
    }
    protected XMLPackageDescription getElement(String key, String attrKey, String attrValue) {
        for (Enumeration e = children.elements(); e.hasMoreElements();) {
            XMLPackageDescription child = (XMLPackageDescription) e.nextElement();
            if (child.key.equals(key)) {
                if (attrKey == null) {
                    return child;
                } else if (attrValue.equals(child.getAttribute(attrKey))) {
                    return child;
                }
            }
        }
        return null;
    }

    /**
     * find a PackageDescription of type package that has the given name,
     * recurses into the children
     */
    private XMLPackageDescription findPackage(String name) {
        String self = (String) attributes.get("name");

        if ((self != null) && self.equals(name))
            return this;
        
        XMLPackageDescription found = null;
        for (Enumeration e = children.elements(); e.hasMoreElements();) {
            XMLPackageDescription child = (XMLPackageDescription) e.nextElement();
            if (child.getAttribute("parent") != null) {
                found = child.findPackage(name);
                if (found != null) {
                    break;
                }
            }
        }        
        return found;
    }
    
    /** 
     * adjust the tree so that all children have a matching parent and not just
     * the ones they got by reading files in random order
     */
    private void adjust(XMLPackageDescription root) {        
        String self = (String) attributes.get("name");
        
        for (int i = children.size() - 1; i >= 0; --i) {
            XMLPackageDescription child = (XMLPackageDescription) children.elementAt(i);
            String childParentName = child.getAttribute("parent");
            if (childParentName != null) {
                
                child.adjust(root);
                
                if ((childParentName != null) && (childParentName.length() > 0) && (! childParentName.equals(self))) {
                    XMLPackageDescription newParent = root.findPackage(childParentName);
                    if (newParent != null) {
                        newParent.add(child);
                        children.remove(i);
                    }
                }
            }
        }
    }
    
    protected void read() {
        PackageDescriptionHandler handler = new PackageDescriptionHandler(this);
        
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();

            InstallData data = InstallData.getInstance();
            File xpdRoot = data.getInfoRoot("xpd");

            if ( xpdRoot != null ) {
                File[] file = xpdRoot.listFiles(new FileExtensionFilter("xpd"));

                if (file != null) {
                    for (int i = 0; i < file.length; i++) {
                        parser.parse(file[i], handler);
                    }
                } else {
                    key   = "";
                    value = "no package file found";
                }
            }
            else {
                System.err.println("Did not find xpd directory");
            }
        } catch (ParserConfigurationException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (SAXException ex) {
            ex.printStackTrace();
        }
        
        adjust(this);
    }
    
    /* provide an iterator through the children */
    protected class Elements implements Enumeration {
        
        Enumeration e;

        protected Elements() {
            e = children.elements();
        }         
        public boolean hasMoreElements() {
            return e.hasMoreElements();
        }
        public Object nextElement() {
            return e.nextElement();
        }     
    }
    
    protected Enumeration elements() {
        return new Elements();
    }
    
    
    // FIXME: remove it, dump() is for testing only
    public void dump() {
        dump("");
    }
    
    // FIXME: remove it, dump(String) is for testing only
    public void dump(String indent) {
        final String space = "    ";
        if (key != null) {
            System.out.print(indent + "<" + key);
        }
        for (Enumeration e = attributes.keys() ; e.hasMoreElements() ;) {
            String key   = (String) e.nextElement();
            String value = (String) attributes.get(key);
            System.out.print(" " + key + "=\"" + value + "\"");
        }
        if (key != null) {
            System.out.print(">");
        }
        
        if ((value != null) && (value.length() > 0)) {
            String trimmedValue = value.trim();
            if (trimmedValue.length() > 60) {
                trimmedValue = trimmedValue.substring(0, 57) + "...";
            }
            if (trimmedValue.length() > 0) {
                System.out.print(trimmedValue);
            }
        }
        
        for (Enumeration e = children.elements() ; e.hasMoreElements() ;) {
            XMLPackageDescription current = (XMLPackageDescription) e.nextElement();
            System.out.println();
            current.dump(indent + space);
        }
        if (children.size() > 0) {
            System.out.println();
            System.out.print(indent);
        }
        if (key != null) {
           System.out.print("</" + key + ">"); 
        }
    }
}
