/*
 * 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.
 */
/*
 * $Id$
 */

package org.apache.qetest.trax;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.qetest.FileBasedTest;
import org.apache.qetest.Logger;
import org.apache.qetest.OutputNameManager;
import org.apache.qetest.QetestUtils;
import org.apache.qetest.xsl.XSLTestfileInfo;
import org.apache.qetest.xslwrapper.TransformWrapper;
import org.apache.qetest.xslwrapper.TransformWrapperFactory;
import org.apache.qetest.xslwrapper.TransformWrapperHelper;

/**
 * Minitest - developer check-in test for Xalan-J 2.x.  
 *
 * <p>Developers should always run either the minitest or smoketest 
 * target before checking any code into the xml-xalan CVS 
 * repository.  Running the minitest before checking in ensures 
 * that the Xalan CVS tree will always be in a compileable and 
 * at least basically functional state, thus ensuring a workable 
 * product for your fellow Xalan developers.  Ensuring your code 
 * passes the smoketest target will also help the nightly GUMP 
 * runs to pass the smoketest as well and avoid 'nag' emails.</p>
 *
 * <p>If you really need to make a checkin that will temporarily 
 * break or fail the minitest, then <b>please</b> be sure to send 
 * email to xalan-dev@xml.apache.org letting everyone know.</p>
 *
 * <p>For more information, please see the 
 * <a href="http://xml.apache.org/xalan-j/test/overview.html">
 * testing docs</a> and the nightly 
 * <a href="http://jakarta.apache.org/builds/gump/">
 * GUMP build page</a>.</p>
 *
 * @author shane_curcuru@us.ibm.com
 * @version $Id$
 */
public class Minitest extends FileBasedTest
{

    /**
     * Provides nextName(), currentName() functionality for tests 
     * that may produce any number of output files.
     */
    protected OutputNameManager outNames;

    /**
     * Basic output name root used throughout tests.
     */
    protected String baseOutName;

    /** The Minitest.xsl/.xml file; note goldName is version-specific.  */
    protected XSLTestfileInfo testFileInfo = new XSLTestfileInfo();

    /** The MinitestParam.xsl/.xml file.  */
    protected XSLTestfileInfo paramFileInfo = new XSLTestfileInfo();

    /** The MinitestPerf.xsl/.xml file.  */
    protected XSLTestfileInfo perfFileInfo = new XSLTestfileInfo();


    /** Constants matching parameter names/values in paramFileInfo.  */
    public static final String PARAM1S = "param1s";
    public static final String PARAM2S = "param2s";
    public static final String PARAM1N = "param1n";
    public static final String PARAM2N = "param2n";

    /** Just initialize test name, comment, numTestCases. */
    public Minitest()
    {
        numTestCases = 5;  // REPLACE_num
        testName = "Minitest";
        testComment = "Minitest - developer check-in test for Xalan-J 2.x.";
    }


    /**
     * Initialize this test - Set names of xml/xsl test files, etc.
     *
     * Also cleans up any Pass-Minitest.xml file that is checked 
     * for in test.properties' minitest.passfile and generated by 
     * Reporter.writeResultsStatus().
     *
     * @param p Properties to initialize from (if needed)
     * @return false if we should abort the test; true otherwise
     * @see Reporter.writeResultsStatus(boolean)
     */
    public boolean doTestFileInit(Properties p)
    {
        // Used for all tests; just dump files in outputDir
        File outSubDir = new File(outputDir);
        if (!outSubDir.mkdirs())
        {
            if (!outSubDir.exists())
                reporter.logErrorMsg("Problem creating output dir: " + outSubDir);
        }
        // Initialize an output name manager to that dir with .out extension
        baseOutName = outputDir + File.separator + testName;
        outNames = new OutputNameManager(baseOutName, ".out");

        String testBasePath = inputDir 
                              + File.separator;
        String goldBasePath = goldDir 
                              + File.separator;

        testFileInfo.inputName = testBasePath + "Minitest.xsl";
        testFileInfo.xmlName = testBasePath + "Minitest.xml";
        // Use separate output files for different versions, since 
        //  some indenting rules are implemented differently 1.x/2.x
        testFileInfo.goldName = goldBasePath + "Minitest-xalanj2.out";
        testFileInfo.description = "General minitest, covers many xsl: elems";

        paramFileInfo.inputName = testBasePath + "MinitestParam.xsl";
        paramFileInfo.xmlName = testBasePath + "MinitestParam.xml";
        paramFileInfo.goldName = goldBasePath + "MinitestParam.out";
        paramFileInfo.description = "Simple string and int params";

        perfFileInfo.inputName = testBasePath + "MinitestPerf.xsl";
        perfFileInfo.xmlName = testBasePath + "MinitestPerf.xml";
        perfFileInfo.goldName = goldBasePath + "MinitestPerf.out";
        perfFileInfo.description = "Simple performance test";

        try
        {
            // Clean up any Pass files for the minitest that exist
            //@see Reporter.writeResultsStatus(boolean)
            String logFileBase = (new File(testProps.getProperty(Logger.OPT_LOGFILE, "ResultsSummary.xml"))).getAbsolutePath();
            logFileBase = (new File(logFileBase)).getParent();

            File f = new File(logFileBase, Logger.PASS + "-" + testName + ".xml");
            reporter.logTraceMsg("Deleting previous file: " + f);
            f.delete();
        } 
        catch (Exception e)
        {
            reporter.logThrowable(reporter.ERRORMSG, e, "Deleting Pass-Minitest file threw");
            reporter.logErrorMsg("Deleting Pass-Minitest file threw: " + e.toString());
        }

        return true;
    }


