/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 * 
 * http://izpack.org/
 * http://izpack.codehaus.org/
 * 
 * Copyright 2004 Klaus Bartz
 * 
 * Licensed 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 com.izforge.izpack.util;

import com.izforge.izpack.installer.InstallerException;
import com.izforge.izpack.installer.ResourceManager;
import com.izforge.izpack.adaptator.*;
import com.izforge.izpack.adaptator.impl.XMLParser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Vector;

/**
 * This class contains some helper methods to simplify handling of xml specification files.
 *
 * @author Klaus Bartz
 */
public class SpecHelper {

    private String specFilename;

    private IXMLElement spec;

    private boolean _haveSpec;

    public static final String YES = "yes";

    public static final String NO = "no";

    private static final String PACK_KEY = "pack";

    private static final String PACK_NAME = "name";

    /**
     * The default constructor.
     */
    public SpecHelper() {
        super();
    }

    /*--------------------------------------------------------------------------*/
    /**
     * Reads the XML specification given by the file name. The result is stored in spec.
     *
     * @throws Exception for any problems in reading the specification
     */
    /*--------------------------------------------------------------------------*/
    public void readSpec(String specFileName) throws Exception {
        readSpec(specFileName, null);
    }

    /*--------------------------------------------------------------------------*/
    /**
     * Reads the XML specification given by the file name. The result is stored in spec.
     *
     * @throws Exception for any problems in reading the specification
     */
    /*--------------------------------------------------------------------------*/
    public void readSpec(String specFileName, VariableSubstitutor substitutor) throws Exception {
        // open an input stream
        InputStream input = null;
        try {
            input = getResource(specFileName);
        }
        catch (Exception exception) {
            _haveSpec = false;
            return;
        }
        if (input == null) {
            _haveSpec = false;
            return;
        }

        readSpec(input, substitutor);

        // close the stream
        input.close();
        this.specFilename = specFileName;
    }

    /*--------------------------------------------------------------------------*/
    /**
     * Reads the XML specification given by the input stream. The result is stored in spec.
     *
     * @throws Exception for any problems in reading the specification
     */
    /*--------------------------------------------------------------------------*/
    public void readSpec(InputStream input) throws Exception {
        readSpec(input, null);
    }

    /*--------------------------------------------------------------------------*/
    /**
     * Reads the XML specification given by the input stream. The result is stored in spec.
     *
     * @throws Exception for any problems in reading the specification
     */
    /*--------------------------------------------------------------------------*/
    public void readSpec(InputStream input, VariableSubstitutor substitutor) throws Exception {
        // first try to substitute the variables
        if (substitutor != null) {
            input = substituteVariables(input, substitutor);
        }

        // initialize the parser
        IXMLParser parser = new XMLParser();

        // get the data
        spec = parser.parse(input);
        _haveSpec = true;
    }

    /**
     * Gets the stream to a resource.
     *
     * @param res The resource id.
     * @return The resource value, null if not found
     */
    public InputStream getResource(String res) {
        try {
            // System.out.println ("retrieving resource " + res);
            return ResourceManager.getInstance().getInputStream(res);
        }
        catch (Exception e) { // Cannot catch ResourceNotFoundException because it is not public.
            return null;
        }
    }

    /**
     * Returns a XML element which represents the pack for the given name.
     *
     * @param packDestName name of the pack which should be returned
     * @return a XML element which represents the pack for the given name
     */
    public IXMLElement getPackForName(String packDestName) {
        Vector<IXMLElement> packs = getSpec().getChildrenNamed(PACK_KEY);
        Iterator<IXMLElement> iter = null;
        if (packs == null) {
            return (null);
        }
        iter = packs.iterator();
        while (iter.hasNext()) {

            IXMLElement pack = iter.next();
            String packName = pack.getAttribute(PACK_NAME);
            if (packName.equals(packDestName)) {
                return (pack);
            }
        }
        return (null);

    }

    /**
     * Create parse error with consistent messages. Includes file name and line # of parent. It is
     * an error for 'parent' to be null.
     *
     * @param parent  The element in which the error occured
     * @param message Brief message explaining error
     */
    public void parseError(IXMLElement parent, String message) throws InstallerException {
        throw new InstallerException(specFilename + ":" + parent.getLineNr() + ": " + message);
    }

