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

// Base header file.  Must be first.
#include <xalanc/Include/PlatformDefinitions.hpp>



#include <iostream>
#include <strstream>

#include <cstdio>
#include <cstring>



using std::cerr;
using std::cout;
using std::endl;



#include "xercesc/util/PlatformUtils.hpp"



#include "xercesc/parsers/XercesDOMParser.hpp"



#include "xalanc/PlatformSupport/XalanMemoryManagerDefault.hpp"



#include "xalanc/XercesParserLiaison/XercesParserLiaison.hpp"
#include "xalanc/XercesParserLiaison/XercesDOMSupport.hpp"



#include "xalanc/XalanTransformer/XalanTransformer.hpp"
#include "xalanc/XalanTransformer/XercesDOMWrapperParsedSource.hpp"



// HARNESS HEADERS...
#include "xalanc/Harness/XalanFileUtility.hpp"
#include "xalanc/Harness/XalanDiagnosticMemoryManager.hpp"
#include "xalanc/Harness/XalanXMLFileReporter.hpp"



//#define XALAN_VQ_SPECIAL_TRACE
#if defined(XALAN_VQ_SPECIAL_TRACE)
#include "C:/Program Files/Rational/Quantify/pure.h"
#endif



// Just hoist everything...
using namespace xalanc;



// This is here for memory leak testing. 
#if !defined(NDEBUG) && defined(_MSC_VER)
#include <crtdbg.h>
#endif



using xercesc::MemoryManager;



void
setHelp(XalanFileUtility&   h)
{
    h.args.getHelpStream() << endl
         << "conf dir [-sub -out -gold -source (XST | XPL | DOM)]"
         << endl
         << endl
         << "dir        (base directory for testcases)"
         << endl
         << "-sub dir   (specific directory)"
         << endl
         << "-out dir   (base directory for output)"
         << endl
         << "-gold dir  (base directory for gold files)"
         << endl
         << "-source type   (parsed source; XalanSourceTree(d), XercesParserLiasion, XercesDOM)"
         << endl;
}



static const char* const    excludeStylesheets[] =
{
    // Excluded because it's broken.  See Jira issue XALANJ-2412.
    "attribset29.xsl",

    // Excluded because Xalan-C++ deviates from the standard WRT namespace nodes.
    "idkey31.xsl",

    // $$$TODO: These are temporarily disabled, waiting for Xalan-J to implement erratum #23.
    "numbering21.xsl",
    "numbering29.xsl",
    "numbering49.xsl",
    "numbering61.xsl",
    "numbering68.xsl",
    "numbering69.xsl",
    "numbering83.xsl",

    // These numberformat tests fail because they test edge cases, and the ICU handles these
    // differently than Java does.
    // $$$TODO: Perhaps ICU will address these issues at some point.
    "numberformat11.xsl",
    "numberformat17.xsl",
    "numberformat25.xsl",
    "numberformat30.xsl",
    "numberformat34.xsl",
    "numberformat35.xsl",

#if defined(XALAN_WINDOWS)
    // Excluded because it outputs HTML, which cannot be parsed.  Text comparison fails because
    // linefeeds are Unix-style, and those fail on Windows.
    "output22.xsl",
#endif

    // Excluded because it outputs ISO-2022-JP, and not all platforms support this encoding.
    "output23.xsl",

    // These sort tests are disabled because the ICU implements a more recent version of the
    // the Unicode collation algorithm, so we won't match the Java results.
    "sort08.xsl",
    "sort27.xsl",

    // These string tests exceed the resolution of printf, so until we have a better
    // implementation, these will fail.
    "string132.xsl",
    "string134.xsl",
    "string135.xsl",

    // These variable tests are incorrect, because they violate the XSLT recommendation,
    // which disallows shadowing a variable name.
    "variable20.xsl",
    "variable56.xsl",
    0
};

