#pragma once
#include "rtt.h"
#include "texture.h"
#include "shader.h"
#include "shape.h"
#include "brush.h"
#include "action.h"
#include "canvas_modes.h"
#include <stack>

#define CANVAS_RES 1536

class Layer
{
public:
    //Layer() = default;
    //Layer(const Layer&) = delete;
    RTT m_rtt[6];
    glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
    bool m_dirty_face[6] = SIXPLETTE(false);
    bool m_visible = true;
    bool m_alpha_locked = false;
    float m_opacity = 1.f;
    bool m_hightlight = false;
    int m_blend_mode = 0;
    std::string m_name;
    int w = 0;
    int h = 0;
    struct Snapshot
    {
        std::unique_ptr<uint8_t[]> image[6] = SIXPLETTE(0);
        glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
        bool m_dirty_face[6] = SIXPLETTE(false);
        int width = 0;
        int height = 0;
        void create(int w, int h)
        {
            for (int i = 0; i < 6; i++)
            {
                image[i] = std::make_unique<uint8_t[]>(w*h*4);
                std::fill_n(image[i].get(), w*h*4, 0);
            }
        }
        void clear()
        {
            for (int i = 0; i < 6; i++)
            {
                m_dirty_face[i] = false;
                m_dirty_box[i] = glm::vec4(0);
                std::fill_n(image[i].get(), width*height*4, 0);
            }
        }
        void optimize()
        {
            for (int i = 0; i < 6; i++)
            {
                if (!m_dirty_face[i] || !image[i])
                    continue;
                auto data = reinterpret_cast<glm::u8vec4*>(image[i].get());
                glm::ivec2 bbmin(width, height);
                glm::ivec2 bbmax(0);
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        if (data[x + y * width].a > 0)
                        {
                            bbmin = glm::min(bbmin, { x, y });
                            bbmax = glm::max(bbmax, { x + 1, y + 1 });
                        }
                    }
                }
                glm::vec2 bbsz = bbmax - bbmin;
            }
        }
    };
    void resize(int width, int height);
    bool create(int width, int height, std::string name);
    void clear(const glm::vec4& c);
    Snapshot snapshot();
    void restore(const Snapshot& snap);
    void destroy();
    void optimize();
};

struct PPIThumb
{
    int width = 128;
    int height = 128;
    int comp = 4;
    bool valid() const
    {
        return (width == 128 && height == 128 && comp == 4);
    }
};

struct PPIDocVersion
{
    int major = 0;
    int minor = 2;
};

struct PPISoftVersion
{
    int major = g_version_major;
    int minor = g_version_minor;
    int fix = g_version_fix;
    int build = g_version_build;
};

struct PPIHeader
{
    char magic[4]{ 'P', 'P', 'I', 0 };
    PPIDocVersion doc_version;
    PPISoftVersion soft_version;
    PPIThumb thumb_header;
    bool valid()
    {
        if (strcmp(magic, "PPI") != 0)
            return false;
        if (doc_version.major != 0 || (doc_version.minor < 1 && doc_version.minor > 2))
            return false;
        if (!thumb_header.valid())
            return false;
        return true;
    }
};

class Canvas
{
    struct StrokeFrame
    {
        glm::vec4 col;
        float flow;
        float opacity;
        std::array<std::vector<vertex_t>, 6> shapes;
        glm::vec4 m_mixer_rect;
    };

public:
    Plane m_plane;
    Plane m_plane_brush;
    DynamicShape m_brush_shape;
    BrushMesh m_mesh;
    bool m_unsaved = false;
    bool m_newdoc = true;
    bool m_dirty = false;
    bool m_commit_delayed = false;
    bool m_dirty_stroke = false;
    std::stack<CameraData> m_camera_stack;

    static Canvas* I;
    NodeCanvas* m_node = nullptr;
    bool m_alpha_lock = false;
    bool m_touch_lock = false;
    glm::mat4 m_mv{ 1 };
    glm::mat4 m_proj{ 1 };
    glm::vec4 m_box{ 0 };
    glm::vec4 m_vp{ 0 };
    glm::vec2 m_pan{ 0 };
    glm::vec2 m_size{ 0 };
    int m_width = 0;
    int m_height = 0;
    bool m_use_instanced = false;
    int m_current_layer_idx = 0;
    std::unique_ptr<Stroke> m_current_stroke;
    std::unique_ptr<Stroke> m_dual_stroke;
    bool m_show_tmp = false;
    std::vector<std::unique_ptr<Layer>> m_layers;
    std::vector<int> m_order;
    std::vector<glm::vec2> m_plane_shape[6]; // screen space projection of the plane
    glm::mat4 m_plane_unproject[6] = SIXPLETTE(glm::mat4(1));
    glm::vec3 m_plane_dir[6] = SIXPLETTE(glm::vec3(0));
    glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
    bool m_dirty_face[6] = SIXPLETTE(false);
    Layer m_smask; // selection mask
    bool m_smask_active = false;
    RTT m_tmp[6];
    RTT m_tmp_dual[6];
    RTT m_mixer;
    float m_mixer_scale = 1;
    Texture2D m_brush_mix;
    Texture2D m_tex[6];
    Texture2D m_tex2[6];
    bool m_pick_ready[6];
    std::unique_ptr<glm::u8vec4[]> m_pick_data[6] = SIXPLETTE(nullptr);
    static glm::vec3 m_plane_origin[6];
    static glm::vec3 m_plane_normal[6];
    static glm::vec3 m_plane_tangent[6];
    static glm::mat4 m_plane_transform[6];
    glm::vec2 m_pattern_offset;
    Sampler m_sampler;
    Sampler m_sampler_nearest;
    Sampler m_sampler_linear;
    Sampler m_sampler_brush;
    Sampler m_sampler_bg;
    Sampler m_sampler_mask;
    Sampler m_sampler_stencil;
    Sampler m_sampler_mix;
    glm::mat4 m_cam_rot = glm::mat4(1);
    glm::vec3 m_cam_pos{ 0 };
    float m_cam_fov = 85;
    glm::vec2 m_cur_pos;