    /**
     * Returns true if a specification exist, else false.
     *
     * @return true if a specification exist, else false
     */
    public boolean haveSpec() {
        return _haveSpec;
    }

    /**
     * Returns the specification.
     *
     * @return the specification
     */
    public IXMLElement getSpec() {
        return spec;
    }

    /**
     * Sets the specifaction to the given XML element.
     *
     * @param element
     */
    public void setSpec(IXMLElement element) {
        spec = element;
    }

    /**
     * Returns a Vector with all leafs of the tree which is described with childdef.
     *
     * @param root     the IXMLElement which is the current root for the search
     * @param childdef a String array which describes the tree; the last element contains the leaf
     *                 name
     * @return a Vector of XMLElements of all leafs founded under root
     */
    public Vector<IXMLElement> getAllSubChildren(IXMLElement root, String[] childdef) {
        return (getSubChildren(root, childdef, 0));
    }

    /**
     * Returns a Vector with all leafs of the tree which is described with childdef beginning at the
     * given depth.
     *
     * @param root     the IXMLElement which is the current root for the search
     * @param childdef a String array which describes the tree; the last element contains the leaf
     *                 name
     * @param depth    depth to start in childdef
     * @return a Vector of XMLElements of all leafs founded under root
     */
    private Vector<IXMLElement> getSubChildren(IXMLElement root, String[] childdef, int depth) {
        Vector<IXMLElement> retval = null;
        Vector<IXMLElement> retval2 = null;
        Vector<IXMLElement> children = root != null ? root.getChildrenNamed(childdef[depth]) : null;
        if (children == null) {
            return (null);
        }
        if (depth < childdef.length - 1) {
            Iterator<IXMLElement> iter = children.iterator();
            while (iter.hasNext()) {
                retval2 = getSubChildren(iter.next(), childdef, depth + 1);
                if (retval2 != null) {
                    if (retval == null) {
                        retval = new Vector<IXMLElement>();
                    }
                    retval.addAll(retval2);
                }
            }
        } else {
            return (children);
        }
        return (retval);
    }

    /**
     * Creates an temp file in to the substitutor the substituted contents of input writes; close it
     * and (re)open it as FileInputStream. The file will be deleted on exit.
     *
     * @param input       the opened input stream which contents should be substituted
     * @param substitutor substitutor which should substitute the contents of input
     * @return a file input stream of the created temporary file
     * @throws Exception
     */
    public InputStream substituteVariables(InputStream input, VariableSubstitutor substitutor)
            throws Exception {
        File tempFile = File.createTempFile("izpacksubs", "");
        FileOutputStream fos = null;
        tempFile.deleteOnExit();
        try {
            fos = new FileOutputStream(tempFile);
            substitutor.substitute(input, fos, null, "UTF-8");
        }
        finally {
            if (fos != null) {
                fos.close();
            }
        }
        return new FileInputStream(tempFile);
    }

    /**
     * Returns whether the value to the given attribute is "yes" or not. If the attribute does not
     * exist, or the value is not "yes" and not "no", the default value is returned.
     *
     * @param element      the XML element which contains the attribute
     * @param attribute    the name of the attribute
     * @param defaultValue the default value
     * @return whether the value to the given attribute is "yes" or not
     */
    public boolean isAttributeYes(IXMLElement element, String attribute, boolean defaultValue) {
        String value = element.getAttribute(attribute, (defaultValue ? YES : NO));
        if (value.equalsIgnoreCase(YES)) {
            return true;
        }
        if (value.equalsIgnoreCase(NO)) {
            return false;
        }

        return defaultValue;
    }

    /**
     * Returns the attribute for the given attribute name. If no attribute exist, an
     * InstallerException with a detail message is thrown.
     *
     * @param element  XML element which should contain the attribute
     * @param attrName key of the attribute
     * @return the attribute as string
     * @throws InstallerException
     */
    public String getRequiredAttribute(IXMLElement element, String attrName)
            throws InstallerException {
        String attr = element.getAttribute(attrName);
        if (attr == null) {
            parseError(element, "<" + element.getName() + "> requires attribute '" + attrName
                    + "'.");
        }
        return (attr);
    }
}
