#ifndef JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H
#define JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H

#include "internal_value.h"
#include "render_context.h"

#include <jinja2cpp/utils/i_comparable.h>

#include <memory>
#include <limits>

namespace jinja2
{

enum
{
    InvalidFn = -1,
    RangeFn = 1,
    LoopCycleFn = 2
};

class ExpressionEvaluatorBase : public IComparable
{
public:
    virtual ~ExpressionEvaluatorBase() {}

    virtual InternalValue Evaluate(RenderContext& values) = 0;
    virtual void Render(OutStream& stream, RenderContext& values);
};

template<typename T = ExpressionEvaluatorBase>
using ExpressionEvaluatorPtr = std::shared_ptr<T>;
using Expression = ExpressionEvaluatorBase;

inline bool operator==(const ExpressionEvaluatorPtr<>& lhs, const ExpressionEvaluatorPtr<>& rhs)
{
    if (lhs && rhs && !lhs->IsEqual(*rhs))
        return false;
    if ((lhs && !rhs) || (!lhs && rhs))
        return false;
    return true;
}
inline bool operator!=(const ExpressionEvaluatorPtr<>& lhs, const ExpressionEvaluatorPtr<>& rhs)
{
    return !(lhs == rhs);
}

struct CallParams
{
    std::unordered_map<std::string, InternalValue> kwParams;
    std::vector<InternalValue> posParams;
};

inline bool operator==(const CallParams& lhs, const CallParams& rhs)
{
    if (lhs.kwParams != rhs.kwParams)
        return false;
    if (lhs.posParams != rhs.posParams)
        return false;
    return true;
}

inline bool operator!=(const CallParams& lhs, const CallParams& rhs)
{
    return !(lhs == rhs);
}

struct CallParamsInfo
{
    std::unordered_map<std::string, ExpressionEvaluatorPtr<>> kwParams;
    std::vector<ExpressionEvaluatorPtr<>> posParams;
};

inline bool operator==(const CallParamsInfo& lhs, const CallParamsInfo& rhs)
{
    if (lhs.kwParams != rhs.kwParams)
        return false;
    if (lhs.posParams != rhs.posParams)
        return false;
    return true;
}

inline bool operator!=(const CallParamsInfo& lhs, const CallParamsInfo& rhs)
{
    return !(lhs == rhs);
}

struct ArgumentInfo
{
    std::string name;
    bool mandatory = false;
    InternalValue defaultVal;

    ArgumentInfo(std::string argName, bool isMandatory = false, InternalValue def = InternalValue())
        : name(std::move(argName))
        , mandatory(isMandatory)
        , defaultVal(std::move(def))
    {
    }
};

inline bool operator==(const ArgumentInfo& lhs, const ArgumentInfo& rhs)
{
    if (lhs.name != rhs.name)
        return false;
    if (lhs.mandatory != rhs.mandatory)
        return false;
    if (!(lhs.defaultVal == rhs.defaultVal))
        return false;
    return true;
}

inline bool operator!=(const ArgumentInfo& lhs, const ArgumentInfo& rhs)
{
    return !(lhs == rhs);
}

struct ParsedArgumentsInfo
{
    std::unordered_map<std::string, ExpressionEvaluatorPtr<>> args;
    std::unordered_map<std::string, ExpressionEvaluatorPtr<>> extraKwArgs;
    std::vector<ExpressionEvaluatorPtr<>> extraPosArgs;

    ExpressionEvaluatorPtr<> operator[](const std::string& name) const
    {
        auto p = args.find(name);
        if (p == args.end())
            return ExpressionEvaluatorPtr<>();

        return p->second;
    }
};

inline bool operator==(const ParsedArgumentsInfo& lhs, const ParsedArgumentsInfo& rhs)
{
    if (lhs.args != rhs.args)
        return false;
    if (lhs.extraKwArgs != rhs.extraKwArgs)
        return false;
    if (lhs.extraPosArgs != rhs.extraPosArgs)
        return false;
    return true;
}

inline bool operator!=(const ParsedArgumentsInfo& lhs, const ParsedArgumentsInfo& rhs)
{
    return !(lhs == rhs);
}

struct ParsedArguments
{
    std::unordered_map<std::string, InternalValue> args;
    std::unordered_map<std::string, InternalValue> extraKwArgs;
    std::vector<InternalValue> extraPosArgs;

