#ifndef AST_H
#define AST_H

#include <vector>
#include <stdlib.h>
#include <string>
#include "llvmlayer.h"

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[1000];
        wcstombs(buffer, value.c_str(), 1000);

        __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);
    }
    int get()const {return __value; }
private:
   double __value;
};

enum TimePrimitive {Bool, Int, Float, Num, String};

template<> class Atom<Type_t>
{
public:
    Atom(wchar_t* value)
    {
        char buffer_[1000];
        wcstombs(buffer_, value, 1000);
        std::string buffer(buffer_);

        if (buffer=="bool"){
            __value = Bool;
        } else if (buffer=="int") {
            __value = Int;
        } else if (buffer=="float") {
            __value = Float;
        } else if (buffer=="num") {
            __value = Num;
        } else if (buffer=="string") {
            __value = String;
        }
    }

    Atom ()
    {
    }

    TimePrimitive get() const
    {
        return __value;
    }

private:
    TimePrimitive __value;
};

typedef Atom<Type_t> TypeAtom;


enum class TypeOperator{LIST};
class TypeAnnotation
{
public:
    TypeAnnotation (const Atom<Type_t>& typ);
    TypeAnnotation (const TypeOperator& op, const TypeAnnotation& typ);

    TypeAnnotation();

    llvm::Type* toLLVMType();

private:
    TimePrimitive __value;
};


enum class Operator
{
ADD, SUB, MUL, DIV, EQU, LSS, GTR, NEG, LIST, CALL, NONE
};


class Function;
class AST;

typedef unsigned int vid;

class Expression
{
public:
    Expression(const Operator& op, const Expression& arg);
    Expression(const Atom<Identifier_t>& ident);
    Expression(const Atom<Number_t>& number);
    Expression();

    void addArg(const Expression& arg);
    llvm::Value* compile(LLVMLayer& l, Function* f,  std::string* retVarHint=0);

private:
    Operator __op;
    std::vector<Expression> operands;

    std::string __valueS;
    double __valueD;

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



typedef std::pair<vid, TypeAnnotation> VariableDefinition;
typedef std::pair<vid, Expression> VariableDeclaration;
class Function
{
    friend class Expression;
public:
    Function(const std::wstring& name);
    void setReturnType(const TypeAnnotation& rtyp);
    void addArg(const std::wstring& vname, const TypeAnnotation& typ);
    void setBody(const Expression& body);

    void addDeclaration(const std::wstring& vname, const TypeAnnotation& typ, const Expression& e);
    void addListDeclaration(const std::wstring& vname, const TypeAnnotation& typ, const Expression& e);

    const std::string& getName() const;
    llvm::Function* compile(LLVMLayer& l);

private:
    AST* root;
    std::string __name;
    TypeAnnotation __retType;
    std::vector<std::string> __args;
    std::map<VariableDefinition::first_type, VariableDefinition::second_type> __definitions;
    std::map<VariableDeclaration::first_type, VariableDeclaration::second_type> __declarations;
    std::map<std::string, vid> __vartable;
    Expression __body;

    llvm::Function* __raw;
    std::map<vid,llvm::Value*> __rawVars;

    vid __vCounter;

    vid registerVar(const std::string& vname);
};

class AST
{
    friend class Expression;
public:
    AST();
    void add(const Function& f);

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

    std::string getModuleName();
private:
    std::vector<Function> __functions;

    std::map<std::string, llvm::Function*> __rawFunctions;
};


#endif // AST_H
