#ifndef AST_H
#define AST_H

#include <vector>
#include <stdlib.h>
#include <string>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <climits>
#include "attachments.h"
#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 {};

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)
        : __value(++value.begin(), --value.end())
    {}

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

private:
    std::string __value;
};

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

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=="int") {
            __value = TypePrimitive::Int;
        } else if (buffer=="float") {
            __value = TypePrimitive::Float;
        } else if (buffer=="num") {
            __value = TypePrimitive::Num;
        } 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, 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);
    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, LSS, GTR, NEG, LIST, LIST_RANGE, LIST_NAMED, CALL, NONE, IMPL/* implication */, MAP, FOLD, INDEX, IF, SWITCH, CASE, LOGIC_AND
};

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()
    {
        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);

struct Expression
{
    friend class CodeScope;
    friend class ClaspLayer;
    friend class CFGPass;

    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;
    const std::string& getValueString() const;
    void setValue(const Atom<Identifier_t>&& v);
    bool isNone() const;
    bool isValid() const;

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

    std::map<std::string, Expression> tags;
    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<VID, TypeAnnotation> VariableDefinition;
typedef std::pair<VID, Expression> VariableDeclaration;

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


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

struct SymbolTags_t{};

template<>
struct AttachmentsDict<SymbolTags_t> {
    typedef std::map<std::string, Expression> Data;
    static const unsigned int key = 2;
};

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(const Atom <Identifier_t> &&name, TypeAnnotation &&typ, Expression&& body);

    void addArg(Atom <Identifier_t>&& name, TypeAnnotation&& typ);


        //TODO exclude forceCompile partz
    Symbol findSymbol(const std::string &name);
    static const Expression& findDeclaration(const Symbol& symbol);
    static TypeAnnotation& findDefinition(const Symbol& symbol);
    static bool hasDeclaration(const Symbol& symbol);

    ~CodeScope();

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

    SymbolAttachments attachments;
    std::map<std::string, VID> __vartable;
    /**
    * definition of return type has variable index Zero(0)
    */
    //TODO move __definitions to SymbolsAttachments data
    std::unordered_map<VariableDefinition::first_type, VariableDefinition::second_type> __definitions;
    std::unordered_map<VariableDeclaration::first_type, VariableDeclaration::second_type> __declarations;
protected:



    VID __vCounter=1;
    CodeScope* __parent;
    std::list<CodeScope*> __storage;
    VID registerVar(std::string&& name, TypeAnnotation &&typ);
};

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

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

    void addArg(Atom <Identifier_t>&& name, TypeAnnotation&& typ);
    void addTag(Expression&& tag, const TagModifier mod);

    void setReturnType(const TypeAnnotation& rtyp);

    const std::string& getName() const;
    const std::vector<Tag>& getAnnotations() const;
    CodeScope* getEntryScope() const;
    CodeScope* __entry;
    std::string __name;
private:

    std::vector<Tag>  __tags;
};


class ExternData;

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

typedef Expanded<TypeAnnotation> ExpandedType;

enum ASTInterface {
	CFA, DFA, Extern
};

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);


    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;

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

    std::map<std::string, unsigned int> __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);

	private:
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