    InternalValue operator[](const std::string& name) const
    {
        auto p = args.find(name);
        if (p == args.end())
            return InternalValue();

        return p->second;
    }
};

inline bool operator==(const ParsedArguments& lhs, const ParsedArguments& rhs)
{
    if (lhs.args != rhs.args)
        return false;
    if (lhs.extraKwArgs != rhs.extraKwArgs)
        return false;
    if (lhs.extraPosArgs != rhs.extraPosArgs)
        return false;
    return true;
}

inline bool operator!=(const ParsedArguments& lhs, const ParsedArguments& rhs)
{
    return !(lhs == rhs);
}

class ExpressionFilter;
class IfExpression;

class FullExpressionEvaluator : public ExpressionEvaluatorBase
{
public:
    void SetExpression(ExpressionEvaluatorPtr<Expression> expr)
    {
        m_expression = std::move(expr);
    }
    void SetTester(ExpressionEvaluatorPtr<IfExpression> expr)
    {
        m_tester = std::move(expr);
    }
    InternalValue Evaluate(RenderContext& values) override;
    void Render(OutStream &stream, RenderContext &values) override;

    bool IsEqual(const IComparable& other) const override
    {
        const auto* eval = dynamic_cast<const FullExpressionEvaluator*>(&other);
        if (!eval)
            return false;
        if (m_expression != eval->m_expression)
            return false;
        if (m_tester != eval->m_tester)
            return false;
        return true;
    }
private:
    ExpressionEvaluatorPtr<Expression> m_expression;
    ExpressionEvaluatorPtr<IfExpression> m_tester;
};

class ValueRefExpression : public Expression
{
public:
    ValueRefExpression(std::string valueName)
        : m_valueName(std::move(valueName))
    {
    }
    InternalValue Evaluate(RenderContext& values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* value = dynamic_cast<const ValueRefExpression*>(&other);
        if (!value)
            return false;
        return m_valueName != value->m_valueName;
    }
private:
    std::string m_valueName;
};

class SubscriptExpression : public Expression
{
public:
    SubscriptExpression(ExpressionEvaluatorPtr<Expression> value)
        : m_value(value)
    {
    }
    InternalValue Evaluate(RenderContext& values) override;
    void AddIndex(ExpressionEvaluatorPtr<Expression> value)
    {
        m_subscriptExprs.push_back(value);
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* otherPtr = dynamic_cast<const SubscriptExpression*>(&other);
        if (!otherPtr)
            return false;
        if (m_value != otherPtr->m_value)
            return false;
        if (m_subscriptExprs != otherPtr->m_subscriptExprs)
            return false;
        return true;
    }

private:
    ExpressionEvaluatorPtr<Expression> m_value;
    std::vector<ExpressionEvaluatorPtr<Expression>> m_subscriptExprs;
};

class FilteredExpression : public Expression
{
public:
    explicit FilteredExpression(ExpressionEvaluatorPtr<Expression> expression, ExpressionEvaluatorPtr<ExpressionFilter> filter)
        : m_expression(std::move(expression))
        , m_filter(std::move(filter))
    {
    }
    InternalValue Evaluate(RenderContext&) override;
    bool IsEqual(const IComparable& other) const override
    {
        auto* otherPtr = dynamic_cast<const FilteredExpression*>(&other);
        if (!otherPtr)
            return false;
        if (m_expression != otherPtr->m_expression)
            return false;
        if (m_filter != otherPtr->m_filter)
            return false;
        return true;
    }

private:
    ExpressionEvaluatorPtr<Expression> m_expression;
    ExpressionEvaluatorPtr<ExpressionFilter> m_filter;
};

class ConstantExpression : public Expression
{
public:
    ConstantExpression(InternalValue constant)
        : m_constant(constant)
    {}
    InternalValue Evaluate(RenderContext&) override
    {
        return m_constant;
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* otherVal = dynamic_cast<const ConstantExpression*>(&other);
        if (!otherVal)
            return false;
        return m_constant == otherVal->m_constant;
    }
private:
    InternalValue m_constant;
};

class TupleCreator : public Expression
{
public:
    TupleCreator(std::vector<ExpressionEvaluatorPtr<>> exprs)
        : m_exprs(std::move(exprs))
    {
    }

