/* $Id: parsingTest.cpp 3583 2010-07-13 20:39:42Z kmartin $ */
/** @file parsingTest.cpp
 * 
 * \brief This file tests the validity of an OSiL, OSoL or OSrL file.  
 *
 * @author  Horand Gassmann, Jun Ma, Kipp Martin 
 *
 * \remarks
 * Copyright (C) 2010-2011, Horand Gassmann, Jun Ma, Kipp Martin,
 * Northwestern University, and the University of Chicago.
 * All Rights Reserved.
 * This software is licensed under the Eclipse Public License. 
 * Please see the accompanying LICENSE file in root directory for terms.
 * 
 * 
 */ 

#include <cppad/cppad.hpp> 
#include "OSConfig.h"
#include "OSInstance.h"  
#include "OSOption.h"
#include "OSResult.h" 
#include "OSiLReader.h"        
#include "OSiLWriter.h"        
#include "OSoLReader.h"        
#include "OSoLWriter.h"        
#include "OSrLReader.h"          
#include "OSrLWriter.h"        
#include "OSFileUtil.h"  
#include "CoinError.hpp"

#include "OSWSUtil.h" 
#include "OShL.h"     
#include "OSErrorClass.h"
#include "OSBase64.h"
#include "OSMathUtil.h"


#include <CoinMpsIO.hpp>
#include <CoinPackedMatrix.hpp>


#include "CoinError.hpp"
#include "CoinHelperFunctions.hpp"

#ifdef HAVE_CTIME
# include <ctime>
#else
# ifdef HAVE_TIME_H
#  include <time.h>
# else
#  error "don't have header file for time"
# endif
#endif


#ifdef HAVE_CMATH
# include <cmath>
#else
# ifdef HAVE_CMATH_H
#  include <cmath.h>
# endif
#endif



#ifdef HAVE_CSTDIO
# include <cstdio>
#else
# ifdef HAVE_STDIO_H
#  include <stdio.h>
# else
#  error "don't have header file for stdio"
# endif
#endif

//#ifdef HAVE_WINDOWS_H
//#include <windows.h>
//#endif

using std::cout;   
using std::endl;
using std::ostringstream; 

//#define MY_DEBUG

bool interactiveShell(std::string *schema, std::string *testFileName, std::string *outFileName,
                      bool *compress, bool *addWhiteSpace, bool *verifyObjects, bool *useRandomObjects,
                      unsigned int *seed, int *nrep, double *density, bool *conformant);
void printHelp();
std::string getUserInput(bool defaultPresent);