static const char* const    excludeStylesheetsWithoutICU[] =
{
    // These numberformat tests fail because we don't implement format-number without
    // ICU integration.
    "numberformat01.xsl",
    "numberformat02.xsl",
    "numberformat03.xsl",
    "numberformat04.xsl",
    "numberformat05.xsl",
    "numberformat06.xsl",
    "numberformat07.xsl",
    "numberformat08.xsl",
    "numberformat09.xsl",
    "numberformat12.xsl",
    "numberformat13.xsl",
    "numberformat16.xsl",
    "numberformat18.xsl",
    "numberformat19.xsl",
    "numberformat20.xsl",
    "numberformat21.xsl",
    "numberformat22.xsl",
    "numberformat23.xsl",
    "numberformat24.xsl",
    "numberformat26.xsl",
    "numberformat27.xsl",
    "numberformat28.xsl",
    "numberformat29.xsl",
    "numberformat31.xsl",
    "numberformat32.xsl",
    "numberformat36.xsl",
    "numberformat45.xsl",
    "numberformat46.xsl",

    // Excluded because it outputs SHIFT_JIS, and not all platforms support this encoding, so
    // the gold file cannot be parsed.
    "output20.xsl",

    // Excluded because it outputs BIG5, and not all platforms support this encoding.
    "output21.xsl",

    // Excluded because it outputs EBCDIC-CP-IT, and not all platforms support this encoding.
    "output22.xsl",

    // Excluded because it outputs HTML, which cannot be parsed.  Text comparison fails because
    // it try to use SHIFT_JIS for the output encoding, which not all platforms support.
    "output73.xsl",

    // These sort tests are disabled because, without the ICU, we don't implement proper
    // sorting, including support for the case-order attribute of xsl:sort.
    // $$$TODO: Basic support for ASCII-only case order is being worked on.
    "sort02.xsl",
    "sort10.xsl",
    "sort14.xsl",
    "sort15.xsl",
    "sort16.xsl",
    "sort34.xsl",

    // These string tests using format-number(), which is not implemented without ICU
    // integration.
    "string108.xsl",
    "string109.xsl",
    "string110.xsl",
    "string132.xsl",
    "string134.xsl",
    "string135.xsl",
    0
};



inline bool
checkForExclusion(
            const XalanDOMString&   currentFile,
            bool                    isICUIntegrated,
            MemoryManager&          theMemoryManager)
{
    using std::strcmp;

    CharVectorType  theFileName(theMemoryManager);

    currentFile.transcode(theFileName);

    for (size_t i = 0; excludeStylesheets[i] != 0; ++i)
    {
        if (!strcmp(c_str(theFileName), excludeStylesheets[i]))
        {
            return true;
        }
    }

    if (!isICUIntegrated)
    {
        for (size_t i = 0; excludeStylesheetsWithoutICU[i] != 0; ++i)
        {
            if (!strcmp(c_str(theFileName), excludeStylesheetsWithoutICU[i]))
            {
                return true;
            }
        }
    }

    return false;
}



int
parseWithTransformer(
            int                             sourceType,
            XalanTransformer&               xalan,
            const XSLTInputSource&          xmlInput,
            const XalanCompiledStylesheet*  styleSheet,
            const XSLTResultTarget&         output, 
            XalanXMLFileReporter&           logFile,
            XalanFileUtility&               h)
{
    const XalanParsedSource* parsedSource = 0;

    MemoryManager&  mgr = h.getMemoryManager();

    int theResult = 0;

    // Parse the XML source accordingly.
    //
    if (sourceType != 0)
    {
        theResult = xalan.parseSource(xmlInput, parsedSource, true);
        h.data.xmlFormat = XalanDOMString("XercesParserLiasion", mgr);
    }
    else
    {
        theResult = xalan.parseSource(xmlInput, parsedSource, false);
        h.data.xmlFormat = XalanDOMString("XalanSourceTree", mgr);
    }

    // If the source was parsed correctly then perform the transform else report the failure.
    //
    if (parsedSource == 0)
    {
        // Report the failure and be sure to increment fail count.
        //
        cout << "ParseWTransformer - Failed to parse source document for " << h.data.testOrFile << endl;
        ++h.data.fail;
        XalanDOMString tmp("Failed to parse source document.  ", mgr);

        tmp.append(xalan.getLastError());

        logFile.logErrorResult(h.data.testOrFile,  tmp );
    }
    else 
    {
        theResult = xalan.transform(*parsedSource, styleSheet, output);
        xalan.destroyParsedSource(parsedSource);
    }

    return theResult;
}