    InternalValue Evaluate(RenderContext&) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const TupleCreator*>(&other);
        if (!val)
            return false;
        return m_exprs == val->m_exprs;
    }
private:
    std::vector<ExpressionEvaluatorPtr<>> m_exprs;
};
/*
class DictionaryCreator : public Expression
{
public:
    DictionaryCreator(std::unordered_map<std::string, ExpressionEvaluatorPtr<>> items)
        : m_items(std::move(items))
    {
    }

    InternalValue Evaluate(RenderContext&) override;

private:
    std::unordered_map<std::string, ExpressionEvaluatorPtr<>> m_items;
};*/

class DictCreator : public Expression
{
public:
    DictCreator(std::unordered_map<std::string, ExpressionEvaluatorPtr<>> exprs)
        : m_exprs(std::move(exprs))
    {
    }

    InternalValue Evaluate(RenderContext&) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const DictCreator*>(&other);
        if (!val)
            return false;
        return m_exprs == val->m_exprs;
    }
private:
    std::unordered_map<std::string, ExpressionEvaluatorPtr<>> m_exprs;
};

class UnaryExpression : public Expression
{
public:
    enum Operation
    {
        LogicalNot,
        UnaryPlus,
        UnaryMinus
    };

    UnaryExpression(Operation oper, ExpressionEvaluatorPtr<> expr)
        : m_oper(oper)
        , m_expr(expr)
    {}
    InternalValue Evaluate(RenderContext&) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const UnaryExpression*>(&other);
        if (!val)
            return false;
        if (m_oper != val->m_oper)
            return false;
        if (m_expr != val->m_expr)
            return false;
        return true;
    }

private:
    Operation m_oper;
    ExpressionEvaluatorPtr<> m_expr;
};

class IsExpression : public Expression
{
public:
    virtual ~IsExpression() {}

    struct ITester : IComparable
    {
        virtual ~ITester() {}
        virtual bool Test(const InternalValue& baseVal, RenderContext& context) = 0;
    };
    using TesterPtr = std::shared_ptr<ITester>;
    using TesterFactoryFn = std::function<TesterPtr(CallParamsInfo params)>;

    IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParamsInfo params);
    InternalValue Evaluate(RenderContext& context) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const IsExpression*>(&other);
        if (!val)
            return false;
        if (m_value != val->m_value)
            return false;
        if (m_tester != val->m_tester)
            return false;
        if (m_tester && val->m_tester && !m_tester->IsEqual(*val->m_tester))
            return false;
        return true;
    }

private:
    ExpressionEvaluatorPtr<> m_value;
    TesterPtr m_tester;
};

class BinaryExpression : public Expression
{
public:
    enum Operation
    {
        LogicalAnd,
        LogicalOr,
        LogicalEq,
        LogicalNe,
        LogicalGt,
        LogicalLt,
        LogicalGe,
        LogicalLe,
        In,
        Plus,
        Minus,
        Mul,
        Div,
        DivReminder,
        DivInteger,
        Pow,
        StringConcat
    };

    enum CompareType
    {
        Undefined = 0,
        CaseSensitive = 0,
        CaseInsensitive = 1
    };

    BinaryExpression(Operation oper, ExpressionEvaluatorPtr<> leftExpr, ExpressionEvaluatorPtr<> rightExpr);
    InternalValue Evaluate(RenderContext&) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const BinaryExpression*>(&other);
        if (!val)
            return false;
        if (m_oper != val->m_oper)
            return false;
        if (m_leftExpr != val->m_leftExpr)
            return false;
        if (m_rightExpr != val->m_rightExpr)
            return false;
        if (m_inTester && val->m_inTester && !m_inTester->IsEqual(*val->m_inTester))
            return false;
        if ((!m_inTester && val->m_inTester) || (m_inTester && !val->m_inTester))
            return false;
        return true;
    }