    std::shared_ptr<Brush> m_current_brush;

    static std::vector<CanvasMode*> modes[];
    std::vector<CanvasMode*>* m_mode = nullptr;
    kCanvasMode m_current_mode = kCanvasMode::Draw;
    std::function<void(kCanvasMode prev, kCanvasMode mode)> on_mode_changed;
    static void set_mode(kCanvasMode mode)
    {
        auto prev = I->m_current_mode;
        if (I->m_mode)
            for (auto& m : *I->m_mode)
                m->leave();
        I->m_mode = &modes[(int)mode];
        I->m_current_mode = mode;
        if (I->m_mode)
            for (auto& m : *I->m_mode)
                m->enter(prev);
        if (I->on_mode_changed)
            I->on_mode_changed(prev, mode);
    }

    std::vector<Layer::Snapshot> m_layers_snapshot;

    Canvas() { I = this; }
    ~Canvas() { destroy(); }
    void destroy();
    bool create(int width, int height);
    void resize(int width, int height);
    void layer_remove(int idx);
    void layer_add(std::string name);
    void layer_order(int idx, int pos);
    void layer_merge(int source_idx, int dest_idx);
    void stroke_start(glm::vec3 point, float pressure);
    void stroke_update(glm::vec3 point, float pressure);
    void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz);
    std::array<std::vector<vertex_t>, 6> stroke_draw_project(std::array<vertex_t, 4>& B) const;
    // return rect {origin, size}
    glm::vec4 stroke_draw_samples(int i, std::vector<vertex_t>& P);
    std::vector<StrokeFrame> stroke_draw_compute(Stroke& stroke) const;
    void stroke_draw();
    void stroke_end();
    void stroke_cancel();
    void stroke_commit();
    void clear(const glm::vec4& color = { 1, 1, 1, 0 });
    void clear_all();
    void pick_start();
    void pick_update(int plane);
    glm::vec4 pick_get(glm::vec2 canvas_loc);
    void pick_end();
    void snapshot_save();
    void snapshot_restore();
    void snap_history(const std::vector<int>& planes);
    void clear_context();
    void import_equirectangular(std::string file_path);
    void import_equirectangular_thread(std::string file_path);
    void export_equirectangular(std::string file_path, std::function<void()> on_complete = nullptr);
    void export_equirectangular_thread(std::string file_path);
    void export_layers(std::string file_name, std::function<void()> on_complete = nullptr);
	void export_layers_thread(std::string file_name);
	void export_cubes();
    void project_save(std::function<void(bool)> on_complete = nullptr);
    void project_save(std::string file_path, std::function<void(bool)> on_complete = nullptr);
    bool project_save_thread(std::string file_path);
    void project_open(std::string file_path, std::function<void(bool)> on_complete = nullptr);
    bool project_open_thread(std::string file_path);
    void inject_xmp(std::string jpg_path);
    Image thumbnail_generate(int w, int h);
    Image thumbnail_read(std::string file_path);
    void draw_objects(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>);
    void draw_objects(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>, Layer& layer);
    void draw_objects_direct(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>, Layer& layer);
    void point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
                         glm::vec3 &out_origin, glm::vec3 &out_dir);
    void point_unproject(glm::vec2 loc, glm::vec3 &out_origin, glm::vec3 &out_dir);
    glm::vec3 point_trace(glm::vec2 loc);
    bool point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
                     glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id);
    bool point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
                     glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id);
    void project2Dpoints(std::vector<vertex_t>& vertices);
    glm::vec3 project2Dpoint(glm::vec2 pt);
    std::vector<glm::vec2> face_to_shape2D(int plane_index);
    void push_camera();
    void pop_camera();
    CameraData get_camera();
    void set_camera(const CameraData& c);
};

class ActionStroke : public Action
{
public:
    //std::unique_ptr<Stroke> m_stroke;
    std::unique_ptr<uint8_t[]> m_image[6] = SIXPLETTE(nullptr);
    glm::ivec4 m_old_box[6] = SIXPLETTE(glm::ivec4(0));
    bool m_old_dirty[6] = SIXPLETTE(false);
    glm::ivec4 m_box[6] = SIXPLETTE(glm::ivec4(0));
    bool clear_layer = false;
    int m_layer_idx = 0;
    Canvas* m_canvas;
    ActionStroke() = default;
    virtual ~ActionStroke() = default;
    virtual void run() override { }
    virtual Action* get_redo() override;
    virtual void undo() override;
    virtual size_t memory() override;
};
