
#ifndef PORYGONLANG_STATICSCOPE_HPP
#define PORYGONLANG_STATICSCOPE_HPP

#include <map>
#include <utility>
#include "BasicLibrary.hpp"
#include "MathLibrary.hpp"
#include "../Utilities/HashedString.hpp"
#include "../Binder/BoundVariables/BoundVariable.hpp"
#include "../Evaluator/EvalValues/EvalValue.hpp"
#include "../UserData/UserDataStorage.hpp"
#include "../UserData/UserDataValue.hpp"

using namespace std;
namespace Porygon::StandardLibraries{
    /*!
    \class StaticScope
    \brief The static shared scope for all scripts. Variables registered in here should be stateless.
    */
    class StaticScope {
        class InternalScope{
        public:
            map<Utilities::HashedString, Binder::BoundVariable *> _boundVariables;
            map<Utilities::HashedString, Evaluation::EvalValue*> _variables;

            InternalScope(){
                BasicLibrary::RegisterVariables(&_boundVariables, &_variables);
                auto mathData = MathLibrary::CreateUserData();
                UserData::UserDataStorage::RegisterType(Utilities::HashedString::ConstHash("__math__"), mathData);
                auto mathHash = Utilities::HashedString(new u16string(u"math"));
                _boundVariables.insert({mathHash, new Binder::BoundVariable(make_shared<UserData::UserDataScriptType>(mathData))});
                _variables.insert({mathHash, new UserData::UserDataValue(mathData, nullptr)});
            }

            ~InternalScope(){
                for (const auto& b: _boundVariables){
                    delete b.second;
                }
                for (const auto& v: _variables){
                    delete v.second;
                }
            }
        };

        static InternalScope* _internal;

        inline static InternalScope* GetScope(){
            if (!_internal)
                _internal = new InternalScope();
            return _internal;
        }
    public:

        ~StaticScope(){
            delete _internal;
        }


        static void RegisterVariable(const Utilities::HashedString& identifier, shared_ptr<ScriptType> type, Evaluation::EvalValue* value){
            GetScope()->_boundVariables.insert({identifier, new Binder::BoundVariable(std::move(type))});
            GetScope()->_variables.insert({identifier, value});
        }

        static Binder::BoundVariable* GetBoundVariable(const Utilities::HashedString &identifier){
            auto found = GetScope()->_boundVariables.find(identifier);
            if (found != GetScope()->_boundVariables.end()) {
                return found->second;
            }
            return nullptr;
        }

        inline static Evaluation::EvalValuePointer GetVariable(const Utilities::HashedString &identifier){
            return GetScope()->_variables[identifier]->Clone();
        }
    };
}


#endif //PORYGONLANG_STATICSCOPE_HPP