int main(int argC, char* argV[])
{
    WindowsErrorPopupBlocker();

    // define the classes
    FileUtil *fileUtil = NULL;  
    OSiLReader *osilreader = NULL;
    OSoLReader *osolreader = NULL;
    OSrLReader *osrlreader = NULL;
    // end classes    

    std::string testFileName;
    std::string outFileName;
    std::string osxl;
    std::string schema;
    ostringstream parsingTestResult;
    ostringstream parsingTestResultFailure;
//    ostringstream outputString;
    std::string outputString;

    bool doOutput = false;
    bool compress = true;
    bool addWhiteSpace = true;
    bool verifyObjects = true;
    bool useRandomObjects = false;

    int nrep = 1;
    unsigned int seed;
    double density = 0.5;
    bool conformant = true;
    
    seed = time(NULL);

//    Command line parser and user interface

    if (argC == 1)
    {
        if (interactiveShell(&schema, &testFileName, &outFileName, &compress, &addWhiteSpace, &verifyObjects,
            &useRandomObjects, &seed, &nrep, &density, &conformant) != true) return 0;

#ifdef MY_DEBUG
            std::cout << "User dialog completed " << std::endl;
#endif
    }
    else //command line arguments were given
/****
 * command line arguments and default values
 * -schema=[osil|osol|osrl]  used to select the schema
 * -file=<filename>          name of the input file
 * -random=[YES|no]          should random objects be generated?
 * -replications=<nnn>       number of replications of the random test
 * -seed=<nnn>               seed for the random number generator
 * -density=<x.x>            average fraction of child elements
 * -conformant=[YES|no]      should side constraints be enforced?
 * -outfile=<filename>       name of the output file
 * -compress=[YES|no]        use mult and incr to compress output file?
 * -whitespace=[YES|no]      use whitespace between elements?
 * -verify=[yes|NO]          test input and output objects for equality?
 * 
 * -random is synonymous with -random=yes,
 * -norandom is synonymous with -random=no, etc.  
 */
    {
        int argM = 12; //maximal number of command line arguments including `parsingTest`
        if( argC > argM)
        {
            std::cout << "Too Many Input Parameters" << std::endl;
            return 1;
        }
        if ( (strcmp(argV[1],"--help") == 0) || (strcmp(argV[1],"-h") == 0) || (strcmp(argV[1],"-?") == 0) )
        {
            printHelp();
            return 0;
        }
        if ( (strcmp(argV[1],"quit") == 0) || (strcmp(argV[1],"exit") == 0) || (strcmp(argV[1],"stop") == 0) )
            return 0;
 /*
        if (argC < 3 || ((strcmp(argV[1],"osil") != 0) && (strcmp(argV[1],"osol") != 0) && (strcmp(argV[1],"osrl") != 0)))
        {
            std::cout << "usage: parsingtest <parser> <filename> [<outputFile> [(no)compress (no)addWhiteSpace (noverify]]" << std::endl;
            std::cout << "type \"parsingtest --help\" for more information" << std::endl;
            return 1;
        }
        schema = argV[1];
        testFileName =  argV[2];
        if (argC > 3)
        {
            outFileName = argV[3];
            doOutput = true;
        }
        if (argC > 4)
            compress = (strcmp(argV[4],"nocompress") != 0); 
        if (argC > 5)
            addWhiteSpace = (strcmp(argV[5],"noaddwhitespace") != 0); 
        if (argC == 7)
            verifyObjects = (strcmp(argV[6],"verifyobjects") == 0); 
 */    
        size_t epos;
        std::string argument, option, value;
        for (int i=1; i<argC; i++)
        {
            if (argV[i][0] != '-') 
                throw ErrorClass("usage: -<option>=<value>; try -help for more");
            argument = std::string(argV[i]);
            epos = argument.find('=');
            option = argument.substr(0,epos);
            if (epos < std::string::npos)
                value = argument.substr(epos+1);
            else
                value = "";

    std::cout << "option = \"" << option << "\"" << std::endl;        
    std::cout << "value  = \"" << value  << "\"" << std::endl;        
    
            if (option == "-schema")
            {
                if (value == "")
                    throw ErrorClass("schema option must have a value");
                schema = value;
            }
            else if (option == "-file")
            {
                if (value == "")
                    throw ErrorClass("file option must have a value");
                testFileName = value;
                useRandomObjects = false;
            }
            else if (option == "-replications")
            {
                if (value == "")
                    throw ErrorClass("number of replications must be given");
                nrep = atoi(value.c_str());
            }
            else if (option == "-seed")
            {
                if (value == "")
                    throw ErrorClass("seed option must have a value");
                seed = atoi(value.c_str());
            }
            else if (option == "-density")
            {
                if (value == "")
                    throw ErrorClass("density option must have a value");
                density = os_strtod(value.c_str(), NULL);
            }
            else if (option == "-outfile")
            {
                if (value == "")
                    throw ErrorClass("outfile option must have a value");
                outFileName = value;
            }
            else if (option == "-random")
            {
                useRandomObjects = (value == "YES" || value == "yes" || value == "Y" || value == "Y" || value == "y" || value == "");
                testFileName = "";
            }
            else if (option == "-conformant")
            {
                conformant = (value == "YES" || value == "yes" || value == "Y" || value == "Y" || value == "y" || value == "");
            }
            else if (option == "-compress")
            {
                compress = (value == "YES" || value == "yes" || value == "Y" || value == "Y" || value == "y" || value == "");
            }
            else if (option == "-whitespace")
            {
                addWhiteSpace = (value == "YES" || value == "yes" || value == "Y" || value == "Y" || value == "y" || value == "");
            }
            else if (option == "-verify")
            {
                if (value == "")
                    verifyObjects = true;
                else 
                    verifyObjects = !(value == "NO" || value == "no" || value == "N" || value == "N" || value == "");

                if (verifyObjects)
                {
                    if (outFileName == "") outFileName = "junk.tmp";
                }
            }
            else if (option == "-norandom")
            {
                useRandomObjects = false;
            }
            else if (option == "-noconformant")
            {
                conformant = false;
            }
            else if (option == "-nocompress")
            {
                compress = false;
            }
            else if (option == "-nowhitespace")
            {
                addWhiteSpace = false;
            }
            else if (option == "-noverify")
            {
                verifyObjects = false;
            }
            else
            {
                std::cout << "option \"" << option << "\" not recognized" << std::endl;
            }

        }
    }

    if (outFileName != "") doOutput = true;

    fileUtil = new FileUtil();

    try
    {

/*********************************************************************
 * Generate and parse random objects
 *********************************************************************/
        if (useRandomObjects)
        {
            if (schema == "osil")
            {
                for (int irep=0; irep < nrep; irep++)
                {
/* steps required:
 *    generate random object
 *    write to string    
 *    parse string
 *    if verify, compare
 */        
                }
            }
            else if (schema == "osol")
            {
                OSOption *osoption, *osoption2;
                OSoLWriter *osolwriter;
                osolwriter = new OSoLWriter();
                OSoLReader *osolreader;

                for (int irep=0; irep < nrep; irep++)
                {
std::cout << "use random number seed: " << seed << std::endl;

                    srand(seed);

std::cout << "set random osoption" << std::endl;
                    osoption = new OSOption();
                    osoption->setRandom(density, conformant);

std::cout << "write to string" << std::endl;

                    osxl = osolwriter->writeOSoL(osoption);

std::cout << osxl << std::endl;

                    osolreader = new OSoLReader();
                    osoption2 = osolreader->readOSoL( osxl);
                    if (verifyObjects == true)
                    {
                        if (osoption->IsEqual(osoption2) == false)
                            throw ErrorClass("Two objects are not equal!");
                    }
                    delete osoption;
                    delete osolreader;
                    osoption  = NULL;
                    osolreader = NULL;

                    seed = rand();
                }
            }
            else if (schema == "osrl")
            {
                OSResult *osresult, *osresult2;
                OSrLWriter *osrlwriter;
                osrlwriter = new OSrLWriter();
                OSrLReader *osrlreader;

                for (int irep=0; irep < nrep; irep++)
                {
std::cout << "use random number seed: " << seed << std::endl;

                    srand(seed);

std::cout << "set random osresult" << std::endl;
                    osresult = new OSResult();
                    osresult->setRandom(density, conformant);

std::cout << "write to string" << std::endl;

                    osxl = osrlwriter->writeOSrL(osresult);

std::cout << osxl << std::endl;

                    osrlreader = new OSrLReader();
                    osresult2 = osrlreader->readOSrL( osxl);
                    if (verifyObjects == true)
                    {
                        if (osresult->IsEqual(osresult2) == false)
                            throw ErrorClass("Two objects are not equal!");
                    }
                    delete osresult;
                    delete osrlreader;
                    osresult  = NULL;
                    osrlreader = NULL;

                    seed = rand();
                }
            }
        }
        else if (testFileName == "")
            throw ErrorClass("No filename given. Nothing to parse.");
        else
        {
#ifdef MY_DEBUG
            std::cout << "Processing file: " ;
            std::cout <<  testFileName << std::endl;
#endif

            osxl = fileUtil->getFileAsString( testFileName.c_str() );

#ifdef MY_DEBUG
            std::cout << "Done reading the file into memory" << std::endl;
#endif

/*********************************************************************
 * Parse OSiL file
 *********************************************************************/
            if (schema == "osil")
            {
                osilreader = new OSiLReader(); 
                OSInstance *osinstance;
#ifdef MY_DEBUG
                std::cout << "Start parsing the file" << std::endl;
#endif
                osinstance = osilreader->readOSiL( osxl);

#ifdef MY_DEBUG
                parsingTestResult << "Parsed file successfully" << std::endl;
#endif

                if (doOutput == true)
                {
                    OSiLWriter *osilwriter;
                    osilwriter = new OSiLWriter();
                    osilwriter->m_bWriteBase64 = compress;
                    osilwriter->m_bWhiteSpace  = addWhiteSpace;
#ifdef MY_DEBUG
                    std::cout << "Create  output string from in-memory object" << std::endl;
#endif
                    outputString = osilwriter->writeOSiL(osinstance);
#ifdef MY_DEBUG
                    std::cout << "Created output string from in-memory object" << std::endl;
#endif
                    if (fileUtil->writeFileFromString( outFileName.c_str(), outputString) != true)
                        throw ErrorClass(" Error while writing compressed file");

                    if (verifyObjects == true)
                    {
                        osxl = fileUtil->getFileAsString( outFileName.c_str() );
                        OSInstance *osinstance2;
                        osinstance2 = osilreader->readOSiL( osxl);
                        if (osinstance2->IsEqual(osinstance) == false)
                            throw ErrorClass("Two objects are not equal!");
                    }

                    delete osilwriter;
                    osilwriter = NULL;
                }
                delete osilreader;
                osilreader = NULL;
            }

/*********************************************************************
 * Parse OSoL file
 *********************************************************************/
            else if (schema == "osol")
            {
                osolreader = new OSoLReader(); 
                OSOption *osoption;
#ifdef MY_DEBUG
                std::cout << "Start parsing the file" << std::endl;
#endif
                osoption = osolreader->readOSoL( osxl);

#ifdef MY_DEBUG
                parsingTestResult << "Parsed file successfully" << std::endl;
#endif

                if (doOutput == true)
                {
                    OSoLWriter *osolwriter;
                    osolwriter = new OSoLWriter();
                    osolwriter->m_bWriteBase64 = compress;
                    osolwriter->m_bWhiteSpace  = addWhiteSpace;
#ifdef MY_DEBUG
                    std::cout << "Create  output string from in-memory object" << std::endl;
#endif
                    outputString = osolwriter->writeOSoL(osoption);
#ifdef MY_DEBUG
                    std::cout << "Created output string from in-memory object" << std::endl;
#endif
                    if (fileUtil->writeFileFromString( outFileName.c_str(), outputString) != true)
                        throw ErrorClass(" Error while writing compressed file");

                    if (verifyObjects == true)
                    {
                        osxl = fileUtil->getFileAsString( outFileName.c_str() );
                        OSoLReader *osolreader2;
                        osolreader2 = new OSoLReader(); 
                        OSOption *osoption2;
                        osoption2 = osolreader2->readOSoL( osxl);
#ifdef MY_DEBUG
                        std::cout << "Compare in-memory objects" << std::endl;
#endif
                        if (osoption2->IsEqual(osoption) == false)
                            throw ErrorClass("Two objects are not equal!");
#ifdef MY_DEBUG
                        else
                            std::cout << "in-memory objects compare equal" << std::endl;
#endif
                        delete osolreader2;
                        osolreader2 = NULL;
                    }

                    delete osolwriter;
                    osolwriter = NULL;
                }
                delete osolreader;
                osolreader = NULL;
            }

/*********************************************************************
 * Parse OSrL file
 *********************************************************************/
            else if (schema == "osrl")
            {
                osrlreader = new OSrLReader(); 
                OSResult *osresult;
#ifdef MY_DEBUG
                std::cout << "Start parsing the file" << std::endl;
#endif
                osresult = osrlreader->readOSrL( osxl);

#ifdef MY_DEBUG
                parsingTestResult << "Parsed file successfully" << std::endl;
#endif

                if (doOutput == true)
                {
                    OSrLWriter *osrlwriter;
                    osrlwriter = new OSrLWriter();
                    osrlwriter->m_bWriteBase64 = compress;
                    osrlwriter->m_bWhiteSpace  = addWhiteSpace;
#ifdef MY_DEBUG
                    std::cout << "Create  output string from in-memory object" << std::endl;
#endif
                    outputString = osrlwriter->writeOSrL(osresult);
#ifdef MY_DEBUG
                    std::cout << "Created output string from in-memory object" << std::endl;
#endif
                    if (fileUtil->writeFileFromString( outFileName.c_str(), outputString) != true)
                        throw ErrorClass(" Error while writing compressed file");

                    if (verifyObjects == true)
                    {
                        osxl = fileUtil->getFileAsString( outFileName.c_str() );
                        OSrLReader *osrlreader2;
                        osrlreader2 = new OSrLReader(); 
                        OSResult *osresult2;
                        osresult2 = osrlreader2->readOSrL( osxl);
#ifdef MY_DEBUG
                        std::cout << "Compare in-memory objects" << std::endl;
#endif
                        if (osresult2->IsEqual(osresult) == false)
                            throw ErrorClass("Two objects are not equal!");
#ifdef MY_DEBUG
                        else
                            std::cout << "in-memory objects compare equal" << std::endl;
#endif
                        delete osrlreader2;
                        osrlreader2 = NULL;
                    }

                    delete osrlwriter;
                    osrlwriter = NULL;
                }
                delete osrlreader;
                osrlreader = NULL;
            }
        }
    }    
    catch(const ErrorClass& eclass)
    {
        cout << endl << endl << endl;
        if (osilreader != NULL) delete osilreader;
//        if (osolreader != NULL) delete osolreader;
        if (osrlreader != NULL) delete osrlreader;
        //  Problem with the parser
        parsingTestResultFailure << eclass.errormsg << endl;
        parsingTestResultFailure << "There was a failure in the " + schema + " parser; ";
        parsingTestResultFailure << "file " << testFileName << " not processed." << endl;
    }

    delete fileUtil;
    fileUtil = NULL;
        
    if (parsingTestResultFailure.str().length() > 0)
    {
        cout << "PARSING TEST FAILED ON THE FOLLOWING:" << endl << endl;
        cout << parsingTestResultFailure.str() << endl << endl;
        return 1;
    }
    else
    {

#ifdef MY_DEBUG
        std::cout << "PARSING TEST SUCCEEDED" << std::endl;
#endif

        return 0;
    }
}//end main

