
#ifndef PORYGONLANG_FUNCTIONSCRIPTTYPE_HPP
#define PORYGONLANG_FUNCTIONSCRIPTTYPE_HPP

#include <utility>
#include "ScriptType.hpp"

namespace Porygon {
    class GenericFunctionOption{
        shared_ptr<const ScriptType> _returnType;
        vector<shared_ptr<ScriptType>> _parameterTypes;
        size_t _option = 0;
    public:
        GenericFunctionOption(shared_ptr<ScriptType> returnType, vector<shared_ptr<ScriptType>> parameterTypes)
            : _returnType(std::move(returnType)), _parameterTypes(std::move(parameterTypes)){
        }

        virtual ~GenericFunctionOption() = default;

        [[nodiscard]] inline shared_ptr<const ScriptType> GetReturnType() const {
            return _returnType;
        }

        inline void SetOption(size_t v){
            _option = v;
        }

        inline void SetReturnType(const shared_ptr<const ScriptType>& t) {
            _returnType = t;
        }

        [[nodiscard]] inline vector<shared_ptr<ScriptType>> GetParameterTypes() const {
            return _parameterTypes;
        }

        bool IsValid(const vector<shared_ptr<const ScriptType>>& parameters){
            if (parameters.size() != _parameterTypes.size()){
                return false;
            }
            for (size_t i = 0; i < parameters.size(); i++){
                if (_parameterTypes[i]->GetClass() == TypeClass::All)
                    continue;
                if (parameters[i]->operator!=(_parameterTypes[i].get())){
                    return false;
                }
            }
            return true;
        }

        [[nodiscard]] inline size_t GetOptionId() const{
            return _option;
        }

        [[nodiscard]] virtual bool IsScriptFunction() const = 0;
    };

    class GenericFunctionScriptType : public Porygon::ScriptType {
        vector<GenericFunctionOption *>* _options = new vector<GenericFunctionOption *>;
    public:
        GenericFunctionScriptType()
                : ScriptType(Porygon::TypeClass::Function){};

        explicit GenericFunctionScriptType(GenericFunctionOption * option)
                : ScriptType(Porygon::TypeClass::Function){
            this -> RegisterFunctionOption(option);
        };

        explicit GenericFunctionScriptType(const vector<GenericFunctionOption*>& options)
                : ScriptType(Porygon::TypeClass::Function){
            for (auto option: options){
                this -> RegisterFunctionOption(option);
            }
        };

        ~GenericFunctionScriptType() final{
            for (auto o: *_options){
                delete o;
            }
            delete _options;
        }

        const GenericFunctionScriptType* RegisterFunctionOption (GenericFunctionOption * opt) const{
            opt->SetOption(_options->size());
            _options->push_back(opt);
            return this;
        }

        const GenericFunctionScriptType* RegisterFunctionOption (size_t index, GenericFunctionOption * opt) const{
            opt->SetOption(index);
            _options->push_back(opt);
            return this;
        }

        GenericFunctionOption* GetFunctionOption(const vector<shared_ptr<const ScriptType>>& parameters) const{
            for (auto o: *_options){
                if (o->IsValid(parameters)){
                    return o;
                }
            }
            return nullptr;
        }

        [[nodiscard]] inline GenericFunctionOption* GetFirstOption() const{
            return _options->at(0);
        }
    };

    class ScriptFunctionOption : public GenericFunctionOption {
        vector<shared_ptr<const Porygon::Binder::BoundVariableKey>> _parameterKeys;
    public:
        ScriptFunctionOption(shared_ptr<ScriptType> returnType, vector<shared_ptr<ScriptType>> parameterTypes,
                           vector<shared_ptr<const Porygon::Binder::BoundVariableKey>> parameterKeys)
                : GenericFunctionOption(move(returnType), std::move(parameterTypes)), _parameterKeys(move(parameterKeys)) {
        }

        [[nodiscard]] inline vector<shared_ptr<const Porygon::Binder::BoundVariableKey>> GetParameterKeys() const {
            return _parameterKeys;
        }

        [[nodiscard]] inline bool IsScriptFunction() const final {
            return true;
        }
    };
}

#include "ScriptType.hpp"

#endif //PORYGONLANG_FUNCTIONSCRIPTTYPE_HPP