int
parseWithXerces(
            XalanTransformer&               xalan,
            const XSLTInputSource&          xmlInput, 
            const XalanCompiledStylesheet*  styleSheet,
            const XSLTResultTarget&         output,
            XalanXMLFileReporter&           logFile,
            XalanFileUtility&               h)
{
    using xercesc::XercesDOMParser;
    using xercesc::DOMDocument;

    MemoryManager&  mgr = h.getMemoryManager();

    h.data.xmlFormat = XalanDOMString("Xerces_DOM", mgr);

    XercesDOMParser  theParser(0, &mgr);

#if XERCES_VERSION_MAJOR < 3
    theParser.setDoValidation(true);
#else
    theParser.setValidationScheme(XercesDOMParser::Val_Auto);
#endif

    theParser.setDoNamespaces(true);

    theParser.parse(xmlInput);

    DOMDocument* const theDOM = theParser.getDocument();

    theDOM->normalize();

    XercesParserLiaison     theParserLiaison(mgr);
    XercesDOMSupport        theDOMSupport(theParserLiaison);

    int theResult = 0;

    try
    {
        const XercesDOMWrapperParsedSource  parsedSource(
                    theDOM, 
                    theParserLiaison, 
                    theDOMSupport, 
                    XalanDOMString(xmlInput.getSystemId(), mgr),
                    mgr);

        theResult = xalan.transform(parsedSource, styleSheet, output);
    }
    catch(...)
    {
        // Report the failure and be sure to increment fail count.
        //
        cout << "parseWXerces - Failed to parse source document for " << h.data.testOrFile << endl;
        ++h.data.fail;
        XalanDOMString resultString("Failed to parse source document.  ", mgr);
        resultString.append( xalan.getLastError());
        logFile.logErrorResult(h.data.testOrFile,  resultString);
    }

    return theResult;
}