bool interactiveShell(std::string *schema, std::string *testFileName, std::string *outFileName,
                      bool *compress, bool *addWhiteSpace, bool *verifyObjects, bool *useRandomObjects,
                      unsigned int *seed, int *nrep, double *density, bool *conformant)
{
    std::string userInput;
    bool haveParser = false;
    std::cout << "Welcome to the parser test, which is part of the Optimization Services suite" << std::endl << std::endl;

    std::cout << "This program (c) 2011 Horand Gassmann, Jun Ma and Kipp Martin" << std::endl << std::endl;

    while (haveParser == false)
    {
        std::cout << "Input the parser you want to test (osil|osol|osrl)" << std::endl;
        std::cout << "    or \"quit\" to exit the program > ";
        *schema = getUserInput(false);
        if (*schema == "osil") break; 
        if (*schema == "osol") break; 
        if (*schema == "osrl") break; 
        if (*schema == "quit") return false; 
    }

    std::cout << "Input the file you want to parse or press return to generate random problems> ";
    *testFileName = getUserInput(true);

    if (*testFileName == "")
    {
        std::cout << std::endl << "Random problem generation selected." << std::endl;
        *useRandomObjects = true;

        std::cout << std::endl << "Do you want to input a random number seed? [y|N] > ";
        std::string temp;
        temp = getUserInput(true);
        if (temp == "Y" || temp == "y")
        {
            std::cout << std::endl << "Input the random number seed (must be nonnegative integer) > ";
            std::cin >> *seed;            
        }

        std::cout << " Input number of replications > "; 
        std::cin  >> *nrep;

        std::cout << " Input density (0..1) > "; 
        std::cin  >> *density;

        std::cout << " Enforce side constraints (Y|n) > ";
        userInput = getUserInput(true);
        if ( (userInput == "no") || (userInput == "NO") || (userInput == "No") || (userInput == "N") || (userInput == "n") ) 
            *conformant = false;

        std::cout << "Do you want to verify that input and output represents the same object? (yes|NO) > ";
        userInput = getUserInput(true);
        if ( (userInput == "yes") || (userInput == "YES") || (userInput == "Yes")|| (userInput == "Y") || (userInput == "y") ) 
            *verifyObjects = true;
    }
    else
    {
        std::cout << std::endl << "The remaining arguments are optional." << std::endl;

        std::cout << "Name of the output file to which you want to print the object after processing" << std::endl;
        std::cout << "> ";
        *outFileName = getUserInput(true);

        if (*outFileName == "")
            std::cout << "No output generated; remaining options skipped." << std::endl; 
        else
        {
            std::cout << "Do you want to compress the output (using mult and incr attributes where possible)? (YES|no) > ";
            userInput = getUserInput(true);
            if ( (userInput == "no") || (userInput == "NO") || (userInput == "No") || (userInput == "N") || (userInput == "n") ) 
                *compress = false;

            std::cout << "Do you want to add whitespace (newline characters) between elements? (YES|no) > ";
            userInput = getUserInput(true);

            if ( (userInput == "no") || (userInput == "NO") || (userInput == "No") ) *addWhiteSpace = false;
    
            std::cout << "Do you want to verify that input and output represents the same object? (yes|NO) > ";
            userInput = getUserInput(true);
            if ( (userInput == "yes") || (userInput == "YES") || (userInput == "Yes")|| (userInput == "Y") || (userInput == "y") ) 
                *verifyObjects = true;
        }
    }
        
    std::cout << std::endl << "echo input parameters" << std::endl;
    std::cout << "schema:        \"" << *schema        << "\"" << std::endl;
    std::cout << "testFileName:  \"" << *testFileName  << "\"" << std::endl;
    std::cout << "outFileName:   \"" << *outFileName   << "\"" << std::endl;
    std::cout << "compress:      \"" << *compress      << "\"" << std::endl;
    std::cout << "addWhiteSpace: \"" << *addWhiteSpace << "\"" << std::endl;
    std::cout << "verifyObjects: \"" << *verifyObjects << "\"" << std::endl;
    std::cout << "replications:  \"" << *nrep          << "\"" << std::endl;
    std::cout << "random seed:   \"" << *seed          << "\"" << std::endl;
    std::cout << "density:       \"" << *density       << "\"" << std::endl;
    std::cout << "conformant:    \"" << *conformant    << "\"" << std::endl;

    return true;
}

