#ifndef AST_H
#define AST_H

#include "attachments.h"

#include <vector>
#include <stdlib.h>
#include <string>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <climits>
#include "utils.h"
#include <algorithm>

namespace llvm {
    class Value;
}

namespace xreate {

    struct String_t {
    };

    struct Identifier_t {
    };

    struct Number_t {
    };

    struct Type_t {
    };

    template<typename A>
    class Atom {
    };

    //DEBT hold for all atoms/identifiers Parser::Token data, like line:col position

    template<> class Atom<Identifier_t> {
    public:

        Atom(const std::wstring& value) {
            char buffer[32];
            wcstombs(buffer, value.c_str(), 32);

            __value = buffer;
        }

        Atom(std::string && name) : __value(name) {
        }

        const std::string& get() const {
            return __value;
        }
    private:
        std::string __value;
    };

    template<> class Atom<Number_t> {
    public:

        Atom(wchar_t* value) {
            __value = wcstol(value, 0, 10);
        }

        Atom(int value)
        : __value(value) {
        }

        double get()const {
            return __value;
        }
    private:
        double __value;
    };

    template<> class Atom<String_t> {
    public:

        Atom(const std::wstring& value) {
            assert(value.size());
            __value = std::string(++value.begin(), --value.end());
        }

        const std::string& get() const {
            return __value;
        }

    private:
        std::string __value;
    };

    enum class TypePrimitive {
        Bool, Num, Int, I32, I8, Float, String, 
    };

    template<> class Atom<Type_t> {
    public:

        Atom(wchar_t* value) {
            char buffer_[32];
            wcstombs(buffer_, value, 32);
            std::string buffer(buffer_);

            if (buffer == "bool") {
                __value = TypePrimitive::Bool;
            } else if (buffer == "num") {
                __value = TypePrimitive::Num;
            } else if (buffer == "int") {
                __value = TypePrimitive::Int;
            } else if (buffer == "i8") {
                __value = TypePrimitive::I8;
            } else if (buffer == "i32") {
                __value = TypePrimitive::I32;                
            } else if (buffer == "float") {
                __value = TypePrimitive::Float;

            } else if (buffer == "string") {
                __value = TypePrimitive::String;
            }
        }

        Atom() {
        }

        TypePrimitive get() const {
            return __value;
        }

    private:
        TypePrimitive __value;
    };

    typedef Atom<Type_t> TypeAtom;

    enum class TypeOperator {
        NONE, CALL, CUSTOM, VARIANT, ARRAY, TUPLE, STRUCT, ACCESS, LINK
    };

    struct llvm_array_tag {
    };

    struct struct_tag {
    };
    const llvm_array_tag tag_array = llvm_array_tag();
    const struct_tag tag_struct = struct_tag();

    class TypeAnnotation {
    public:
        TypeAnnotation();
        TypeAnnotation(const Atom<Type_t>& typ);
        TypeAnnotation(TypePrimitive typ);
        TypeAnnotation(llvm_array_tag, TypeAnnotation typ, int size);

        TypeAnnotation(TypeOperator op, std::initializer_list<TypeAnnotation> operands);
        TypeAnnotation(TypeOperator op, std::vector<TypeAnnotation>&& operands);
        void addBindings(std::vector<Atom<Identifier_t>>&& params);
        void addFields(std::vector<Atom<Identifier_t>>&& listFields);
        bool operator<(const TypeAnnotation& t) const;
        //   TypeAnnotation (struct_tag, std::initializer_list<TypePrimitive>);


        TypeOperator __operator = TypeOperator::NONE;

        std::vector<TypeAnnotation> __operands;
        TypePrimitive __value;
        std::string __valueCustom;
        int conjuctionId = -1; //conjunction point id (relevant for recursive types)

        uint64_t __size = 0;
        std::vector<std::string> fields;
        std::vector<std::string> bindings;
    private:
    };

    enum class Operator {
        ADD, SUB, MUL, DIV, EQU, NE, NEG, LSS, LSE, GTR, GTE, LIST, LIST_RANGE, LIST_NAMED, CALL, NONE, IMPL/* implication */, MAP, FOLD, FOLD_INF, LOOP_CONTEXT, INDEX, IF, SWITCH, SWITCH_ADHOC, CASE, CASE_DEFAULT, LOGIC_AND, ADHOC, CONTEXT_RULE, SEQUENCE
    };

    class Function;
    class AST;
    class CodeScope;
    class MetaRuleAbstract;

    template<class Target>
    struct ManagedPtr {

        static ManagedPtr<Target> Invalid() {
            return ManagedPtr<Target>();
        }

        ManagedPtr() : __storage(0) {
        }

        ManagedPtr(unsigned int id, const std::vector<Target*>* storage)
        : __id(id), __storage(storage) {
        }

        Target&
        operator*() const {
            assert(isValid() && "Invalid Ptr");
            return *__storage->at(__id);
        }