    /**
     * Basic systemId transforms and params plus API coverage.
     *
     * @return false if we should abort the test; true otherwise
     */
    public boolean testCase1()
    {
        reporter.testCaseInit("Basic systemId transforms and params plus API coverage");

        TransformerFactory factory = null;
        Templates templates = null;
        Transformer transformer = null;
        try
        {
            factory = TransformerFactory.newInstance();
            templates = factory.newTemplates(new StreamSource(QetestUtils.filenameToURL(testFileInfo.inputName)));
            reporter.check((templates != null), true, "factory.newTemplates(StreamSource) is non-null");
        }
        catch (Throwable t)
        {
            reporter.logThrowable(reporter.ERRORMSG, t, 
                                  "Problem creating Templates; cannot continue testcase");
            reporter.checkErr("Problem creating Templates; cannot continue testcase");
            return true;
        }
        try
        {
            // Validate a systemId transform
            reporter.logTraceMsg("Basic stream transform(1)(" + QetestUtils.filenameToURL(testFileInfo.xmlName) + ", "
                                 + QetestUtils.filenameToURL(testFileInfo.inputName)  + ", "
                                 + outNames.nextName());
            transformer = templates.newTransformer();
            FileOutputStream fos = new FileOutputStream(outNames.currentName());
            transformer.transform(new StreamSource(QetestUtils.filenameToURL(testFileInfo.xmlName)), 
                                  new StreamResult(fos));
            fos.close();
            int fileCheckStatus = fileChecker.check(reporter, 
                              new File(outNames.currentName()), 
                              new File(testFileInfo.goldName), 
                              "Basic stream transform(1) into: " + outNames.currentName());
            if (fileCheckStatus != reporter.PASS_RESULT)
            {
                reporter.logWarningMsg("Basic stream transform(1) into: " + outNames.currentName()
                                       + fileChecker.getExtendedInfo());
            }

            // Validate transformer reuse
            reporter.logTraceMsg("Basic stream transform(2)(" + QetestUtils.filenameToURL(testFileInfo.xmlName) + ", "
                                 + QetestUtils.filenameToURL(testFileInfo.inputName)  + ", "
                                 + outNames.nextName());
            fos = new FileOutputStream(outNames.currentName());
            transformer.transform(new StreamSource(QetestUtils.filenameToURL(testFileInfo.xmlName)), 
                                  new StreamResult(fos));
            fos.close();
            fileCheckStatus = fileChecker.check(reporter, 
                              new File(outNames.currentName()), 
                              new File(testFileInfo.goldName), 
                              "Basic stream transform(2) into: " + outNames.currentName());
            if (fileCheckStatus != reporter.PASS_RESULT)
            {
                reporter.logWarningMsg("Basic stream transform(2) into: " + outNames.currentName()
                                       + fileChecker.getExtendedInfo());
            }
        } 
        catch (Throwable t)
        {
            reporter.checkFail("Problem with simple stream transform");
            reporter.logThrowable(reporter.ERRORMSG, t, "Problem with simple stream transform");
        }

        try
        {
            // Validate selected API's - primarily Parameters
            Templates paramTemplates = factory.newTemplates(new StreamSource(QetestUtils.filenameToURL(paramFileInfo.inputName)));
            Transformer paramTransformer = paramTemplates.newTransformer();
            String paramStr = "paramVal";
            paramTransformer.setParameter(PARAM1S, paramStr);
            reporter.logTraceMsg("Just set " + PARAM1S + " to " + paramStr);
            Object tmp = paramTransformer.getParameter(PARAM1S);    // SPR SCUU4QWTVZ - returns an XObject - fixed
            if (tmp == null)
            {
                reporter.checkFail(PARAM1S + " is still set to null!");
            }
            else
            {   // Validate SPR SCUU4QWTVZ - should return the same type you set
                if (tmp instanceof String)
                {
                    reporter.checkObject(tmp, paramStr, PARAM1S + " is now set to ?" + tmp + "?");
                }
                else
                {
                    reporter.checkFail(PARAM1S + " is now ?" + tmp + "?, isa " + tmp.getClass().getName());
                }
            }

            // Verify simple re-set/get of a single parameter - new Integer
            Integer paramInteger = new Integer(1234);
            paramTransformer.setParameter(PARAM1S, paramInteger);   // SPR SCUU4R3JGY - can't re-set
            reporter.logTraceMsg("Just reset " + PARAM1S + " to new Integer(99)");
            tmp = null;
            tmp = paramTransformer.getParameter(PARAM1S);
            if (tmp == null)
            {
                reporter.checkFail(PARAM1S + " is still set to null!");
            }
            else
            {   // Validate SPR SCUU4QWTVZ - should return the same type you set
                if (tmp instanceof Integer)
                {
                    reporter.checkObject(tmp, paramInteger, PARAM1S + " is now set to ?" + tmp + "?");
                }
                else
                {
                    reporter.checkFail(PARAM1S + " is now ?" + tmp + "?, isa " + tmp.getClass().getName());
                }
            }
            // Validate a transform with two params set
            paramTransformer.setParameter(PARAM1N, "new-param1n-value");
            reporter.logTraceMsg("Just reset " + PARAM1N + " to new-param1n-value");

            reporter.logTraceMsg("Stream-param transform(" + QetestUtils.filenameToURL(paramFileInfo.xmlName) + ", "
                                 + QetestUtils.filenameToURL(paramFileInfo.inputName)  + ", "
                                 + outNames.nextName());
            FileOutputStream fos = new FileOutputStream(outNames.currentName());
            paramTransformer.transform(new StreamSource(QetestUtils.filenameToURL(paramFileInfo.xmlName)), 
                                  new StreamResult(fos));
            fos.close();
            int fileCheckStatus = fileChecker.check(reporter, 
                              new File(outNames.currentName()), 
                              new File(paramFileInfo.goldName), 
                              "Stream transform with params into: " + outNames.currentName());
            if (fileCheckStatus != reporter.PASS_RESULT)
            {
                reporter.logWarningMsg("Stream transform with params into: " + outNames.currentName()
                                       + fileChecker.getExtendedInfo());
            }
            // Validate params are still set after transform
            tmp = paramTransformer.getParameter(PARAM1S);
            reporter.checkObject(tmp, paramInteger, PARAM1S + " is now set to ?" + tmp + "?");
            tmp = paramTransformer.getParameter(PARAM1N);
            reporter.checkObject(tmp, "new-param1n-value", PARAM1N + " is now set to ?" + tmp + "?");
        } 
        catch (Throwable t)
        {
            reporter.checkFail("Problem with parameters");
            reporter.logThrowable(reporter.ERRORMSG, t, "Problem with parameters");
        }
        reporter.testCaseClose();
        return true;
    }


