/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include <bu/file.h>
#include <bu/optparser.h>
#include <bu/buffer.h>
#include <bu/list.h>
#include <bu/sio.h>
#include <bu/variant.h>

using namespace Bu;

class Test
{
public:
    Test() :
        bExpectPass( true )
    {
    }

    Bu::String sName;
    bool bExpectPass;
};
typedef Bu::List<Test> TestList;

class Suite
{
public:
    Bu::String sName;
    TestList lTest;
};
//typedef Bu::List<Suite> SuiteList;

enum TokType
{
    tokFluff,
    tokSuite,
    tokTest,
    tokChar,
    tokBlock,
    tokCleanup,
    tokEof
};

Bu::Formatter &operator<<( Bu::Formatter &f, TokType t )
{
    switch( t )
    {
        case tokFluff:      return f << "tokFluff";
        case tokSuite:      return f << "tokSuite";
        case tokTest:       return f << "tokTest";
        case tokChar:       return f << "tokChar";
        case tokBlock:      return f << "tokBlock";
        case tokCleanup:    return f << "tokCleanup";
        case tokEof:        return f << "tokEof";
    }

    return f;
}

Bu::Formatter &operator<<( Bu::Formatter &f, const Test &t )
{
    return f << "{" << t.sName << ", bExpectPass=" << t.bExpectPass << "}";
}

Bu::Formatter &operator<<( Bu::Formatter &f, const Suite &s )
{
    return f << "Suite[" << s.sName << "] = " << s.lTest << f.nl;
}

class Parser
{
public:
    Parser( const Bu::String &sFile ) :
        sIn( sFile ),
        fIn( sFile, File::Read ),
        bIn( fIn ),
        cBuf( 0 ),
        bAvail( false ),
        eMode( mRoot ),
        iLine( 1 ),
        iChar( 0 ),
        iDepth( 0 )
    {
    }

    char nextChar()
    {
        if( bAvail )
            return cBuf;

        if( bIn.read( &cBuf, 1 ) < 1 )
            throw Bu::ExceptionBase("End of stream");
        bAvail = true;

        if( cBuf == '\n' )
        {
            iLine++;
            iChar = 0;
        }
        else
            iChar++;

        return cBuf;
    }
    
    TokType nextToken( Variant &v, Bu::String &sWsOut, int &iLineStart,
            int &iCharStart )
    {
        Bu::String sTok, sWs;
    
        char buf;
        try
        {
            buf = nextChar();
        }
        catch(...)
        {
            return tokEof;
        }

        for(;;)
        {
            if( buf == ' ' || buf == '\t' || buf == '\n' || buf == '\r' )
            {
                sWs += buf;
                bAvail = false;
            }
            else
                break;

            try
            {
                buf = nextChar();
            }
            catch(...)
            {
                sWsOut = sWs;
                return tokEof;
            }
        }

        sWsOut = sWs;

        iLineStart = iLine;
        iCharStart = iChar;
        bool bInStr = false;
        bool bDblStr;

        for(;;)
        {
            switch( eMode )
            {
                case mRoot:
                    if( buf == ' ' || buf == '\t' || buf == '\n'
                        || buf == '\r' )
                    {
                        if( sTok == "suite" )
                            return tokSuite;
                        else
                        {
                            v = sTok;
                            return tokFluff;
                        }
                    }
                    else if( buf == '(' || buf == ')' || buf == '{'
                        || buf == '}' || buf == ';' )
                    {
                        if( sTok.getSize() == 0 )
                        {
                            bAvail = false;
                            v = buf;
                            return tokChar;
                        }
                        else
                        {
                            v = sTok;
                            return tokFluff;
                        }
                    }
                    else
                    {
                        sTok += buf;
                        bAvail = false;
                    }
                    break;

                case mSuite:
                    if( buf == ' ' || buf == '\t' || buf == '\n'
                        || buf == '\r' )
                    {
                        if( sTok == "test" )
                            return tokTest;
                        else if( sTok == "cleanup" )
                            return tokCleanup;
                        else
                        {
                            v = sTok;
                            return tokFluff;
                        }
                    }
                    else if( buf == '(' || buf == ')'
                        || buf == '}' || buf == ';' )
                    {
                        if( sTok.getSize() == 0 )
                        {
                            bAvail = false;
                            v = buf;
                            return tokChar;
                        }
                        else
                        {
                            v = sTok;
                            return tokFluff;
                        }
                    }
                    else if( buf == '{' )
                    {
                        if( sTok.getSize() > 0 )
                        {
                            v = sTok;
                            return tokFluff;
                        }
                        else
                        {
                            sTok += buf;
                            bAvail = false;
                            eMode = mBlock;
                            iDepth = 1;
                        }
                    }
                    else
                    {
                        sTok += buf;
                        bAvail = false;
                    }
                    break;

                case mBlock:
                    if( bInStr )
                    {
                        if( buf == '\\' )
                        {
                            sTok += buf;
                            bAvail = false;
                            sTok += nextChar();
                            bAvail = false;
                        }
                        else if( bDblStr == true && buf == '\"' )
						{
							sTok += buf;
							bAvail = false;
							bInStr = false;
						}
						else if( bDblStr == false && buf == '\'' )
						{
							sTok += buf;
							bAvail = false;
							bInStr = false;
						}
						else
						{
							sTok += buf;
							bAvail = false;
						}
					}
					else
					{
						if( buf == '\"' )
                        {
                            bInStr = true;
                            bDblStr = true;
                            sTok += buf;
                            bAvail = false;
                        }
                        else if( buf == '\'' )
                        {
                            bInStr = true;
                            bDblStr = false;
                            sTok += buf;
                            bAvail = false;
                        }
                        else if( buf == '}' )
                        {
                            sTok += buf;
                            bAvail = false;
                            iDepth--;
                            if( iDepth == 0 )
                            {
                                v = sTok;
                                eMode = mSuite;
                                return tokBlock;
                            }
                        }
                        else if( buf == '{' )
                        {
                            sTok += buf;
                            bAvail = false;
                            iDepth++;
                        }
                        else
                        {
                            sTok += buf;
                            bAvail = false;
                        }
                    }
                    break;
            }
        
            buf = nextChar();
        }
    }