        void operator=(const ManagedPtr<Target>& other) {
            __id = other.__id;
            __storage = other.__storage;
        }

        bool
        operator==(const ManagedPtr<Target>& other) {
            return isValid() && (__id == other.__id);
        }

        Target*
        operator->() const noexcept {
            assert(isValid() && "Invalid Ptr");
            return __storage->at(__id);
        }

        inline bool isValid() const {
            return (__storage) && (0 <= __id) && (__id < __storage->size());
        }

        inline operator bool() const {
            return isValid();
        }

        ManagedPtr<Target>& operator++() {
            ++__id;
            return *this;
        }

        inline unsigned int id() const {
            return __id;
        }

    private:
        unsigned int __id = 0;
        const std::vector<Target*> * __storage = 0;
    };

    typedef ManagedPtr<Function> ManagedFnPtr;
    typedef ManagedPtr<CodeScope> ManagedScpPtr;
    typedef ManagedPtr<MetaRuleAbstract> ManagedRulePtr;
    const ManagedScpPtr NO_SCOPE = ManagedScpPtr(UINT_MAX, 0);

    //To update ExpressionHints in case of any changes 
    struct Expression {
        friend class CodeScope;
        friend class ClaspLayer;
        friend class CFAPass;
        friend class ExpressionHints;

        Expression(const Operator &oprt, std::initializer_list<Expression> params);
        Expression(const Atom<Identifier_t>& ident);
        Expression(const Atom<Number_t>& number);
        Expression(const Atom<String_t>& a);
        Expression();

        void setOp(Operator oprt);
        void addArg(Expression&& arg);
        void addBindings(std::initializer_list<Atom<Identifier_t>> params);
        void bindType(TypeAnnotation t);

        template<class InputIt>
        void addBindings(InputIt paramsBegin, InputIt paramsEnd);

        void addBlock(ManagedScpPtr scope);

        const std::vector<Expression>& getOperands() const;
        double getValueDouble() const;
        void setValueDouble(double value);
        const std::string& getValueString() const;
        void setValue(const Atom<Identifier_t>&& v);
        bool isValid() const;
        bool isDefined() const;
        

        bool operator==(const Expression& other) const;

        enum {
            INVALID, COMPOUND, IDENT, NUMBER, STRING, VARIANT, BINDING
        } __state = INVALID;
        Operator op;
        std::vector<std::string> bindings;
        std::map<std::string, size_t> __indexBindings;
        std::vector<Expression> operands;
        TypeAnnotation type;

        mutable std::map<std::string, Expression> tags;
        mutable Attachments tagsInternal;
        std::list<CodeScope*> blocks;

    private:
        std::string __valueS;
        double __valueD;
    };

    template<class InputIt>
    void Expression::addBindings(InputIt paramsBegin, InputIt paramsEnd) {
        size_t index = bindings.size();

        std::transform(paramsBegin, paramsEnd, std::inserter(bindings, bindings.end()),
                [&index, this] (const Atom<Identifier_t> atom) {
                    std::string key = atom.get();
                    this->__indexBindings[key] = index++;
                    return key;
                });
    }

    typedef std::list<Expression> ExpressionList;

    enum class TagModifier {
        NONE, ASSERT, REQUIRE
    };

    enum class DomainAnnotation {
        FUNCTION, VARIABLE
    };

    class RuleArguments : public std::vector<std::pair<std::string, DomainAnnotation>>
    {
        public:
        void add(const Atom<Identifier_t>& name, DomainAnnotation typ);
    };

    class RuleGuards : public std::vector<Expression> {
    public:
        void add(Expression&& e);
    };


    class ClaspLayer;
    class LLVMLayer;

    class MetaRuleAbstract {
    public:
        MetaRuleAbstract(RuleArguments&& args, RuleGuards&& guards);
        virtual ~MetaRuleAbstract();
        virtual void compile(ClaspLayer& layer) = 0;
    protected:
        RuleArguments __args;
        RuleGuards __guards;
    };

    class RuleWarning : public MetaRuleAbstract {
        friend class ClaspLayer;
    public:
        RuleWarning(RuleArguments&& args, RuleGuards&& guards, Expression&& condition, Atom<String_t>&& message);
        virtual void compile(ClaspLayer& layer);
        ~RuleWarning();

    private:
        std::string __message;
        Expression __condition;
    };

    typedef unsigned int VID;



    /*
    class Expression: ExpressionAbstract
    {
        friend class CFGPass;

    public:
        llvm::Value* compile(LLVMLayer& l, Function* f,  std::string* hintRetVar=0) const;
    };
     */



    typedef std::pair<Expression, TagModifier> Tag;

    struct Symbol {
        VID identifier;
        CodeScope * scope;
    };

    bool operator<(const Symbol& s1, const Symbol& s2);
    bool operator==(const Symbol& s1, const Symbol& s2);

