/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
 *    (C) 2004, TOPP - www.openplans.org
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.validation.xml;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.geotools.validation.dto.ArgumentDTO;
import org.geotools.validation.dto.PlugInDTO;
import org.geotools.validation.dto.TestDTO;
import org.geotools.validation.dto.TestSuiteDTO;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


/**
 * Load validation configuration from XML.
 *
 * @author dzwiers, Refractions Research, Inc.
 * @author $Author: jive $ (last modification)
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/validation/src/main/java/org/geotools/validation/xml/XMLReader.java $
 * @version $Id: XMLReader.java 22754 2006-11-16 03:03:20Z jgarnett $
 */
public class XMLReader {
    /**
     * XMLReader constructor.
     * 
     * <p>
     * Should never be used.
     * </p>
     */
    private XMLReader() {
    }

    /**
     * readPlugIn purpose.
     * 
     * <p>
     * This method is intended to read an XML PlugIn (pluginSchema.xsd) into a
     * PlugInDTO object.
     * </p>
     *
     * @param inputSource A features which contains a copy of a valid PlugIn
     *        desciption.
     *
     * @return the resulting dto based on the input provided.
     *
     * @throws ValidationException DOCUMENT ME!
     */
    public static PlugInDTO readPlugIn(Reader inputSource)
        throws ValidationException {
        PlugInDTO dto = new PlugInDTO();

        try {
            Element elem = null;

            try {
                elem = ReaderUtils.loadConfig(inputSource);
            } catch (ParserConfigurationException pce) {
                throw new ValidationException("Cannot parse the inputSource: Cannot configure the parser.",
                    pce);
            } catch (SAXException se) {
                throw new ValidationException("Cannot parse the inputSource: Cannot configure the parser.",
                    se);
            }

            try {
                dto.setName(ReaderUtils.getElementText(
                        ReaderUtils.getChildElement(elem, "name", true), true));
            } catch (SAXException e) {
                throw new ValidationException("Error parsing name for this plugin",
                    e);
            }

            try {
                dto.setDescription(ReaderUtils.getElementText(
                        ReaderUtils.getChildElement(elem, "description", true),
                        true));
            } catch (SAXException e) {
                throw new ValidationException(
                    "Error parsing description for the " + dto.getName()
                    + " plugin", e);
            }

            try {
                dto.setClassName(ReaderUtils.getElementText(
                        ReaderUtils.getChildElement(elem, "class", true), true));
            } catch (SAXException e) {
                throw new ValidationException("Error parsing class for the "
                    + dto.getName() + " plugin", e);
            }

            NodeList nl = elem.getElementsByTagName("argument");

            if (nl != null) {
                Map m = new HashMap();
                dto.setArgs(m);

                for (int i = 0; i < nl.getLength(); i++) {
                    elem = (Element) nl.item(i);

                    ArgumentDTO adto = null;

                    try {
                        adto = loadArg(elem, dto);
                    } catch (ValidationException e) {
                        e.printStackTrace();

                        // error
                    }

                    m.put(adto.getName(), adto);
                }
            }
        } catch (IOException ioe) {
            throw new ValidationException("Cannot parse the inputSource: Cannot configure the parser.",
                ioe);
        }

        return dto;
    }

    /**
     * readTestSuiteDTO purpose.
     * 
     * <p>
     * This method is intended to read an XML Test (testSuiteSchema.xsd) into a
     * TestSuiteDTO object.
     * </p>
     *
     * @param inputSource A features which contains a copy of a valid TestSuite
     *        desciption.
     * @param plugIns A name of plugin names to valid plugin DTOs
     *
     * @return the resulting dto based on the input provided.
     *
     * @throws ValidationException DOCUMENT ME!
     */
    public static TestSuiteDTO readTestSuite(String name, Reader inputSource, Map plugIns)
        throws ValidationException {
        TestSuiteDTO dto = new TestSuiteDTO();
        try {
            Element elem = null;
            try {
                elem = ReaderUtils.loadConfig(inputSource);
            } catch (ParserConfigurationException e) {
                throw new ValidationException( "Problem parsing "+name+":"+e.getMessage(),
                    e);
            } catch (SAXException e) {
                throw new ValidationException( "XML problem with  "+name+":"+e.getMessage(),e);
            }

            try {
                dto.setName(ReaderUtils.getChildText(elem, "name", true));
            } catch (SAXException e) {
                throw new ValidationException("Error loading test suite name", e);
            }

            try {
                dto.setDescription(ReaderUtils.getChildText(elem,
                        "description", true));
            } catch (SAXException e) {
                throw new ValidationException("Error loading test suite description",
                    e);
            }

            Map l = new HashMap();
            dto.setTests(l);

            NodeList nl = elem.getElementsByTagName("test");

            if ((nl == null) || (nl.getLength() == 0)) {
                throw new ValidationException(
                    "The test suite loader has detected an error: no tests provided.");
            } else {
                for (int i = 0; i < nl.getLength(); i++) {
                    try {
                        TestDTO t = loadTestDTO((Element) nl.item(i), plugIns);
                        l.put(t.getName(), t);
                    } catch (ValidationException e) {
                        throw new ValidationException(
                            "An error occured loading a test in "
                            + dto.getName() + " test suite.", e);
                    }
                    catch (Throwable t) {
                        throw new ValidationException(
                            "Could not load test suite "
                            + dto.getName() + ":"+ t.getMessage(), t);
                    }                    
                }
            }
        } catch (IOException e) {
            throw new ValidationException("An error occured loading the "
                + dto.getName() + "test suite", e);
        }

        return dto;
    }