    void firstPass()
    {
        Variant v;
        Bu::String sWs;
        int iL, iC;
        for(;;)
        {
            TokType t = nextToken( v, sWs, iL, iC );
            if( t == tokEof )
                return;
            switch( eMode )
            {
                case mRoot:
                    if( t == tokSuite )
                    {
                        if( nextToken( v, sWs, iL, iC ) != tokFluff )
                            throw Bu::ExceptionBase("%d:%d: Expected string "
                                "following suite.", iL, iC );
                        s.sName = v.get<Bu::String>();
                        if( nextToken( v, sWs, iL, iC ) != tokChar ||
                            v.get<char>() != '{' )
                            throw Bu::ExceptionBase("%d:%d: Expected {, got "
                                "'%s'", iL, iC, v.toString().getStr() );
                        eMode = mSuite;
                    }
                    break;

                case mSuite:
                    switch( t )
                    {
                        case tokFluff:
                            break;

                        case tokBlock:
                            break;

                        case tokTest:
                            {
                                if( nextToken( v, sWs, iL, iC ) != tokFluff )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "string following test.", iL, iC );
                                Test t;
                                t.sName = v.get<Bu::String>();
                                if( nextToken( v, sWs, iL, iC ) != tokBlock )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "{...} block.",
                                        iL, iC );
                                s.lTest.append( t );
                            }
                            break;
                        
                        case tokCleanup:
                            {
                                if( nextToken( v, sWs, iL, iC ) != tokBlock )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "{...} block.",
                                        iL, iC );
                            }
                            break;

                        case tokChar:
                            if( v.get<char>() == '}' )
                            {
                                eMode = mRoot;
                            }
                            else
                            {
                            }
                            break;

                        default:
                            sio << iL << ":" << iC << ": Unexpected "
                                << t << " found." << sio.nl;
                            return;
                            break;
                    }
                    break;

