#include <utility>
#include <vector>
#include <locale>
#include <codecvt>
#include "Script.hpp"
#include "Parser/Lexer.hpp"
#include "Parser/Parser.hpp"
#include "Binder/Binder.hpp"
#include "EvaluateResult.hpp"

Porygon::Script* Porygon::Script::Create(const u16string& script) {
    return new Script(script);
}

std::u16string To_UTF16(const string &s)
{
    std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
    return conv.from_bytes(s);
}
Porygon::Script *Porygon::Script::Create(const string &script) {
    return Script::Create(To_UTF16(script));
}

Porygon::Script::Script(const u16string& s)
    :   Diagnostics(make_shared<Diagnostics::DiagnosticsHolder>(s)),
        _boundScript(nullptr),
        _scriptVariables(new map<Utilities::HashedString, EvalValuePointer>()),
        _scriptTypes(new unordered_map<Utilities::HashedString, shared_ptr<ScriptType>>{}),
        _scriptOptions(nullptr)
{
    _evaluator = new Evaluator(this -> _scriptVariables, this -> GetScriptOptions());
    this -> Parse(s);
}

EvalValuePointer Porygon::Script::Evaluate() {
    return _evaluator->Evaluate(_boundScript.get());
}

Porygon::Script::~Script() {
    delete this -> _evaluator;
    this->_scriptVariables->clear();
    delete this->_scriptVariables;
    this->_scriptTypes->clear();
    delete this->_scriptTypes;
}

void Porygon::Script::Parse(const u16string& script) {
    auto lexer = Lexer(script, this);
    auto lexResult = lexer.Lex();
    auto parser = Parser::Parser(lexResult, this);
    auto parseResult = parser.Parse();
    for (auto token : lexResult){
        delete token;
    }
    lexResult.clear();
    if (!Diagnostics->HasErrors()){
        map<Utilities::HashedString, BoundVariable*> scriptScope;
        unordered_map<Utilities::HashedString, shared_ptr<const ScriptType>> variableTypes;
        auto bindScope = new BoundScope(&scriptScope, &variableTypes);
        this->_boundScript = shared_ptr<BoundScriptStatement>(Binder::Binder::Bind(this, parseResult, bindScope));
        for (const auto& v : scriptScope){
            this->_scriptVariables -> insert({v.first, nullptr});
            delete v.second;
        }
        for (const auto& v : variableTypes){
            this->_scriptTypes -> insert({v.first, nullptr});
        }
        scriptScope.clear();
        variableTypes.clear();
    }
    delete parseResult;
}

const EvalValue* Porygon::Script::GetVariable(const u16string &key) {
    return _scriptVariables -> at(HashedString::CreateLookup(key)).Clone();
}

bool Porygon::Script::HasVariable(const u16string &key) {
    auto f = _scriptVariables->find(HashedString::CreateLookup(key));
    return f != _scriptVariables->end();
}

bool Porygon::Script::HasFunction(const u16string &key) {
    auto f = _scriptVariables->find(HashedString::CreateLookup(key));
    return f != _scriptVariables->end() && f.operator->()->second->GetTypeClass() == TypeClass ::Function;
}

const EvalValue* Porygon::Script::CallFunction(const u16string &key, const vector<EvalValue *>& variables) {
    auto var = (GenericFunctionEvalValue*)GetVariable(key);
    return this->_evaluator->EvaluateFunction(var, variables);
}

Porygon::Script *Porygon::Script::Clone(const Porygon::Script *script) {
    auto s = new Script(script->_boundScript, script->Diagnostics);
    for (const auto& v: *script->_scriptVariables){
        s->_scriptVariables->insert({v.first, nullptr});
    }
    for (const auto& v: *script->_scriptTypes){
        s->_scriptTypes->insert({v.first, v.second});
    }

    s->_returnType = script->_returnType;
    return s;
}

Porygon::Script::Script(shared_ptr<BoundScriptStatement> boundScript,
                        shared_ptr<Porygon::Diagnostics::DiagnosticsHolder> diagnostics)
                        : _boundScript(std::move(boundScript)),
                          Diagnostics(std::move(diagnostics)),
                          _scriptVariables(new map<Utilities::HashedString, EvalValuePointer>()),
                          _scriptTypes(new unordered_map<Utilities::HashedString, shared_ptr<ScriptType>>{}),
                          _scriptOptions(nullptr)
                        {
    _evaluator = new Evaluator(_scriptVariables, this -> GetScriptOptions());
}

extern "C" {
    Porygon::Script* CreateScript(char16_t * s){
        return Porygon::Script::Create(s);
    }

    Porygon::EvaluateResult* EvaluateScript(Porygon::Script* script){
        try{
            auto result = script -> Evaluate();
            return new Porygon::EvaluateResult(result.Take());
        }
        catch (const EvaluationException& e){
            return new Porygon::EvaluateResult(e.what());
        }
    }

    bool HasVariable(Porygon::Script* script, const char16_t* key){
        return script->HasVariable(key);
    }

    const EvalValue* GetVariable(Porygon::Script* script, const char16_t* key){
        return script->GetVariable(key);
    }

    bool HasFunction(Porygon::Script* script, const char16_t* key){
        return script->HasFunction(key);
    }

    Porygon::EvaluateResult* CallFunction(Porygon::Script* script, const char16_t* key, EvalValue* parameters[], int parameterCount){
        try{
            std::vector<EvalValue*> v(parameters, parameters + parameterCount);
            auto result = script->CallFunction(key, v);
            return new Porygon::EvaluateResult(result);
        }
        catch (const EvaluationException& e){
            return new Porygon::EvaluateResult(e.what());
        }
    }

    const char * GetResultError(Porygon::EvaluateResult * result){
        return result->GetError();
    }
}