int
runTests(
            int             argc,
            char*           argv[],
            MemoryManager&  theMemoryManager)
{
    int theResult = 0;

    try
    {
        XalanFileUtility    h(theMemoryManager);

        // Set the program help string,  then get the command line parameters.
        //
        setHelp(h);

        if (h.getParams(argc, argv, "CONF-RESULTS") == true)
        {
            XalanTransformer    xalan(theMemoryManager);

            const bool          isICUAvailable = xalan.getICUAvailable();

            const XalanDOMString  resultFilePrefix("conf", theMemoryManager);       // This & UniqRunid used for log file name.
            XalanDOMString  UniqRunid(theMemoryManager); 
            h.generateUniqRunid(UniqRunid);
            XalanDOMString  resultsFile(theMemoryManager);
            resultsFile += h.args.output;
            resultsFile += resultFilePrefix;
            resultsFile += UniqRunid;
            resultsFile += XalanFileUtility::s_xmlSuffix;

            // Open results log, and do some initialization of result data.
            //
            XalanXMLFileReporter    logFile(theMemoryManager, resultsFile);
            logFile.logTestFileInit("Conformance Testing:");
            h.data.xmlFormat = XalanDOMString("NotSet", theMemoryManager);

            // Get the list of Directories that are below conf and iterate through them
            //

            // Flag indicates directory found. Used in conjunction with -sub cmd-line arg.
            bool    foundDir = false;

            typedef XalanFileUtility::FileNameVectorType        FileNameVectorType;

            FileNameVectorType  dirs(theMemoryManager);
            h.getDirectoryNames(h.args.base, dirs);

            int theResult = 0;

            for(FileNameVectorType::size_type j = 0;
                    j < dirs.size() && theResult == 0;
                        ++j)
            {
                // Skip all but the specified directory if the -sub cmd-line option was used.
                //
                const XalanDOMString&   currentDir = dirs[j];

                if ((h.args.sub.empty() == true ||
                     equals(currentDir, h.args.sub) == true) &&
                    currentDir[0] != XalanUnicode::charFullStop)
                {
                    // Check that output directory is there.
                    //
                    XalanDOMString  theOutputDir(theMemoryManager);
                    theOutputDir = h.args.output;
                    theOutputDir += currentDir;

                    h.checkAndCreateDir(theOutputDir);

                    // Indicate that directory was processed and get test files from the directory
                    //
                    foundDir = true;
                    FileNameVectorType files(theMemoryManager);
                    h.getTestFileNames(h.args.base, currentDir, true, files);

                    // Log directory in results log and process it's files.
                    //
                    logFile.logTestCaseInit(currentDir);

                    for(FileNameVectorType::size_type i = 0; i < files.size(); i++)
                    {
                        XalanXMLFileReporter::Hashtable attrs(theMemoryManager);

                        const XalanDOMString&   currentFile = files[i];
                        if (checkForExclusion(currentFile, isICUAvailable, theMemoryManager))
                            continue;

                        h.data.testOrFile = currentFile;
                        XalanDOMString  theXSLFile( h.args.base, theMemoryManager);
                        theXSLFile += currentDir;
                        theXSLFile += XalanFileUtility::s_pathSep;
                        theXSLFile += currentFile;

                        // Check and see if the .xml file exists. If not skip this .xsl file and continue.
                        bool fileStatus = true;
                        XalanDOMString  theXMLFile(theMemoryManager); 
                        h.generateFileName(theXSLFile, "xml", theXMLFile, &fileStatus);
                        if (!fileStatus)
                            continue;

                        h.data.xmlFileURL = theXMLFile;
                        h.data.xslFileURL = theXSLFile;


                        XalanDOMString  theGoldFile(h.args.gold, theMemoryManager);
                        theGoldFile += currentDir;
                        theGoldFile += XalanFileUtility::s_pathSep;
                        theGoldFile += currentFile;
                         
                        h.generateFileName(theGoldFile, "out", theGoldFile);

                        XalanDOMString  outbase (h.args.output, theMemoryManager);
                        outbase += currentDir;
                        outbase += XalanFileUtility::s_pathSep;
                        outbase += currentFile; 
                        XalanDOMString  theOutputFile(theMemoryManager);
                        h.generateFileName(outbase, "out", theOutputFile);

                        const XSLTInputSource   xslInputSource(theXSLFile, theMemoryManager);
                        const XSLTInputSource   xmlInputSource(theXMLFile, theMemoryManager);
                        const XSLTResultTarget  resultFile(theOutputFile, theMemoryManager);

                        using std::ostrstream;

                        // Setting a warning stream will swallow output in non-verbose mode.
                        ostrstream  theDummyStream;

                        if (h.getVerbose() == false)
                        {
                            xalan.setWarningStream(0);
                            // xalan.setWarningStream(&theDummyStream);
                        }

                        // Parsing(compile) the XSL stylesheet and report the results..
                        //
                        const XalanCompiledStylesheet*  compiledSS = 0;
                        xalan.compileStylesheet(xslInputSource, compiledSS);
                        if (compiledSS == 0 )
                        {
                            // Report the failure and be sure to increment fail count.
                            //
                            CharVectorType  theVector(theMemoryManager);

                            TranscodeToLocalCodePage(currentFile, theVector);

                            cout << "Failed to parse stylesheet for "
                                 << theVector
                                 << endl;
                            h.data.fail += 1;
                            XalanDOMString tmp("Failed to parse stylesheet.  ", theMemoryManager);
                            tmp += XalanDOMString(xalan.getLastError(), theMemoryManager);
                            logFile.logErrorResult(currentFile, theMemoryManager);
                            continue;
                        }

                        // Parse the Source XML based on input parameters, and then perform transform.
                        //
                        switch (h.args.source)
                        {
                            case 0:
                            case 1:
                                theResult = parseWithTransformer(h.args.source, xalan, xmlInputSource, compiledSS, resultFile, logFile, h);
                                break;

                            case 2:
                                theResult = parseWithXerces(xalan, xmlInputSource, compiledSS, resultFile, logFile, h);
                                break;
                        }

                        // Check and report results...  Then delete compiled stylesheet.
                        //
                        h.checkResults(theOutputFile, theGoldFile, logFile);
                        xalan.destroyStylesheet(compiledSS);
                    }

                    logFile.logTestCaseClose("Done", "Pass");
                }
            }

            // Check to see if -sub cmd-line directory was processed correctly.
            //
            if (!foundDir)
            {
                CharVectorType  vect(theMemoryManager);
                TranscodeToLocalCodePage(h.args.sub, vect);

                cout << "Specified test directory: \"" << c_str(vect) << "\" not found" << endl;
            }
            else if (theResult != 0)
            {
                cout << "An unexpected tranformer error occurred.  The error code is "
                     << theResult
                     << "\n"
                     << "The error message is \""
                     << xalan.getLastError()
                     << endl;
            }

            h.reportPassFail(logFile, UniqRunid);
            logFile.logTestFileClose("Conformance ", "Done");
            logFile.close();

            h.analyzeResults(xalan, resultsFile);

            theResult = h.getFailureCount();
        }
    }
    catch(const XalanDiagnosticMemoryManager::LockException&)
    {
        cerr << "An attempt was made to allocate memory "
                "from a locked XalanDiagnosticMemoryManager "
                "instance!"
             << endl
             << endl;

        theResult = -1;
    }
    catch(...)
    {
        cerr << "Initialization of testing harness failed!" << endl << endl;
    }

    return theResult;
}