                default:
                    sio << "???" << sio.nl;
                    break;
            }
        }
    }

    void secondPass( const Bu::String &sOut )
    {
        File fOut( sOut, File::WriteNew );
        Formatter f( fOut );
        fIn.setPos( 0 );
        bIn.stop();
        bIn.start();
        bAvail = false;
        eMode = mRoot;
        iLine = 1;
        iChar = 0;
        bool bHasIncluded = false;

        Bu::String sWs;
        Variant v;
        int iL, iC;
        for(;;)
        {
            TokType t = nextToken( v, sWs, iL, iC );
            switch( eMode )
            {
                case mRoot:
                    if( t == tokSuite )
                    {
                        fOut.write( sWs );
                        if( nextToken( v, sWs, iL, iC ) != tokFluff )
                            throw Bu::ExceptionBase("%d:%d: Expected string "
                                "following suite.", iL, iC );
                        s.sName = v.get<Bu::String>();
                        if( nextToken( v, sWs, iL, iC ) != tokChar ||
                            v.get<char>() != '{' )
                            throw Bu::ExceptionBase("%d:%d: Expected {",
                                iL, iC );
                        eMode = mSuite;

                        if( bHasIncluded == false )
                        {
                            fOut.write("#include <bu/unitsuite.h>\n");
                            bHasIncluded = true;
                        }

                        Bu::String sClass = "_UnitSuite_" + s.sName;
                        f << "class " << sClass
                            << " : public Bu::UnitSuite" << f.nl
                            << "{" << f.nl << "public:" << f.nl
                            << "\t" << sClass << "()" << f.nl
                            << "\t{" << f.nl
                            << "\t\tsetName(\"" << s.sName << "\");" << f.nl;
                        for( TestList::iterator i = s.lTest.begin(); i; i++ )
                        {
                            f << "\t\tadd( static_cast<Bu::UnitSuite::Test>("
                                "&" << sClass << "::_test_" << (*i).sName << "), \""
								<< (*i).sName << "\", Bu::UnitSuite::"
                                "expectPass );" << f.nl;
                        }
                        f << "\t}" << f.nl << f.nl
                            << "\tvirtual ~" << sClass << "() { }" << f.nl
                            << f.nl;
                    }
                    else if( t == tokEof )
                    {
                        Bu::String sClass = "_UnitSuite_" + s.sName;
                        f << sWs << f.nl << "int main( int argc, char *argv[] )"
                            << f.nl << "{" << f.nl << "\treturn " << sClass
                            << "().run( argc, argv );" << f.nl << "}" << f.nl;
                    }
                    else
                    {
                        fOut.write( sWs );
                        f << v;
                    }
                    break;

                case mSuite:
                    switch( t )
                    {
                        case tokFluff:
                            fOut.write( sWs );
                            fOut.write( v.get<Bu::String>() );
                            break;

                        case tokTest:
                            {
                                fOut.write( sWs );
                                if( nextToken( v, sWs, iL, iC ) != tokFluff )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "string following test.", iL, iC );
                                Test t;
                                t.sName = v.get<Bu::String>();
                                if( nextToken( v, sWs, iL, iC ) != tokBlock )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "{...} block.",
                                        iL, iC );
                                
                                f << "void _test_" << t.sName << "()"
                                    << f.nl << "#line " << iL
                                    << " \"" << sIn << "\"" << f.nl << "    "
                                    << v << f.nl;
                            }
                            break;

                        case tokCleanup:
                            {
                                fOut.write( sWs );
                                if( nextToken( v, sWs, iL, iC ) != tokBlock )
                                    throw Bu::ExceptionBase("%d:%d: Expected "
                                        "{...} block.",
                                        iL, iC );
                                
                                f << "void cleanup()"
                                    << f.nl << "#line " << iL
                                    << " \"" << sIn << "\"" << f.nl << "    "
                                    << v << f.nl;
                            }
                            break;

                        case tokChar:
                            if( v.get<char>() == '}' )
                            {
                                f << "};" << f.nl << f.nl;
                                eMode = mRoot;
                            }
                            else
                            {
                                char buf = v.get<char>();
                                fOut.write( sWs );
                                fOut.write( &buf, 1 );
                            }
                            break;
                        
                        case tokBlock:
                            fOut.write( sWs );
                            f << f.nl << "#line " << iL << " \"" << sIn
								<< "\"" << f.nl;
                            fOut.write( v.get<Bu::String>() );

                            break;

                        default:
                            sio << iL << ":" << iC << ": Unexpected "
                                << t << " found." << sio.nl;
                            return;
                            break;
                    }
                    break;

                default:
                    sio << "???" << sio.nl;
                    break;
            }
            if( t == tokEof )
                return;
        }
    }

private:
    Bu::String sIn;
    File fIn;
    Buffer bIn;
    char cBuf;
    bool bAvail;
    enum Mode
    {
        mRoot,
        mSuite,
        mBlock
    };
    Mode eMode;
    int iLine, iChar;
    int iDepth;
    Suite s;
};

int main( int argc, char *argv[] )
{
    if( argc < 3 )
    {
        sio << "Too few parameters." << sio.nl;
        return 0;
    }
    Parser p( argv[1] );

    p.firstPass();

    p.secondPass( argv[2] );
}

