#ifndef JINJA2CPP_SRC_STATEMENTS_H
#define JINJA2CPP_SRC_STATEMENTS_H

#include "renderer.h"
#include "expression_evaluator.h"

#include <string>
#include <vector>

namespace jinja2
{
class Statement : public VisitableRendererBase
{
public:
    VISITABLE_STATEMENT();
};

template<typename T = Statement>
using StatementPtr = std::shared_ptr<T>;

template<typename CharT>
class TemplateImpl;

struct MacroParam
{
    std::string paramName;
    ExpressionEvaluatorPtr<> defaultValue;
};
inline bool operator==(const MacroParam& lhs, const MacroParam& rhs)
{
    if (lhs.paramName != rhs.paramName)
        return false;
    if (lhs.defaultValue != rhs.defaultValue)
        return false;
    return true;
}

using MacroParams = std::vector<MacroParam>;

class ForStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    ForStatement(std::vector<std::string> vars, ExpressionEvaluatorPtr<> expr, ExpressionEvaluatorPtr<> ifExpr, bool isRecursive)
        : m_vars(std::move(vars))
        , m_value(expr)
        , m_ifExpr(ifExpr)
        , m_isRecursive(isRecursive)
    {
    }

    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }

    void SetElseBody(RendererPtr renderer)
    {
        m_elseBody = std::move(renderer);
    }

    void Render(OutStream& os, RenderContext& values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ForStatement*>(&other);
        if (!val)
            return false;
        if (m_vars != val->m_vars)
            return false;
        if (m_value != val->m_value)
            return false;
        if (m_ifExpr != val->m_ifExpr)
            return false;
        if (m_isRecursive != val->m_isRecursive)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        if (m_elseBody != val->m_elseBody)
            return false;
        return true;
    }

private:
  void RenderLoop(const InternalValue &loopVal, OutStream &os,
                  RenderContext &values, int level);
    ListAdapter CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const;

private:
    std::vector<std::string> m_vars;
    ExpressionEvaluatorPtr<> m_value;
    ExpressionEvaluatorPtr<> m_ifExpr;
    bool m_isRecursive{};
    RendererPtr m_mainBody;
    RendererPtr m_elseBody;
};

class ElseBranchStatement;

class IfStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    IfStatement(ExpressionEvaluatorPtr<> expr)
        : m_expr(expr)
    {
    }

    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }

    void AddElseBranch(StatementPtr<ElseBranchStatement> branch)
    {
        m_elseBranches.push_back(branch);
    }

    void Render(OutStream& os, RenderContext& values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const IfStatement*>(&other);
        if (!val)
            return false;
        if (m_expr != val->m_expr)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        if (m_elseBranches != val->m_elseBranches)
            return false;
        return true;
    }
private:
    ExpressionEvaluatorPtr<> m_expr;
    RendererPtr m_mainBody;
    std::vector<StatementPtr<ElseBranchStatement>> m_elseBranches;
};


class ElseBranchStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    ElseBranchStatement(ExpressionEvaluatorPtr<> expr)
        : m_expr(expr)
    {
    }

    bool ShouldRender(RenderContext& values) const;
    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }
    void Render(OutStream& os, RenderContext& values) override;
    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ElseBranchStatement*>(&other);
        if (!val)
            return false;
        if (m_expr != val->m_expr)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        return true;
    }

private:
    ExpressionEvaluatorPtr<> m_expr;
    RendererPtr m_mainBody;
};

class SetStatement : public Statement
{
public:
    SetStatement(std::vector<std::string> fields)
        : m_fields(std::move(fields))
    {
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const SetStatement*>(&other);
        if (!val)
            return false;
        if (m_fields != val->m_fields)
            return false;
        return true;
    }
protected:
    void AssignBody(InternalValue, RenderContext&);

private:
    const std::vector<std::string> m_fields;
};

class SetLineStatement final : public SetStatement
{
public:
    VISITABLE_STATEMENT();

    SetLineStatement(std::vector<std::string> fields, ExpressionEvaluatorPtr<> expr)
        : SetStatement(std::move(fields)), m_expr(std::move(expr))
    {
    }

    void Render(OutStream& os, RenderContext& values) override;

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

class SetBlockStatement : public SetStatement
{
public:
    using SetStatement::SetStatement;