    /**
     * Basic trax.dom transformWrapper.  
     * 
     * @return false if we should abort the test; true otherwise
     */
    public boolean testCase2()
    {
        final String FLAVOR = "trax.dom";
        final String DESC = "Basic " + FLAVOR + " transformWrapper";
        reporter.testCaseInit(DESC);
        try
        {
            testFileInfo.outputName = outNames.nextName();
            transformUsingFlavor(testFileInfo, FLAVOR);
            fileChecker.check(reporter, 
                              new File(testFileInfo.outputName), 
                              new File(testFileInfo.goldName), 
                              DESC +" into: " + testFileInfo.outputName);
        
        } 
        catch (Throwable t)
        {
            reporter.logThrowable(reporter.ERRORMSG, t, DESC + " threw: ");
            reporter.checkErr(DESC + " threw: " + t.toString());
        }
        return true;
    }


    /**
     * Basic trax.sax transformWrapper.  
     * 
     * @return false if we should abort the test; true otherwise
     */
    public boolean testCase3()
    {
        final String FLAVOR = "trax.sax";
        final String DESC = "Basic " + FLAVOR + " transformWrapper";
        reporter.testCaseInit(DESC);
        try
        {
            testFileInfo.outputName = outNames.nextName();
            transformUsingFlavor(testFileInfo, FLAVOR);
            fileChecker.check(reporter, 
                              new File(testFileInfo.outputName), 
                              new File(testFileInfo.goldName), 
                              DESC +" into: " + testFileInfo.outputName);
        } 
        catch (Throwable t)
        {
            reporter.logThrowable(reporter.ERRORMSG, t, DESC + " threw: ");
            reporter.checkErr(DESC + " threw: " + t.toString());
        }
        reporter.testCaseClose();
        return true;
    }