    /**
     * loadTestDTO purpose.
     * 
     * <p>
     * Helper method used by readTestDTO and readTestSuiteDTO
     * </p>
     *
     * @param elem The head element of a test
     * @param plugIns DOCUMENT ME!
     *
     * @return a TestDTO representing elem, null if elem is not corretly
     *         defined.
     *
     * @throws ValidationException DOCUMENT ME!
     */
    private static TestDTO loadTestDTO(Element elem, Map plugIns)
        throws ValidationException {
        TestDTO dto = new TestDTO();

        try {
            dto.setName(ReaderUtils.getChildText(elem, "name", true));
        } catch (SAXException e) {
            throw new ValidationException("Error reading the name for this test case.",
                e);
        }

        try {
            dto.setDescription(ReaderUtils.getChildText(elem, "description",
                    false));
        } catch (SAXException e) {
            throw new ValidationException(
                "Error reading the description for the " + dto.getName()
                + " test case.", e);
        }

        try {
            String pluginName = ReaderUtils.getChildText(elem, "plugin", true);
            dto.setPlugIn((PlugInDTO) plugIns.get(pluginName));

            if (dto.getPlugIn() == null) {
                throw new NullPointerException(
                    "Error - should have a plugin at "+elem);
            }
        } catch (SAXException e) {
            throw new ValidationException("Error reading the plugin for the "
                + dto.getName() + " test case.", e);
        }

        NodeList nl = elem.getElementsByTagName("argument");

        if (nl != null) {
            Map m = new HashMap();
            dto.setArgs(m);

            for (int i = 0; i < nl.getLength(); i++) {
                elem = (Element) nl.item(i);

                ArgumentDTO adto = null;

                try {
                    adto = loadArg(elem, dto.getPlugIn());
                } catch (ValidationException e) {
                    e.printStackTrace();

                    // error
                }

                if ((adto == null) || !adto.isFinal()) {
                    m.put(adto.getName(), adto);
                }
            }
        }

        return dto;
    }

    private static ArgumentDTO loadArg(Element elem, PlugInDTO dto)
        throws ValidationException {
        String key = "";
        boolean _fixed = false;

        try {
            _fixed = ReaderUtils.getBooleanAttribute(elem, "final", false);
            key = ReaderUtils.getChildText(elem, "name", true);
        } catch (SAXException e) {
            throw new ValidationException("Error reading argument for "
                + dto.getName() + " :name required");
        }

        NodeList nl2 = elem.getChildNodes();
        Element value = null;

        for (int j = 0; j < nl2.getLength(); j++) {
            if (nl2.item(j).getNodeType() == Node.ELEMENT_NODE) {
                elem = (Element) nl2.item(j);

                if (elem.getTagName().trim().equals("name")) {
                    value = elem;
                } else {
                    value = elem;
                }
            }
        }

        if (value == null) {
            throw new ValidationException("Invalid Argument \"" + dto.getName()
                + "\" for argument \"" + key + "\"");
        }

        ArgumentDTO adto = (ArgumentDTO) dto.getArgs().get(key);

        // elem whould have the value now
        Object val = ArgHelper.getArgumentInstance(value.getTagName().trim(),
                value);

        if (val == null) {
            throw new ValidationException(
                "Didn't find a real value for argument " + key);
        }

        if (adto == null) {
            adto = new ArgumentDTO();
        } else {
            adto = (ArgumentDTO) adto.clone();
        }

        adto.setName(key);
        adto.setValue(val);
        adto.setFinal(_fixed);

        return adto;
    }

    /**
     * loadPlugIns purpose.
     * 
     * <p>
     * Loads all the plugins in the directory
     * </p>
     *
     * @param plugInDir
     *
     *
     * @throws ValidationException DOCUMENT ME!
     */
    public static Map loadPlugIns(File plugInDir) throws ValidationException {
        Map r = null;

        try {
            plugInDir = ReaderUtils.initFile(plugInDir, true);

            File[] fileList = plugInDir.listFiles();
            r = new HashMap();

            for (int i = 0; i < fileList.length; i++) {
                if (fileList[i].canWrite() && fileList[i].isFile()) {
                    FileReader fr = new FileReader(fileList[i]);
                    PlugInDTO dto = XMLReader.readPlugIn(fr);
                    r.put(dto.getName(), dto);
                    fr.close();
                }
            }
        } catch (IOException e) {
            throw new ValidationException("An io error occured while loading the plugin's",
                e);
        }

        return r;
    }

    /**
     * Loads all the test suites in the validations directory
     * </p>
     *
     * @param validationDir
     * @param plugInDTOs Already loaded list of plug-ins to link.
     *
     *
     * @throws ValidationException DOCUMENT ME!
     */
    public static Map loadValidations(File validationDir, Map plugInDTOs)
        throws ValidationException {
        Map r = null;

        try {
            validationDir = ReaderUtils.initFile(validationDir, true);
        }
        catch( IOException dirException ){
            throw new ValidationException("Problem opening "+validationDir.getName(),
                    dirException);            
        }
        File[] fileList = validationDir.listFiles();
        r = new HashMap();

        for (int i = 0; i < fileList.length; i++) {
            try {
                if (fileList[i].canWrite() && fileList[i].isFile()) {
                    FileReader fr = new FileReader(fileList[i]);
                    try {
                        TestSuiteDTO dto = XMLReader.readTestSuite( fileList[i].getName(), fr, plugInDTOs);
                        r.put(dto.getName(), dto);                        
                    }
                    finally {                    
                        fr.close();
                    }
                }
            }
            catch (IOException open) {
                throw new ValidationException("Could not open "+fileList[i].getName(),
                    open);
            }
        }
        return r;
    }
}