void printHelp()
{
    std::cout << "usage: parsertest <parser> <filename>" << std::endl;
    std::cout << "            [<outfile> [(no)compress (no)addwhitespace (no)verify]]" << std::endl << std::endl;

     std::cout << "where <parser> is \'osil\' or \'osol\' or \'osrl\'" << std::endl;
    std::cout << "      <filename> is the (path to the) input file you want to parse" << std::endl;
    std::cout << "      <outfile> is the (path to the) output file to print the object just read" << std::endl;
    std::cout << "      compress|nocompress controls the use of mult and incr attributes" << std::endl;
    std::cout << "            to compress the output file. (The default is compress)" << std::endl;
    std::cout << "      addwhitespace|noaddwhitespace governs the use of whitespace (linefeed" << std::endl;
    std::cout << "            characters between elements. (The default is addwhitespace)" << std::endl;
    std::cout << "      verify|noverify governs checks whether the input and output files " << std::endl;
    std::cout << "            represent the same in-memory object. (The default is noverify)" << std::endl;
    return;
}

std::string getUserInput(bool defaultPresent)
{
    std::string lineText;

    std::string wordSep = " ";
    std::string optionName = "";
    std::string optionValue = "";
    std::string::size_type indexStart;
    std::string::size_type indexEnd;

    for (;;)
    {
        getline(std::cin, lineText);
        lineText = " " + lineText + " ";
        indexStart = lineText.find_first_not_of(wordSep);
        if (indexStart != std::string::npos) 
        {
            indexEnd = lineText.find_first_of(wordSep, indexStart + 1);
            return lineText.substr(indexStart, indexEnd - indexStart);
        }
        if (defaultPresent) return "";
        std::cout << std::endl;
        std::cout << "You did not enter a valid option. Try again." << std::endl;
    } 
    return "";
}
