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

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

using namespace empathy;
using namespace empathy::util;
using namespace empathy::ir;
using namespace empathy::sema;
using namespace empathy::diagnostics;

Compiler::Compiler( int argc, char** argv ) :
    m_pEnv( make_shared< sema::Env >() )
{
    llvm::InitLLVM( argc, argv );

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

    builtins::SetupBuiltins( *m_pEnv );

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

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

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

uint32_t Compiler::execute( const string& filename )
{
    auto identity = sema::InjectDomainIntoIdentity( builtins::RootIdentity(), sema::DomainCompileTime() );

    auto result = LoadAndExecuteFile( m_pEnv, filename, identity, GetValueType< uint32_t >() );

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

    if( !result )
        return 1;

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

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

optional< Value > Compiler::LoadAndExecuteFile( const ptr< Env >& e, const string& filename, const Term& identity, optional< Term > returnType )
{
    auto cfg = LoadAndParseFile( e, filename, identity, returnType );
    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< llr::CFG > Compiler::LoadAndParseFile( const ptr< Env >& e, const string& filename, const Term& identity, optional< Term > returnType )
{
    ifstream sourcefile( filename.c_str() );
    if( !sourcefile.good() )
    {
        DiagnosticsManager::GetInstance().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 r = make_shared< parse::Resolver >(
        make_shared< lex::Lexer >( sourcefile, filename ), c );
    parse::Parser p( r );

    auto cfg = make_shared< llr::CFG >();
    p.setCFG( cfg );
    p.cfg()->setCurrentBB( cfg->entryBB() );

    p.parseSequence();

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

    if( !r->eos() )
    {
        DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
            r->getCurrentLocation(), "syntax error." );
    }

    return cfg;
}
