/**
 * Copyright(c) [2010] World Wide Web Consortium, (Massachusetts Institute
 * of Technology, European Research Consortium for Informatics and
 * Mathematics, Keio University). All Rights Reserved. This work is
 * distributed under the W3C(r) Software License [1] 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.
 * 
 * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
 * 
 */

import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.xqparser.XParser;
import org.xml.sax.*;
import org.apache.xml.serialize.*;
import java.util.Calendar;
import java.util.TreeMap;

// arg 0 - directory in which to find catalog file, no trailing forward slash
// arg 1 - catalog file in directory
// arg 2 - result file name

// Properties XQTSParse.username and XQTSParse.email will be used if they are set

public class XQTSParse {

    final static String XQTSR_URI = "http://www.w3.org/2005/02/query-test-XQTSResult";

    final static String XQTS_URI = "http://www.w3.org/2005/02/query-test-XQTSCatalog";

    final static String username = System.getProperty("XQTSParse.username",
            System.getProperty("user.name", null));

    final static String email = System.getProperty("XQTSParse.email", null);

    static TreeMap xqtsAttributes = new TreeMap();

    /**
     * Read catalog, parse queries, generate result document
     */

    public static void main(String[] args) {
        Document xqtsCatalog = null;
        Document results = null;

        // Parse the catalog

        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory
                    .newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            File catalog = new File(args[0], args[1]);
            // System.out.println("Reading catalog: " + catalog);
            xqtsCatalog = builder.parse(catalog.toString());
            results = builder.newDocument();
        } catch (FactoryConfigurationError e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (SAXException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        // Create a map of catalog root element attributes

        NamedNodeMap attrs = xqtsCatalog.getDocumentElement().getAttributes();
        for (int n = attrs.getLength() - 1; n >= 0; n--) {
            Node attr = attrs.item(n);
            xqtsAttributes.put(attr.getLocalName(), attr.getNodeValue());
        }

        Element root = buildResultHeader(xqtsCatalog, results);

        runTests(xqtsCatalog, root, args[0]);

        // Serialize the results document

        try {
            OutputFormat format = new OutputFormat("xml", "UTF-8", true);
            format.setLineSeparator(LineSeparator.Windows);
            format.setIndenting(true);
            format.setLineWidth(0);
            format.setIndent(3);
            format.setPreserveSpace(true);
            XMLSerializer serializer = new XMLSerializer(
                    new FileWriter(args[2]), format);
            serializer.asDOMSerializer();
            serializer.serialize(results);
        } catch (java.io.IOException ioe) {
            ioe.printStackTrace();
            System.exit(-1);

        }

    }

    /**
     * @param XQCatalog     catalog document
     * @param results       document to hold results
     * @return              root element of results document
     */

    public static Element buildResultHeader(Document XQCatalog, Document results) {
        Element root = results.createElement("test-suite-result");
        root.setAttribute("xmlns", XQTSR_URI);
        results.appendChild(root);
        root.appendChild(root.getOwnerDocument().createTextNode("\n"));

        Element implementation = results.createElement("implementation");
        implementation.setAttribute("name", "Parse Test");
        implementation.setAttribute("version", "1.0");
        implementation.setAttribute("anonymous-result-column", "false");
        Element organization = results.createElement("organization");
        organization.setAttribute("name", username);
        organization.setAttribute("anonymous", "false");
        implementation.appendChild(organization);
        Element submittor = results.createElement("submittor");
        submittor.setAttribute("name", username);
        submittor.setAttribute("email", email);
        implementation.appendChild(submittor);
        root.appendChild(implementation);
        root.appendChild(root.getOwnerDocument().createTextNode("\n"));

        Element syntax = results.createElement("syntax");
        syntax.appendChild(root.getOwnerDocument().createTextNode("XQuery"));
        root.appendChild(syntax);
        root.appendChild(root.getOwnerDocument().createTextNode("\n"));

        Element testRun = results.createElement("test-run");
        Calendar now = Calendar.getInstance();
        String month = Integer.toString(now.get(Calendar.MONTH) + 1);
        month = (month.length() == 1 ? "0" : "") + month;
        String date = Integer.toString(now.get(Calendar.DATE));
        date = (date.length() == 1 ? "0" : "") + date;

        String today = Integer.toString(now.get(Calendar.YEAR)) + "-" + month
                + "-" + date;
        testRun.setAttribute("dateRun", today);
        Element testSuite = results.createElement("test-suite");
        NodeList testSuites = XQCatalog.getElementsByTagNameNS(XQTS_URI,
                "test-suite");
        Element catalogTestSuite = (Element) testSuites.item(0);
        testSuite.setAttribute("version", catalogTestSuite
                .getAttribute("version"));
        testRun.appendChild(testSuite);
        root.appendChild(testRun);
        root.appendChild(root.getOwnerDocument().createTextNode("\n"));

        return root;

    }

    /**
     * Parse the query in each of the test cases, filling in the
     * result document.
     * 
     * @param XQCatalog     catalog document
     * @param root          root of result document
     * @param catalogDir    directory holding the catalog
     */

    public static void runTests(Document XQCatalog, Element root,
            String catalogDir) {

        java.util.TreeMap moduleMap = new TreeMap();

        // build map of module ids to module paths

        Element sources = (Element) XQCatalog.getElementsByTagNameNS(XQTS_URI,
                "sources").item(0);
        NodeList modules = sources.getElementsByTagNameNS(XQTS_URI, "module");
        // System.out.println("Modules found: " + modules.getLength());
        for (int i = 0; i < modules.getLength(); i++) {
            Element module = (Element) modules.item(i);
            moduleMap.put(module.getAttribute("ID"), catalogDir
                    + File.separator + module.getAttribute("FileName") + ".xq");
            moduleMap.put(module.getAttribute("ID"), xqueryFileName(catalogDir,
                    (String) xqtsAttributes.get("SourceOffsetPath"), module
                            .getAttribute("FileName")));

            moduleMap.put(module.getAttribute("ID"), xqueryModuleFileName(
                    catalogDir, module.getAttribute("FileName")));
        }

        // display test cases

        NodeList testCases = XQCatalog.getElementsByTagNameNS(XQTS_URI,
                "test-case");
        // System.out.println("Test cases found: " + testCases.getLength());
        for (int i = 0; i < testCases.getLength(); i++) {
            Element testCase = (Element) testCases.item(i);
            runTest(testCase, root, catalogDir, moduleMap);
        }
    }

    /**
     * Parse the query for a test case and add a result element to the
     * result document.
     * 
     * @param testCase      test case in the cqatalog
     * @param root          root of the results document
     * @param catalogDir    directory of the catalog
     * @param moduleMap     map containing XQTS root element attributes
     * 
     */

    public static void runTest(Element testCase, Element root,
            String catalogDir, TreeMap moduleMap) {

        String testCaseName = testCase.getAttribute("name");
        String filePathName = testCase.getAttribute("FilePath");
        String scenario = testCase.getAttribute("scenario");

        // setup the result element

        Element testcase = root.getOwnerDocument().createElement("test-case");
        testcase.setAttribute("name", testCaseName);
        root.appendChild(testcase);
        root.appendChild(root.getOwnerDocument().createTextNode("\n"));

        // test the main query

        Element query = (Element) testCase.getElementsByTagNameNS(XQTS_URI,
                "query").item(0);
        String queryName = query.getAttribute("name");

        boolean result = testQuery(xqueryFileName(catalogDir, filePathName,
                queryName), root, scenario.equals("parse-error"), testcase);

        // test any external variable queries

        NodeList extVarQueries = testCase.getElementsByTagNameNS(XQTS_URI,
                "input-query");

        // System.out.println("Test cases found: " + extVarQueries.getLength());

        for (int i = 0; i < extVarQueries.getLength(); i++) {
            Element extVarQuery = (Element) extVarQueries.item(i);
            queryName = extVarQuery.getAttribute("name");
            if (!testQuery(xqueryFileName(catalogDir, filePathName, queryName),
                    root, false, testcase)) {
                testcase.setAttribute("comment", testcase
                        .getAttribute("comment")
                        + " in variable query " + queryName);
                result = false;
            }
        }

        // test any modules

        NodeList modules = testCase.getElementsByTagNameNS(XQTS_URI, "module");

        // System.out.println("Modules found: " + modules.getLength());

        for (int i = 0; i < modules.getLength(); i++) {
            Element module = (Element) modules.item(i);
            String moduleName = module.getFirstChild().getNodeValue();
            if (!testQuery((String) moduleMap.get(moduleName), root, false,
                    testcase)) {
                testcase.setAttribute("comment", testcase
                        .getAttribute("comment")
                        + " in module " + moduleName);

                result = false;
            }
        }

        // report this result

        testcase.setAttribute("result", result ? "pass" : "fail");

    }

    /**
     * @param catalogDir        Directory of the catalog
     * @param filePathName      pathname to get to file
     * @param queryName         query name
     * @return                  Full path of query file
     */

    private static String xqueryFileName(String catalogDir,
            String filePathName, String queryName) {
        File f = new File(catalogDir
                + xqtsAttributes.get("XQueryQueryOffsetPath") + filePathName
                + File.separator + queryName
                + xqtsAttributes.get("XQueryFileExtension"));

        return f.toString();
    }

    /**
     * @param catalogDir        Directory of the catalog
     * @param moduleName        name of the module
     * @return                  Full path of the module file
     */

    private static String xqueryModuleFileName(String catalogDir,
            String moduleName) {
        File f = new File(catalogDir + xqtsAttributes.get("SourceOffsetPath")
                + moduleName + xqtsAttributes.get("XQueryFileExtension"));

        return f.toString();
    }

    /**
     * 
     * testQuery
     * 
     * return whether the query had its expected result
     *  
     */

    /**
     * @param queryLocation     filename of query
     * @param root              root element of result document
     * @param parseError        indicate whether a parse error is expected
     * @param testcase          element that will be added to result document
     * @return                  whether test was successful
     */

    public static boolean testQuery(String queryLocation, Element root,
            boolean parseError, Element testcase) {
        FileInputStream fis = null;

        try {
            File file = new File(queryLocation);
            fis = new FileInputStream(file);
            XParser parser = new XParser(fis);
            parser.START();
            System.out.flush();
        } catch (Exception e) {
            if (!parseError) {
                // System.out.println(e.getMessage());
                // System.out.println("      *** Query failed ***");
                testcase.setAttribute("comment", e.getMessage());
            }
            return (parseError);
        } catch (Error e) {
            if (!parseError) {
                // System.out.println(e.getMessage());
                // System.out.println("      *** Query failed ***");
                testcase.setAttribute("comment", e.getMessage());
            }
            return (parseError);
        } finally {
            try {
                fis.close();
            } catch (Exception e) {
            }
            ;
        }

        if (parseError) {
            testcase.setAttribute("comment", "Parse error expected.");
        }
        return (!parseError);

    }
}