#include "CrossLang.hpp"
#include <iostream>
#include <fstream>
#include <sstream>

namespace Tesses::CrossLang
{
    static TObject AstToTObject(GCList& ls,SyntaxNode node)
    {
        if(std::holds_alternative<std::nullptr_t>(node))
        return nullptr;
    if(std::holds_alternative<int64_t>(node))
    {
        return std::get<int64_t>(node);
    }
    if(std::holds_alternative<double>(node))
    {
        return std::get<double>(node);
    }
    if(std::holds_alternative<bool>(node))
    {
        return std::get<bool>(node);
    }
    if(std::holds_alternative<std::string>(node))
    {
        std::string str = std::get<std::string>(node);
        return str;
    }
    if(std::holds_alternative<char>(node))
    {
        char c = std::get<char>(node);
        return c;
    }
    if(std::holds_alternative<Undefined>(node))
    {
        auto r = TDictionary::Create(ls);
        ls.GetGC()->BarrierBegin();
        r->SetValue("Type",std::string(UndefinedExpression));
        ls.GetGC()->BarrierEnd();
        return r;
        
    }


    if(std::holds_alternative<AdvancedSyntaxNode>(node))
    {
        auto asn = std::get<AdvancedSyntaxNode>(node);
        auto r = TDictionary::Create(ls);
        ls.GetGC()->BarrierBegin();
        r->SetValue("Type",asn.nodeName);
        r->SetValue("IsExpression",asn.isExpression);
        TList* ls2 = TList::Create(ls);
        for(auto item : asn.nodes)
        {
            ls2->Add(AstToTObject(ls,item));
        }
        r->SetValue("Arguments", ls2);
        ls.GetGC()->BarrierEnd();
        return r;
    }
        return nullptr;
    }
    static TObject VM_SourceToAst(GCList& ls, std::vector<TObject> args)
    {
        std::string str;
        if(GetArgument(args,0,str))
        {
            std::stringstream strm(str);
            std::vector<LexToken> tokens;
            int res = Lex("memory.tcross",strm,tokens);
            if(res != 0)
            {
                throw VMException("Lex error at line: " + std::to_string(res));
            }
            Parser p(tokens);
            auto res2 =p.ParseRoot();
            return AstToTObject(ls,res2);
        }
        return nullptr;
    }
    static TObject VM_Eval(GCList& ls, std::vector<TObject> args)
    {
        std::string str;
        if(GetArgument(args,0,str))
        {
            if(current_function != nullptr)
            {
                return current_function->env->Eval(ls,str);
            }
        }
        return nullptr;
    }
    static TObject Failure(GCList& ls, std::string reason)
    {
        TDictionary* dict = TDictionary::Create(ls);
        ls.GetGC()->BarrierBegin();
        dict->SetValue("Success",false);
        dict->SetValue("Reason", reason);
        ls.GetGC()->BarrierEnd();
        return dict;
    }
    static TObject Success(GCList& ls)
    {
        TDictionary* dict = TDictionary::Create(ls);
        ls.GetGC()->BarrierBegin();
        dict->SetValue("Success",true);
        ls.GetGC()->BarrierEnd();
        return dict;
    }
    static TObject VM_getCurrentEnvironment(GCList& ls2,std::vector<TObject> args)
    {
        if(current_function != nullptr) return current_function->env;
        return Undefined();
    }
    static TObject VM_Compile(GCList& ls, std::vector<TObject> args)
    {
        try {
        TDictionary* dict;
        if(!GetArgumentHeap<TDictionary*>(args,0,dict)) return Undefined();
        /*
            VM.Compile
            {
                Name = "My name",
                Info = "",
                Sources = [],
                Version = "",
                Dependencies = [{Name="Name", Version}],
                ResourceDirectory = "",
                
            }   
        */

        std::string name = "Out";
        std::vector<std::pair<std::string,std::string>> sources;
        TVMVersion version(TVM_MAJOR,TVM_MINOR,TVM_PATCH,TVM_BUILD,TVM_VERSIONSTAGE);
        std::vector<std::pair<std::string,TVMVersion>> dependencies;
        std::vector<std::pair<std::string,TVMVersion>> tools;
        std::string info;
        std::string icon;
        TVFSHeapObject* vfsHO =nullptr;

        ls.GetGC()->BarrierBegin();
        TObject _name = dict->GetValue("Name");
        TObject _version = dict->GetValue("Version");
        TObject _sources = dict->GetValue("Sources");
        TObject _dependencies = dict->GetValue("Dependencies");
        TObject _tools = dict->GetValue("Tools");
        TObject _info = dict->GetValue("Info");
        TObject _icon = dict->GetValue("Icon");
        TObject _resourceFileSystem = dict->GetValue("ResourceFileSystem");
        
        TObject _out = dict->GetValue("Output");
        TList* _toolList;
        TList* _depList; TList* srcLst;
        GetObject<std::string>(_name,name);
        GetObject<std::string>(_info,info);
        GetObject<std::string>(_icon,icon);
        GetObjectHeap(_resourceFileSystem, vfsHO);
        std::string v2;
        if(GetObject<std::string>(_version,v2))
            TVMVersion::TryParse(v2, version);
        else
            GetObject(_version,version);

        if(GetObjectHeap<TList*>(_dependencies,_depList))
        {
            for(int64_t i = 0; i < _depList->Count(); i++)
            {
                TObject _dep = _depList->Get(i);
                TDictionary* _depD;
                if(GetObjectHeap<TDictionary*>(_dep, _depD))
                {
                    TObject _name2 = _depD->GetValue("Name");
                    TObject _version2 = _depD->GetValue("Version");
                    std::string name2;
                    std::string version2;
                    TVMVersion version02;

                    if(GetObject<std::string>(_name2,name2) && GetObject<std::string>(_version2,version2) && TVMVersion::TryParse(version2,version02))
                    {
                        dependencies.push_back(std::pair<std::string,TVMVersion>(name2, version02));
                    }
                    else if(GetObject<std::string>(_name2,name2) && GetObject(_version2,version02))
                    {
                        dependencies.push_back(std::pair<std::string,TVMVersion>(name2, version02));
                    }
                }
            }
            
        }
        if(GetObjectHeap<TList*>(_tools,_toolList))
        {
            for(int64_t i = 0; i < _toolList->Count(); i++)
            {
                TObject _dep = _toolList->Get(i);
                TDictionary* _depD;
                if(GetObjectHeap<TDictionary*>(_dep, _depD))
                {
                    TObject _name2 = _depD->GetValue("Name");
                    TObject _version2 = _depD->GetValue("Version");
                    std::string name2;
                    std::string version2;
                    TVMVersion version02;

                    if(GetObject<std::string>(_name2,name2) && GetObject<std::string>(_version2,version2) && TVMVersion::TryParse(version2,version02))
                    {
                        tools.push_back(std::pair<std::string,TVMVersion>(name2, version02));
                    }
                    else if(GetObject<std::string>(_name2,name2) && GetObject(_version2,version02))
                    {
                        tools.push_back(std::pair<std::string,TVMVersion>(name2, version02));
                    }
                }
            }
            
        }
        if(GetObjectHeap<TList*>(_sources,srcLst))
        {
            for(int64_t i = 0; i < srcLst->Count(); i++)
            {
                TObject _src = srcLst->Get(i);
                TDictionary* _srcD;
                if(GetObjectHeap<TDictionary*>(_src, _srcD))
                {
                    TObject _sourceTxt = _srcD->GetValue("Source");
                    TObject _filename = _srcD->GetValue("FileName");
                    

                    std::string srctxt = "";
                    std::string filename = "memory_" + std::to_string(i) + ".tcross";
                    bool fromFile = false;

                    GetObject<std::string>(_sourceTxt,srctxt);
                    GetObject<std::string>(_filename,filename);
                   
                    
                    sources.push_back(std::pair<std::string,std::string>(srctxt,filename));
                }
            }
            
        }
       
        ls.GetGC()->BarrierEnd();

        std::vector<LexToken> tokens;
        for(auto source : sources)
        {
            std::stringstream strm(source.first);

            int res = Lex(source.second, strm, tokens);
            if(res != 0)
            {
                    return Failure(ls, "Lex error in file \"" + source.second + "\":" + std::to_string(res));
            }
        }
        Parser parser(tokens);
        SyntaxNode n = parser.ParseRoot();
        CodeGen gen;
        gen.GenRoot(n);
        gen.dependencies = dependencies;
        gen.tools = tools;
        gen.info = info;
        gen.name = name;
        gen.version = version;
        gen.icon = icon;
        std::string outpath;
        TStreamHeapObject* stream;
        if(GetObjectHeap<TStreamHeapObject*>(_out, stream))
        {
            gen.Save(vfsHO != nullptr ? vfsHO->vfs : nullptr, stream->stream);
        }

        

        return Success(ls);
        } catch(std::exception& ex)
        {
            return Failure(ls, ex.what());
        }
    }
    void TStd::RegisterVM(GC* gc,TRootEnvironment* env)
    {

        env->permissions.canRegisterVM=true;
        GCList ls(gc);
        TDictionary* dict = TDictionary::Create(ls);
        dict->DeclareFunction(gc, "getRootEnvironmentAsDictionary","Get root environment as a dictionary",{},[env](GCList& ls, std::vector<TObject> args)-> TObject{
            return env->GetDictionary();
        });
        dict->DeclareFunction(gc, "getRootEnvironment","Get root environment, for reflection purposes",{},[env](GCList& ls2,std::vector<TObject> args)->TObject {return env;});
        dict->DeclareFunction(gc, "getCurrentEnvironment","Get current environment, for reflection purposes",{},VM_getCurrentEnvironment);
        dict->DeclareFunction(gc, "CreateEnvironment","Create root environment",{"$dict"},[](GCList& ls,std::vector<TObject> args)->TObject{
            TDictionary* dict;
            if(GetArgumentHeap(args,0,dict))
            {
                return TRootEnvironment::Create(ls,dict);
            }
            else
            {
                return TRootEnvironment::Create(ls,TDictionary::Create(ls));
            }
        });
        dict->DeclareFunction(gc, "LoadExecutable", "Load a crossvm executable",{"stream"},[](GCList& ls,std::vector<TObject> args)->TObject{
            TStreamHeapObject* strm;
            if(GetArgumentHeap(args,0,strm))
            {
                TFile* f =TFile::Create(ls);
                f->Load(ls.GetGC(),strm->stream);
                return f;
            }
            return nullptr;
        });
        dict->DeclareFunction(gc, "Eval", "Eval source code",{"source"}, VM_Eval);
        dict->DeclareFunction(gc, "Compile", "Compile Source",{"dict"},VM_Compile);
        
        dict->DeclareFunction(gc, "SourceToAst", "Convert source to ast", {"source"}, VM_SourceToAst);
        dict->DeclareFunction(gc, "getRuntimeVersion","Get the runtime version",{},[](GCList& ls,std::vector<TObject> args)->TObject {
            return TVMVersion(TVM_MAJOR,TVM_MINOR,TVM_PATCH,TVM_BUILD,TVM_VERSIONSTAGE);
        });
        
        gc->BarrierBegin();
        env->DeclareVariable("VM", dict);
        gc->BarrierEnd();
    }
    
}