    /**
     * Basic trax.stream transformWrapper.  
     * 
     * @return false if we should abort the test; true otherwise
     */
    public boolean testCase4()
    {
        final String FLAVOR = "trax.stream";
        final String DESC = "Basic " + FLAVOR + " transformWrapper";
        reporter.testCaseInit(DESC);
        try
        {
            testFileInfo.outputName = outNames.nextName();
            transformUsingFlavor(testFileInfo, FLAVOR);
            fileChecker.check(reporter, 
                              new File(testFileInfo.outputName), 
                              new File(testFileInfo.goldName), 
                              DESC +" into: " + testFileInfo.outputName);
        } 
        catch (Throwable t)
        {
            reporter.logThrowable(reporter.ERRORMSG, t, DESC + " threw: ");
            reporter.checkErr(DESC + " threw: " + t.toString());
        }
        reporter.testCaseClose();
        return true;
    }


    /**
     * Basic performance measurements of sample files.  
     * @return false if we should abort the test; true otherwise
     */
    public boolean testCase5()
    {
        String flavor = null;
        final String DESC = "Simple performance measurement ";
        reporter.testCaseInit(DESC);
        // Reset the counting for outputNames for this testcase
        outNames = new OutputNameManager(baseOutName + "Perf", ".out");
        try
        {
            long[] times = null;
            Vector streamTimes = new Vector();
            Vector domTimes = new Vector();
            TransformWrapper transformWrapper = null;

            flavor = "trax.stream";
            transformWrapper = TransformWrapperFactory.newWrapper(flavor);
            transformWrapper.newProcessor(testProps);
            reporter.logHashtable(Logger.TRACEMSG, transformWrapper.getProcessorInfo(), "wrapper.getProcessorInfo() for next transforms");

            // Repeat a few times with streams
            for (int i = 1; i <= 5; i++)
            {
                perfFileInfo.outputName = outNames.nextName();
                reporter.logInfoMsg("perf-stream transform into " + perfFileInfo.outputName);
                times = transformWrapper.transform(perfFileInfo.xmlName, perfFileInfo.inputName, perfFileInfo.outputName);
                logPerfElem(times, perfFileInfo, flavor);
                streamTimes.addElement(new Long(times[TransformWrapper.IDX_OVERALL]));
            }
            // Only bother checking the *last* iteration of perfs
            fileChecker.check(reporter, 
                              new File(perfFileInfo.outputName), 
                              new File(perfFileInfo.goldName), 
                              DESC + flavor + " into: " + perfFileInfo.outputName);
            
            flavor = "trax.dom";
            transformWrapper = TransformWrapperFactory.newWrapper(flavor);
            transformWrapper.newProcessor(testProps);
            reporter.logHashtable(Logger.TRACEMSG, transformWrapper.getProcessorInfo(), "wrapper.getProcessorInfo() for next transforms");

            // Repeat a few times with DOMs
            for (int i = 1; i <= 5; i++)
            {
                perfFileInfo.outputName = outNames.nextName();
                reporter.logInfoMsg("perf-dom transform into " + perfFileInfo.outputName);
                times = transformWrapper.transform(perfFileInfo.xmlName, perfFileInfo.inputName, perfFileInfo.outputName);
                logPerfElem(times, perfFileInfo, flavor);
                domTimes.addElement(new Long(times[TransformWrapper.IDX_OVERALL]));
            }
            // Only bother checking the *last* iteration of perfs
            fileChecker.check(reporter, 
                              new File(perfFileInfo.outputName), 
                              new File(perfFileInfo.goldName), 
                              DESC + flavor + " into: " + perfFileInfo.outputName);

            // Log a big message at the very end to make it easier to see
            StringBuffer buf = new StringBuffer("Minitest.testCase5 PERFORMANCE NUMBERS\n");
            buf.append("        STREAM OVERALL TIMES: ");
            for (Enumeration elements = streamTimes.elements();
                    elements.hasMoreElements(); /* no increment portion */ )
            {
                buf.append(elements.nextElement());
                buf.append(", ");
            }
            buf.append("\n");
            buf.append("           DOM OVERALL TIMES: ");
            for (Enumeration elements = domTimes.elements();
                    elements.hasMoreElements(); /* no increment portion */ )
            {
                buf.append(elements.nextElement());
                buf.append(", ");
            }
            buf.append("\n");
            reporter.logArbitrary(Logger.CRITICALMSG, buf.toString());
        } 
        catch (Throwable t)
        {
            reporter.logThrowable(reporter.ERRORMSG, t, DESC + flavor + " threw: ");
            reporter.checkErr(DESC + flavor + " threw: " + t.toString());
        }
        reporter.testCaseClose();
        return true;
    }