int
main(
            int     argc,
            char*   argv[])
{
#if !defined(NDEBUG) && defined(_MSC_VER)
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
#endif

#if defined(XALAN_VQ_SPECIAL_TRACE)
    QuantifyStopRecordingData();
    QuantifyClearData();
#endif

    int theResult = 0;

    try
    {
        using xercesc::XMLPlatformUtils;
        using xercesc::XMLUni;

        XalanMemoryManagerDefault       theGlobalMemoryManager;
        XalanDiagnosticMemoryManager    theDiagnosticMemoryManager(theGlobalMemoryManager, true, &cerr);
        XalanMemoryManagerDefault       theTestingMemoryManager;

        // Call the static initializers for xerces and xalan, and create a transformer
        //
        XMLPlatformUtils::Initialize(
            XMLUni::fgXercescDefaultLocale,
            0,
            0,
#if XERCES_VERSION_MAJOR >= 3
            &theDiagnosticMemoryManager);
#else
            &theDiagnosticMemoryManager,
            true);
#endif

        XalanTransformer::initialize(theDiagnosticMemoryManager);

        theDiagnosticMemoryManager.lock();

        {
            theResult = runTests(argc, argv, theTestingMemoryManager);
        }

        theDiagnosticMemoryManager.unlock();

        XalanTransformer::terminate();

        XMLPlatformUtils::Terminate();

        XalanTransformer::ICUCleanUp();
    }
    catch(...)
    {
        cerr << "Initialization failed!" << endl << endl;

        theResult = -1;
    }

    return theResult;
}
