#include <iostream>
#include <blt/std/allocator.h>
#include <blt/gfx/window.h>
#include <blt/gfx/state.h>
#include <blt/gfx/stb/stb_image.h>
#include <blt/gfx/stb/stb_perlin.h>
#include <blt/std/hashmap.h>
#include <image.h>
#include "blt/gfx/renderer/resource_manager.h"
#include "blt/gfx/renderer/batch_2d_renderer.h"
#include "blt/std/assert.h"
#include <variant>
#include <random>

blt::gfx::matrix_state_manager global_matrices;
blt::gfx::resource_manager resources;
blt::gfx::batch_renderer_2d renderer_2d(resources);

struct node;

blt::area_allocator<node, 32000> node_allocator;
blt::area_allocator<image, 32000> img_allocator;

node* createNode()
{
    auto* n = node_allocator.allocate(1);
    node_allocator.construct(n);
    return n;
}

image* createImage()
{
    auto* i = img_allocator.allocate(1);
    img_allocator.construct(i);
    return i;
}

void destroyNode(node* n)
{
    node_allocator.destroy(n);
    node_allocator.deallocate(n, 1);
}

void destroyImage(image* img)
{
    img_allocator.destroy(img);
    img_allocator.deallocate(img, 1);
}

image* apply_operator(op_con operation)
{
    auto* ret = createImage();
    for (i32 y = 0; y < height; y++)
    {
        for (i32 x = 0; x < width; x++)
        {
            switch (operation.op)
            {
                case function_t::ADD:
                {
                    BLT_ASSERT(operation.has_both());
                    ret->set((operation.i1.value()->get(x, y) + operation.i2.value()->get(x, y)) % std::numeric_limits<u8>::max(), x, y);
                    break;
                }
                case function_t::SUB:
                {
                    BLT_ASSERT(operation.has_both());
                    ret->set((operation.i1.value()->get(x, y) - operation.i2.value()->get(x, y)) % std::numeric_limits<u8>::max(), x, y);
                    break;
                }
                case function_t::MUL:
                {
                    BLT_ASSERT(operation.has_both());
                    ret->set((operation.i1.value()->get(x, y) * operation.i2.value()->get(x, y)) % std::numeric_limits<u8>::max(), x, y);
                    break;
                }
                case function_t::DIV:
                {
                    BLT_ASSERT(operation.has_both());
                    auto den = operation.i2.value()->get(x, y);
                    if (den == 0)
                        ret->set(0, x, y);
                    else
                        ret->set((operation.i1.value()->get(x, y) / den) % std::numeric_limits<u8>::max(), x, y);
                    break;
                }
                case function_t::EXP:
                {
                    BLT_ASSERT(operation.has_both());
                    ret->set(static_cast<u8>(std::pow(operation.i1.value()->get(x, y), operation.i2.value()->get(x, y))), x, y);
                    break;
                }
                case function_t::LOG:
                {
                    BLT_ASSERT(operation.has_one());
                    ret->set(static_cast<u8>(std::log(static_cast<double>(operation.getOne()->get(x, y)))), x, y);
                    break;
                }
                case function_t::SQRT:
                {
                    BLT_ASSERT(operation.has_one());
                    ret->set(static_cast<u8>(std::sqrt(static_cast<double>(operation.getOne()->get(x, y)))), x, y);
                    break;
                }
                case function_t::QUAD:
                {
                    BLT_ASSERT(operation.has_one());
                    auto v = operation.getOne()->get(x, y);
                    ret->set(static_cast<u8>(v * v), x, y);
                    break;
                }
                case function_t::RANDOM:
                {
                    
                    break;
                }
                case function_t::NOISE:
                {
                    break;
                }
                case function_t::COLOR:
                {
                    break;
                }
                case function_t::SCALAR:
                {
                    break;
                }
            }
        }
    }
}

static constexpr int OPERATOR_COUNT = 13;

class tree;

struct node
{
        friend tree;
    private:
        node* left;
        node* right;
        function_t type;
        std::array<float, 7> data;
    public:
        
        static node* construct_random()
        {
            node* n = createNode();
            
            std::random_device dev;
            std::mt19937_64 engine{dev()};
            std::uniform_real_distribution<float> dist(0.0f, 1.0f);
            for (float& f : n->data)
                f = dist(engine);
            
            std::uniform_int_distribution op(0, OPERATOR_COUNT - 1);
            n->type = static_cast<function_t>(op(engine));
            
            return n;
        }
        
        ~node()
        {
            destroyNode(left);
            destroyNode(right);
        }
};

class tree
{
    public:
        node* root;
    
    
};

void init()
{
    global_matrices.create_internals();
    resources.load_resources();
    renderer_2d.create();
}

void update(std::int32_t, std::int32_t)
{
    global_matrices.update();
    renderer_2d.render();
}

int main()
{
    blt::gfx::init(blt::gfx::window_data{"Window of GP test", init, update}.setSyncInterval(1));
    global_matrices.cleanup();
    resources.cleanup();
    renderer_2d.cleanup();
    blt::gfx::cleanup();
    return 0;
}
