#pragma once

#include "grbl.h"
#include "grbl_machine.h"
#include "glm/ext/matrix_float4x4.hpp"
#include <nanogui/opengl.h>

namespace grbl {

class shader_program;

class program_renderer {
public:
    void update(const program& pgm, const machine& cnc);
    void render(glm::mat4 mvp, glm::vec2 viewport_size);

    glm::vec3 get_extents_min() const { return min_pos; };

    glm::vec3 get_extents_max() const { return max_pos; };

private:
    GLsizei build_vbo(const grbl::program& pgm);

    GLuint spindle_vbo_id;
    GLuint spindle_vao_id;

    GLuint vbo_id;
    GLuint vao_id;

    std::vector<float> buffer_data;
    shader_program *shader = nullptr;

    bool initialized = false;

    glm::vec3 min_pos, max_pos, spindle_pos;
    GLsizei vertices_count, spindle_vertices_count;

    void initialize_program_buffers();
    void initialize_spindle_buffers();
};


class shader_program {
public:
    shader_program(const char *vs_content, const char *ps_content);
    virtual ~shader_program();

    void bind() const;
    static void unbind();

    GLuint get_id() const { return program_id; }

    int get_uniform(const char *name);

    void set_mat3(float *value, const char *uniform_name);
    void set_mat4(float *value, const char *uniform_name);
    void set_vec2(float *value, const char *uniform_name);
    void set_vec3(float *value, const char *uniform_name);
    void set_vec4(float *value, const char *uniform_name);
    void set_float(float value, const char *uniform_name);
    void set_int(int value, const char *uniform_name);

private:
    GLuint program_id;
    GLuint shader_ids[2];
    std::map<const char *, int> uniform_cache;
};

}
