#ifndef AST_H
#define AST_H

#include <vector>
#include <stdlib.h>
#include <string>
#include "llvmlayer.h"
#include <list>
#include <unordered_map>
#include <unordered_set>

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

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

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, LIST, STRUCT};
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, TypePrimitive typ, int size);

    TypeAnnotation (TypeOperator op, std::initializer_list<TypeAnnotation> operands);
 //   TypeAnnotation (struct_tag, std::initializer_list<TypePrimitive>);

    llvm::Type* toLLVMType();

private:
    TypePrimitive __value;
    TypeOperator __operator = TypeOperator::NONE;
    std::vector<TypeAnnotation> __operands;
    int __size = 0;
};

enum class Operator
{
ADD, SUB, MUL, DIV, EQU, LSS, GTR, NEG, LIST, CALL, NONE, IMPL, LOOP, INDEX/* implication */
};

class Function;
class AST;

class CodeScope;
class Expression
{
    friend class CodeScope;
    friend class ClaspLayer;
    friend class CFGPass;
public:
    Expression(const Operator &op, std::initializer_list<Expression> params);
    Expression(const Atom<Identifier_t>& ident);
    Expression(const Atom<Number_t>& number);
    Expression();

    void setOp(Operator op);
    void addArg(Expression&& arg);
    void addBindings(std::initializer_list<Atom<Identifier_t>> params);
    void addBlock(CodeScope&& scope);

    const std::vector<Expression>& getOperands() const;
    double getValueDouble() const;
    const std::string& getValueString() const;

private:
    Operator __op ;
    std::vector<Expression> operands;
    std::vector<Atom<Identifier_t>> bindings;
    std::vector<CodeScope> blocks;

    std::string __valueS;
    double __valueD;

    enum {INVALID, COMPOUND, IDENT, NUMBER, STRING} __state;
};

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

class CodeScope
{
    friend class Function;
    friend class CFGPass;

public:
    CodeScope();
    void setBody(const Expression& body);
    void addDeclaration(const Atom <Identifier_t> &&name, TypeAnnotation &&typ, Expression&& body);

    void addArg(Atom <Identifier_t>&& name, TypeAnnotation&& typ);
    void bindArg(llvm::Value* var, std::string&& name);
    llvm::Value* compile(LLVMLayer &l, const std::string * const hintBlockName=0);
    llvm::Value* findSymbol(const std::string &name, LLVMLayer &l);

private:
    std::unordered_map<VariableDefinition::first_type, VariableDefinition::second_type> __definitions;
    std::unordered_map<VariableDeclaration::first_type, VariableDeclaration::second_type> __declarations;
    std::map<std::string, VID> __vartable;
    std::vector<std::string> __args;

    Expression __body;
    VID __vCounter=0;

    std::map<VID,llvm::Value*> __rawVars;
    CodeScope * __parent=0;

    VID registerVar(std::string&& name, TypeAnnotation &&typ);
    llvm::Value* compileExpression(const Expression& expr, LLVMLayer& l, const std::string* const hintRetVar = 0);
};

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;

    llvm::Function* compile(LLVMLayer& l);
    const std::vector<Tag>& getAnnotations() const;
    const CodeScope& getEntryScope() const;
    CodeScope& getEntryScope();
    void setEntryScope(CodeScope&& scope);
private:

    CodeScope __entry;
    std::string __name;
    TypeAnnotation __retType;
    std::vector<Tag>  __tags;

    llvm::Function* __raw;

};

typedef unsigned int FID;
class AST
{
    friend class CodeScope;
    friend class CFGPass;
    friend class RulesPass;

public:
    AST();
    void add(Function& f);
    void add(MetaRuleAbstract* r);

    void compile(LLVMLayer& l);
    void run(LLVMLayer& l);

    std::string getModuleName();

    FID getFunctionsCount() const;
    const Function& getFunctionById(FID id) const;
private:
    std::list<std::unique_ptr<MetaRuleAbstract>> __rules;
    std::vector<Function> __functions;
    std::map<std::string, FID> __indexFunctions;
};
}


#endif // AST_H