    void SetBody(RendererPtr renderer)
    {
        m_body = std::move(renderer);
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const SetBlockStatement*>(&other);
        if (!val)
            return false;
        if (!SetStatement::IsEqual(*val))
            return false;
        if (m_body != val->m_body)
            return false;
        return true;
    }
protected:
    InternalValue RenderBody(RenderContext&);

private:
    RendererPtr m_body;
};

class SetRawBlockStatement final : public SetBlockStatement
{
public:
    VISITABLE_STATEMENT();

    using SetBlockStatement::SetBlockStatement;

    void Render(OutStream&, RenderContext&) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const SetRawBlockStatement*>(&other);
        if (!val)
            return false;
        if (!SetBlockStatement::IsEqual(*val))
            return false;
        return true;
    }
};

class SetFilteredBlockStatement final : public SetBlockStatement
{
public:
    VISITABLE_STATEMENT();

    explicit SetFilteredBlockStatement(std::vector<std::string> fields, ExpressionEvaluatorPtr<ExpressionFilter> expr)
        : SetBlockStatement(std::move(fields)), m_expr(std::move(expr))
    {
    }

    void Render(OutStream&, RenderContext&) override;

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

private:
    const ExpressionEvaluatorPtr<ExpressionFilter> m_expr;
};

class ParentBlockStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    ParentBlockStatement(std::string name, bool isScoped)
        : m_name(std::move(name))
        , m_isScoped(isScoped)
    {
    }

    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }
    void Render(OutStream &os, RenderContext &values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ParentBlockStatement*>(&other);
        if (!val)
            return false;
        if (m_name != val->m_name)
            return false;
        if (m_isScoped != val->m_isScoped)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        return true;
    }

private:
    std::string m_name;
    bool m_isScoped{};
    RendererPtr m_mainBody;
};

class BlockStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    BlockStatement(std::string name)
        : m_name(std::move(name))
    {
    }

    auto& GetName() const {return m_name;}

    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }
    void Render(OutStream &os, RenderContext &values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const BlockStatement*>(&other);
        if (!val)
            return false;
        if (m_name != val->m_name)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        return true;
    }
private:
    std::string m_name;
    RendererPtr m_mainBody;
};

class ExtendsStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    using BlocksCollection = std::unordered_map<std::string, StatementPtr<BlockStatement>>;

    ExtendsStatement(std::string name, bool isPath)
        : m_templateName(std::move(name))
        , m_isPath(isPath)
    {
    }

    void Render(OutStream &os, RenderContext &values) override;
    void AddBlock(StatementPtr<BlockStatement> block)
    {
        m_blocks[block->GetName()] = block;
    }
    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ExtendsStatement*>(&other);
        if (!val)
            return false;
        if (m_templateName != val->m_templateName)
            return false;
        if (m_isPath != val->m_isPath)
            return false;
        if (m_blocks != val->m_blocks)
            return false;
        return true;
    }
private:
    std::string m_templateName;
    bool m_isPath{};
    BlocksCollection m_blocks;
    void DoRender(OutStream &os, RenderContext &values);
};

class IncludeStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    IncludeStatement(bool ignoreMissing, bool withContext)
        : m_ignoreMissing(ignoreMissing)
        , m_withContext(withContext)
    {}

    void SetIncludeNamesExpr(ExpressionEvaluatorPtr<> expr)
    {
        m_expr = std::move(expr);
    }

    void Render(OutStream& os, RenderContext& values) override;
    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const IncludeStatement*>(&other);
        if (!val)
            return false;
        if (m_ignoreMissing != val->m_ignoreMissing)
            return false;
        if (m_withContext != val->m_withContext)
            return false;
        if (m_expr != val->m_expr)
            return false;
        return true;
    }
private:
    bool m_ignoreMissing{};
    bool m_withContext{};
    ExpressionEvaluatorPtr<> m_expr;
};

class ImportStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    explicit ImportStatement(bool withContext)
        : m_withContext(withContext)
    {}

    void SetImportNameExpr(ExpressionEvaluatorPtr<> expr)
    {
        m_nameExpr = std::move(expr);
    }

    void SetNamespace(std::string name)
    {
        m_namespace = std::move(name);
    }

    void AddNameToImport(std::string name, std::string alias)
    {
        m_namesToImport[std::move(name)] = std::move(alias);
    }

    void Render(OutStream& os, RenderContext& values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ImportStatement*>(&other);
        if (!val)
            return false;
        if (m_namespace != val->m_namespace)
            return false;
        if (m_withContext != val->m_withContext)
            return false;
        if (m_namesToImport != val->m_namesToImport)
            return false;
        if (m_nameExpr != val->m_nameExpr)
            return false;
        if (m_renderer != val->m_renderer)
            return false;
        return true;
    }
private:
    void ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const;

private:
    bool m_withContext{};
    RendererPtr m_renderer;
    ExpressionEvaluatorPtr<> m_nameExpr;
    std::optional<std::string> m_namespace;
    std::unordered_map<std::string, std::string> m_namesToImport;
};

class MacroStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    MacroStatement(std::string name, MacroParams params)
        : m_name(std::move(name))
        , m_params(std::move(params))
    {
    }

    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }

    void Render(OutStream &os, RenderContext &values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const MacroStatement*>(&other);
        if (!val)
            return false;
        if (m_name != val->m_name)
            return false;
        if (m_params != val->m_params)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        return true;
    }

protected:
    void InvokeMacroRenderer(const std::vector<ArgumentInfo>& params, const CallParams& callParams, OutStream& stream, RenderContext& context);
    void SetupCallArgs(const std::vector<ArgumentInfo>& argsInfo, const CallParams& callParams, RenderContext& context, InternalValueMap& callArgs, InternalValueMap& kwArgs, InternalValueList& varArgs);
    virtual void SetupMacroScope(InternalValueMap& scope);
    std::vector<ArgumentInfo> PrepareMacroParams(RenderContext& values);

protected:
    std::string m_name;
    MacroParams m_params;
    RendererPtr m_mainBody;
};

class MacroCallStatement : public MacroStatement
{
public:
    VISITABLE_STATEMENT();

    MacroCallStatement(std::string macroName, CallParamsInfo callParams, MacroParams callbackParams)
        : MacroStatement("$call$", std::move(callbackParams))
        , m_macroName(std::move(macroName))
        , m_callParams(std::move(callParams))
    {
    }

    void Render(OutStream &os, RenderContext &values) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const MacroCallStatement*>(&other);
        if (!val)
            return false;
        if (m_macroName != val->m_macroName)
            return false;
        if (m_callParams != val->m_callParams)
            return false;
        return true;
    }
protected:
    void SetupMacroScope(InternalValueMap& scope) override;

protected:
    std::string m_macroName;
    CallParamsInfo m_callParams;
};

class DoStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    DoStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) {}

    void Render(OutStream &os, RenderContext &values) override;
    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const DoStatement*>(&other);
        if (!val)
            return false;
        if (m_expr != val->m_expr)
            return false;
        return true;
    }
private:
    ExpressionEvaluatorPtr<> m_expr;
};

class WithStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    void SetScopeVars(std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars)
    {
        m_scopeVars = std::move(vars);
    }
    void SetMainBody(RendererPtr renderer)
    {
        m_mainBody = std::move(renderer);
    }

    void Render(OutStream &os, RenderContext &values) override;
    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const WithStatement*>(&other);
        if (!val)
            return false;
        if (m_scopeVars != val->m_scopeVars)
            return false;
        if (m_mainBody != val->m_mainBody)
            return false;
        return true;
    }
private:
    std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> m_scopeVars;
    RendererPtr m_mainBody;
};

class FilterStatement : public Statement
{
public:
    VISITABLE_STATEMENT();

    explicit FilterStatement(ExpressionEvaluatorPtr<ExpressionFilter> expr)
      : m_expr(std::move(expr)) {}

    void SetBody(RendererPtr renderer)
    {
        m_body = std::move(renderer);
    }

    void Render(OutStream &, RenderContext &) override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const FilterStatement*>(&other);
        if (!val)
            return false;
        if (m_expr != val->m_expr)
            return false;
        if (m_body != val->m_body)
            return false;
        return true;
    }
private:
    ExpressionEvaluatorPtr<ExpressionFilter> m_expr;
    RendererPtr m_body;
};

} // namespace jinja2

#endif // JINJA2CPP_SRC_STATEMENTS_H