    class CodeScope {
        friend class Function;
        friend class PassManager;

    public:
        CodeScope(CodeScope* parent = 0);
        void setBody(const Expression& body);
        void addDeclaration(Atom <Identifier_t>&& name, Expression&& body);
        void addBinding(Atom <Identifier_t>&& name, Expression&& argument);
        Symbol findSymbol(const std::string &name);
        static const Expression& findDeclaration(const Symbol& symbol);

        ~CodeScope();

        std::vector<std::string> __bindings;
        Expression __body; //TODO move __body to __declarations[0]

        std::map<std::string, VID> __identifiers;
        /**
         * definition of return type has variable index Zero(0)
         */
        //TODO move __definitions to SymbolsAttachments data
        std::unordered_map<VID, Expression> __declarations;
        std::vector<Expression> tags;
        std::vector<Expression> contextRules;
        
    private:
        VID __vCounter = 0;
        CodeScope* __parent;
        
        Symbol registerIdentifier(Atom <Identifier_t>&& name);
    };

    class Function {
        friend class Expression;
        friend class CodeScope;
        friend class AST;

    public:
        Function(const Atom<Identifier_t>& name);

        void addBinding(Atom <Identifier_t>&& name, Expression&& argument);
        void addTag(Expression&& tag, const TagModifier mod);

        void setReturnType(const TypeAnnotation& rtyp);

        const std::string& getName() const;
        const std::map<std::string, Expression>& getTags() const;
        CodeScope* getEntryScope() const;
        CodeScope* __entry;
        std::string __name;
        bool isPrefunction = false; //SECTIONTAG adhoc Function::isPrefunction flag

        Expression guardContext;
    private:

        std::map<std::string, Expression> __tags;
    };


    class ExternData;

    struct ExternEntry {
        std::string package;
        std::vector<std::string> headers;
    };

    typedef Expanded<TypeAnnotation> ExpandedType;

    enum ASTInterface {
        CFA, DFA, Extern, Adhoc
    };

    struct FunctionSpecialization {
        std::string guard;
        size_t id;
    };

    struct FunctionSpecializationQuery {
        std::unordered_set<std::string> context;
    };

    template<>
    struct AttachmentsStorage<Symbol> {

        static Attachments*
        get(const Symbol& s) {
            return &s.scope->findDeclaration(s).tagsInternal;
        }
    };

    template<>
    struct AttachmentsStorage<Expression> {

        static Attachments*
        get(const Expression& e) {
            return &e.tagsInternal;
        }
    };

    class AST {
    public:
        AST();

        //TASK extern and DFA interfaces move into addInterfaceData
        /**
         *  DFA Interface
         */
        void addDFAData(Expression&& data);

        /**
         * Extern Interface
         */
        void addExternData(ExternData&& data);

        void addInterfaceData(const ASTInterface& interface, Expression&& data);
        void add(Function* f);

        void add(MetaRuleAbstract* r);
        ManagedScpPtr add(CodeScope* scope);

        std::string getModuleName();
        ManagedPtr<Function> findFunction(const std::string& name);

        typedef std::multimap<std::string, unsigned int> FUNCTIONS_REGISTRY;
        std::list<ManagedFnPtr> getAllFunctions() const;
        std::list<ManagedFnPtr> getFunctionVariants(const std::string& name) const;


        template<class Target>
        ManagedPtr<Target> begin();

        std::vector<ExternEntry> __externdata;
        std::list<Expression> __dfadata; //TODO move to more appropriate place
        std::list<std::string> __rawImports; //TODO move to more appropriate place
        std::multimap<ASTInterface, Expression> __interfacesData; //TODO CFA data here.

    private:
        std::vector<MetaRuleAbstract*> __rules;
        std::vector<Function*> __functions;
        std::vector<CodeScope*> __scopes;

        FUNCTIONS_REGISTRY __indexFunctions;


        // ***** TYPES SECTION *****
    public:
        std::map<std::string, TypeAnnotation> __indexTypeAliases;
        ExpandedType expandType(const TypeAnnotation &t) const;
        ExpandedType findType(const std::string& name);
        void add(TypeAnnotation t, Atom<Identifier_t> alias);
        void recognizeVariantIdentifier(Expression& identifier);


    private:
        std::map<std::string, std::pair<TypeAnnotation, int>> __dictVariants;
        ExpandedType expandType(const TypeAnnotation &t, std::map<std::string, TypeAnnotation> scope,
                const std::vector<TypeAnnotation> &args = std::vector<TypeAnnotation>()) const;

        // ***** TYPES SECTION END *****
    };

    template<>
    ManagedPtr<Function>
    AST::begin<Function>();

    template<>
    ManagedPtr<CodeScope>
    AST::begin<CodeScope>();

    template<>
    ManagedPtr<MetaRuleAbstract>
    AST::begin<MetaRuleAbstract>();

}
#endif // AST_H
