#ifndef MANDEL_NAIVEIRGENERATOR_H
#define MANDEL_NAIVEIRGENERATOR_H

#include "IterationIR.h"
#include "Generators.h"
#include <memory>
#include <variant>

namespace mnd
{
    template<typename T>
    class NaiveIRGenerator;

    namespace eval
    {
        struct Load;
        struct Store;
        struct Add;
        struct Sub;
        struct Mul;
        struct Div;
        struct Neg;    
        struct Atan2;
        struct Pow;
        struct Cos;
        struct Sin;
        struct Exp;
        struct Ln;

        using EvalNode = std::variant<
            Load,
            Store,
            Add,
            Sub,
            Mul,
            Div,
            Neg,
            Atan2,
            Pow,
            Cos,
            Sin,
            Exp,
            Ln
        >;


        struct Load { size_t index; };
        struct Store
        {
            size_t index;
            std::unique_ptr<EvalNode> v;
        };
    
        struct BinaryOperation
        {
            std::unique_ptr<EvalNode> a;
            std::unique_ptr<EvalNode> b;
        };
    
        struct UnaryOperation
        {
            std::unique_ptr<EvalNode> a;
        };
    
        struct Add : BinaryOperation {};
        struct Sub : BinaryOperation {};
        struct Mul : BinaryOperation {};
        struct Div : BinaryOperation {};

        struct Neg : UnaryOperation {};


        struct Atan2 : BinaryOperation {};
        struct Pow : BinaryOperation {};
        struct Cos : UnaryOperation {};
        struct Sin : UnaryOperation {};
        struct Exp : UnaryOperation {};
        struct Ln : UnaryOperation {};



        template<typename T>
        struct EvalStruct
        {
            std::map<std::string, size_t> variableNames;
            std::vector<T*> variables;
            std::vector<T> temporaries;

            void prepare(T* zre, T* zim, T* cre, T* cim)
            {
                auto z_re = variableNames.find("z_re");
                auto z_im = variableNames.find("z_im");
                auto c_re = variableNames.find("c_re");
                auto c_im = variableNames.find("c_im");
                if (z_re != variableNames.end())
                    variables[z_re->second] = zre;
                if (z_im != variableNames.end())
                    variables[z_im->second] = zre;
                if (c_re != variableNames.end())
                    variables[c_re->second] = zre;
                if (c_im != variableNames.end())
                    variables[c_im->second] = zre;
            }
        };
    }
}


template<typename T>
class mnd::NaiveIRGenerator : public mnd::MandelGenerator
{
    const ir::Formula& form;
    eval::EvalStruct<T> es;
    std::unique_ptr<eval::EvalNode> newz_re;
    std::unique_ptr<eval::EvalNode> newz_im;
    std::unique_ptr<eval::EvalNode> start_re;
    std::unique_ptr<eval::EvalNode> start_im;
public:
    NaiveIRGenerator(const ir::Formula& irf, mnd::Precision prec = mnd::getType<T>());
    NaiveIRGenerator(NaiveIRGenerator&&) = default;

    virtual void generate(const MandelInfo& info, float* data);
};

extern template class mnd::NaiveIRGenerator<float>;
extern template class mnd::NaiveIRGenerator<double>;
extern template class mnd::NaiveIRGenerator<mnd::DoubleDouble>;
extern template class mnd::NaiveIRGenerator<mnd::QuadDouble>;

#endif // MANDEL_NAIVEIRGENERATOR_H