private:
    Operation m_oper;
    ExpressionEvaluatorPtr<> m_leftExpr;
    ExpressionEvaluatorPtr<> m_rightExpr;
    IsExpression::TesterPtr m_inTester;
};


class CallExpression : public Expression
{
public:
    virtual ~CallExpression() {}

    CallExpression(ExpressionEvaluatorPtr<> valueRef, CallParamsInfo params)
        : m_valueRef(std::move(valueRef))
        , m_params(std::move(params))
    {
    }

    InternalValue Evaluate(RenderContext &values) override;
    void Render(OutStream &stream, RenderContext &values) override;

    auto& GetValueRef() const {return m_valueRef;}
    auto& GetParams() const {return m_params;}

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const CallExpression*>(&other);
        if (!val)
            return false;
        if (m_valueRef != val->m_valueRef)
            return false;
        return m_params == val->m_params;
    }
private:
    InternalValue CallArbitraryFn(RenderContext &values);
    InternalValue CallGlobalRange(RenderContext &values);
    InternalValue CallLoopCycle(RenderContext &values);

private:
    ExpressionEvaluatorPtr<> m_valueRef;
    CallParamsInfo m_params;
};

class ExpressionFilter : public IComparable
{
public:
    virtual ~ExpressionFilter() {}

    struct IExpressionFilter : IComparable
    {
        virtual ~IExpressionFilter() {}
        virtual InternalValue Filter(const InternalValue& baseVal, RenderContext& context) = 0;
    };
    using ExpressionFilterPtr = std::shared_ptr<IExpressionFilter>;
    using FilterFactoryFn = std::function<ExpressionFilterPtr(CallParamsInfo params)>;

    ExpressionFilter(const std::string& filterName, CallParamsInfo params);

    InternalValue Evaluate(const InternalValue& baseVal, RenderContext& context);
    void SetParentFilter(std::shared_ptr<ExpressionFilter> parentFilter)
    {
        m_parentFilter = std::move(parentFilter);
    }
    bool IsEqual(const IComparable& other) const override
    {
        auto* valuePtr = dynamic_cast<const ExpressionFilter*>(&other);
        if (!valuePtr)
            return false;
        if (m_filter && valuePtr->m_filter && !m_filter->IsEqual(*valuePtr->m_filter))
            return false;
        if ((m_filter && !valuePtr->m_filter) || (!m_filter && !valuePtr->m_filter))
            return false;
        if (m_parentFilter != valuePtr->m_parentFilter)
            return false;
        return true;
    }


private:
    ExpressionFilterPtr m_filter;
    std::shared_ptr<ExpressionFilter> m_parentFilter;
};


class IfExpression : public IComparable
{
public:
    virtual ~IfExpression() {}

    IfExpression(ExpressionEvaluatorPtr<> testExpr, ExpressionEvaluatorPtr<> altValue)
        : m_testExpr(testExpr)
        , m_altValue(altValue)
    {
    }

    bool Evaluate(RenderContext& context);
    InternalValue EvaluateAltValue(RenderContext& context);

    void SetAltValue(ExpressionEvaluatorPtr<> altValue)
    {
        m_altValue = std::move(altValue);
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* valPtr = dynamic_cast<const IfExpression*>(&other);
        if (!valPtr)
            return false;
        if (m_testExpr != valPtr->m_testExpr)
            return false;
        if (m_altValue != valPtr->m_altValue)
            return false;
        return true;
    }

private:
    ExpressionEvaluatorPtr<> m_testExpr;
    ExpressionEvaluatorPtr<> m_altValue;
};

namespace helpers
{
ParsedArguments ParseCallParams(const std::initializer_list<ArgumentInfo>& argsInfo, const CallParams& params, bool& isSucceeded);
ParsedArguments ParseCallParams(const std::vector<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded);
ParsedArgumentsInfo ParseCallParamsInfo(const std::initializer_list<ArgumentInfo>& argsInfo, const CallParamsInfo& params, bool& isSucceeded);
ParsedArgumentsInfo ParseCallParamsInfo(const std::vector<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded);
CallParams EvaluateCallParams(const CallParamsInfo& info, RenderContext& context);
} // namespace helpers
} // namespace jinja2

#endif // JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H