#include "compiler.h"
#include "builtins/builtins.h"
#include "builtins/helpers.h"
#include "sema/sema.h"
#include "verify/verify.h"
#include "execute/execute.h"
#include "diagnostics/diagnostics.h"

#include "llvm/InitializePasses.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/TargetSelect.h"

#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Signals.h"

using namespace goose;
using namespace goose::util;
using namespace goose::eir;
using namespace goose::sema;
using namespace goose::diagnostics;
using namespace goose::builtins;

namespace goose::compile
{
    Compiler::Compiler( int argc, char** argv ) :
        m_pEnv( make_shared< sema::Env >() )
    {
        llvm::sys::PrintStackTraceOnErrorSignal(*argv);

        string cmdArgs;
        for( size_t i = 1; i < static_cast< size_t >( argc ); ++i )
        {
            if( i > 1 )
                cmdArgs = cmdArgs + ' ';
            cmdArgs = cmdArgs + argv[i];
        }

        SetupBuiltins( *m_pEnv );

        RegisterBuiltinFunc< string() >( *m_pEnv, "Args"_sid,
            [cmdArgs]()
            {
                return cmdArgs;
            } );

        RegisterBuiltinFunc< void ( string ) >( *m_pEnv, "Print"_sid,
            []( const string& str )
            {
                cout << str;
            } );

        llvm::InitializeAllTargets();
        llvm::InitializeAllTargetMCs();
        llvm::InitializeAllAsmPrinters();
        llvm::InitializeAllAsmParsers();
    }

    uint32_t Compiler::execute( const string& filename )
    {
        auto locVarsIdentity = AppendToVectorTerm( builtins::RootIdentity(), TSID( locvars ) );
        m_pEnv->addVisibilityRule( builtins::RootIdentity(), locVarsIdentity );
        auto result = LoadAndExecuteFile( m_pEnv, filename, locVarsIdentity, GetValueType< uint32_t >(), ToValue< uint32_t >( 1 ) );

        if( DiagnosticsManager::GetInstance().errorsWereEmitted() )
            return 1;

        if( !result )
        {
            DiagnosticsManager::GetInstance().emitErrorMessage( 0,
                format( "{}: could not be executed.", filename ) );
            return 1;
        }

        if( result->isPoison() )
            return 1;

        return *FromValue< uint32_t >( *result );
    }

    optional< Value > Compiler::LoadAndExecuteFile( const ptr< Env >& e, const string& filename, const Term& identity,
        const Term& returnType, optional< Value > defRetVal )
    {
        auto cfg = LoadAndParseFile( e, filename, identity, returnType, defRetVal );
        if( !cfg )
            return PoisonValue();

        if( !cfg->entryBB()->canBeExecuted() )
        {
            DiagnosticsManager::GetInstance().emitErrorMessage( 0,
                format( "{}: can not be executed.", filename ) );
            return PoisonValue();
        }

        execute::VM vm;
        return vm.execute( cfg->entryBB() );
    }

    ptr< cir::CFG > Compiler::LoadAndParseFile( const ptr< Env >& e, const string& filename, const Term& identity,
        const Term& returnType, optional< Value > defRetVal )
    {
        auto& dm = DiagnosticsManager::GetInstance();

        assert( returnType == GetValueType< void >() || defRetVal );

        ifstream sourcefile( filename.c_str() );
        if( !sourcefile.good() )
        {
            dm.emitErrorMessage( 0,
                format( "can't open '{}'.", filename ) );
            return nullptr;
        }

        sema::Context c( e, identity, returnType );
        DiagnosticsContext dc( 0, true );
        VerbosityContext vc( Verbosity::Normal, true );

        auto cfg = make_shared< cir::CFG >( 0 );
        cfg->createBB();
        cfg->setCurrentBB( cfg->entryBB() );

        auto cb = make_shared< CodeBuilder >( cfg );
        c.setBuilder( cb );

        auto r = make_shared< parse::Resolver >(
            make_shared< lex::Lexer >( sourcefile, filename ), c );
        parse::Parser p( r );

        p.parseSequence();

        if( cfg->isPoisoned() )
            return cfg;

        if( !r->eos() )
        {
            dm.emitSyntaxErrorMessage( r->currentLocation(), "syntax error." );
            return nullptr;
        }

        if( cfg->currentBB() && !cfg->currentBB()->terminator() )
        {
            p.flushValue();
            cb->destroyAllLiveValues( c );

            if( returnType == GetValueType< void >() )
               cb->emitTerminator( r->currentLocation(), cir::Ret() );
            else if( !defRetVal )
            {
                dm.emitSyntaxErrorMessage( r->currentLocation(), "missing return statement." );
                return nullptr;
            }
            else
            {
                auto converted = ConvertValueToType( c, *defRetVal, returnType );
                if( holds_alternative< ValUnifyError >( converted ) )
                {
                    switch( get< ValUnifyError >( converted ) )
                    {
                        case ValUnifyError::NoSolution:
                            dm.emitErrorMessage( defRetVal->locationId(), "default return value type mismatch." );
                            break;

                        case ValUnifyError::Ambiguous:
                            dm.emitErrorMessage( defRetVal->locationId(), "ambiguous default return value conversion." );
                            break;
                    }

                    return nullptr;
                }

                cb->emitTerminator( r->currentLocation(), cir::Ret( get< Value >( converted ) ) );
            }
        }

        verify::Func fv( c, cfg, returnType );
        if( !fv.verify() )
            return nullptr;

        return cfg;
    }
}