    /**
     * Worker method to use a TransformWrapper to transform a file.
     * 
     * @param fileInfo inputName, xmlName of file to test
     * @param flavor of TransformWrapper to use
     * @return log number of overall millisec for transform.
     */
    public void transformUsingFlavor(XSLTestfileInfo fileInfo, String flavor)
            throws Exception
    {
        TransformWrapper transformWrapper = TransformWrapperFactory.newWrapper(flavor);
        transformWrapper.newProcessor(testProps);
        reporter.logHashtable(Logger.TRACEMSG, transformWrapper.getProcessorInfo(), "wrapper.getProcessorInfo() for next transform");
        if (null == fileInfo.inputName)
        {
            // presume it's an embedded test
            reporter.logInfoMsg("transformEmbedded(" + fileInfo.xmlName + ", " + fileInfo.outputName + ")");
            long[] times = transformWrapper.transformEmbedded(fileInfo.xmlName, fileInfo.outputName);
            logPerfElem(times, fileInfo, flavor);
        }
        else
        {
            // presume it's a normal stylesheet test
            reporter.logInfoMsg("transform(" + fileInfo.xmlName + ", " + fileInfo.inputName + ", " + fileInfo.outputName + ")");
            long[] times = transformWrapper.transform(fileInfo.xmlName, fileInfo.inputName, fileInfo.outputName);
            logPerfElem(times, fileInfo, flavor);
        }
        
    }


    /**
     * Worker method to output a &lt;perf&gt; element.  
     * @return false if we should abort the test; true otherwise
     */
    public void logPerfElem(long[] times, XSLTestfileInfo fileInfo, String flavor)
    {
        Hashtable attrs = new Hashtable();
        // Add general information about this perf elem
        attrs.put("UniqRunid", testProps.getProperty("runId", "runId;none"));
        attrs.put("processor", flavor);
        // idref is the individual filename
        attrs.put("idref", (new File(fileInfo.inputName)).getName());
        // inputName is the actual name we gave to the processor
        attrs.put("inputName", fileInfo.inputName);

        // Add all available specific timing data as well
        for (int i = 0; i < times.length; i++)
        {
            // Only log items that have actual timing data
            if (TransformWrapper.TIME_UNUSED != times[i])
            {
                attrs.put(TransformWrapperHelper.getTimeArrayDesc(i), 
                          new Long(times[i]));
            }
        }

        // Log the element out; note formatting matches
        reporter.logElement(Logger.STATUSMSG, "perf", attrs, fileInfo.description);
    }


    /**
     * Convenience method to print out usage information - update if needed.  
     * @return String denoting usage of this test class
     */
    public String usage()
    {
        return ("Common [optional] options supported by Minitest:\n"
                + "(Note: assumes inputDir=.\\tests\\api)\n"
                + super.usage());   // Grab our parent classes usage as well
    }


    /**
     * Main method to run test from the command line - can be left alone.  
     * @param args command line argument array
     */
    public static void main(String[] args)
    {
        Minitest app = new Minitest();
        app.doMain(args);
    }
}
