#pragma once
#include "rtt.h"
#include "shader.h"
#include "texture.h"
#include "serializer.h"

class Brush : public Serializer::Type
{
    std::shared_ptr<Image> m_tip_img;
    std::shared_ptr<Image> m_pattern_img;
    std::shared_ptr<Image> m_dual_img;
public:
    //Brush() = default;
    //Brush(const Brush& brush) = default;
    std::string m_name;

    std::shared_ptr<Texture2D> m_tip_texture;
    std::string m_brush_path;
    std::string m_brush_thumb_path;

    std::shared_ptr<Texture2D> m_dual_texture;
    std::string m_dual_path;
    std::string m_dual_thumb_path;
    
    std::shared_ptr<Texture2D> m_pattern_texture;
    std::string m_pattern_path;
    std::string m_pattern_thumb_path;

    glm::vec4 m_tip_color = { 0, 0, 0, 1 };
    glm::vec2 m_tip_scale = { 1.f, 1.f };
    glm::vec2 m_dual_scale = { 1.f, 1.f };
    float m_tip_size = 50;
    float m_tip_spacing = .05;
    float m_tip_flow = 1.f;
    float m_tip_opacity = 1.f;
    float m_tip_angle = 0;
    float m_tip_angle_smooth = 0.2;
    float m_tip_mix = 0;
    float m_tip_wet = 0;
    float m_tip_noise = 0;
    float m_tip_hue = 0;
    float m_tip_sat = 0;
    float m_tip_val = 0;
    bool  m_tip_angle_init = false;
    bool  m_tip_angle_follow = false;
    bool  m_tip_flow_pressure = false;
    bool  m_tip_opacity_pressure = false;
    bool  m_tip_size_pressure = false;
    float m_jitter_scale = 0;
    float m_jitter_angle = 0;
    float m_jitter_scatter = 0;
    bool  m_jitter_scatter_bothaxis = false;
    float m_jitter_flow = 0;
    float m_jitter_opacity = 0;
    float m_jitter_hue = 0;
    float m_jitter_sat = 0;
    float m_jitter_val = 0;
    bool  m_jitter_hsv_eachsample = false;
    float m_jitter_aspect = 0;
    bool  m_jitter_aspect_bothaxis = false;
    int   m_blend_mode = 0;

    bool  m_tip_invert = false;
    bool  m_tip_flipx = false;
    bool  m_tip_flipy = false;
    bool  m_pattern_enabled = false;
    bool  m_dual_enabled = false;
    int   m_dual_blend_mode = 1;
    bool  m_dual_randflip = false;
    float m_dual_size = .75;
    float m_dual_spacing = .25;
    float m_dual_scatter = 0;
    bool  m_dual_scatter_bothaxis = false;
    bool  m_dual_invert = false;
    bool  m_dual_flipx = false;
    bool  m_dual_flipy = false;
    bool  m_tip_randflipx = false;
    bool  m_tip_randflipy = false;
    float m_tip_aspect = 0.5f;
    float m_dual_flow = 1.f;
    float m_dual_opacity = 1.f;
    float m_dual_rotate = .25f;
    float m_dual_angle = 0;
    float m_dual_aspect = 0.5f;
    int   m_dual_count = 1;

    int   m_pattern_blend_mode = 1;
    bool  m_pattern_eachsample = false;
    bool  m_pattern_invert = false;
    bool  m_pattern_flipx = false;
    bool  m_pattern_flipy = false;
    float m_pattern_scale = .25f;
    float m_pattern_brightness = 0.5f;
    float m_pattern_contrast = 0.5f;
    bool  m_pattern_rand_offset = false;
    float m_pattern_depth = 1.f;

    bool load_tip(const std::string& path, const std::string& thumb);
    bool load_dual(const std::string& path, const std::string& thumb);
    bool load_pattern(const std::string& path, const std::string& thumb);
    bool load();
    bool preload();
    void unload();
    bool valid();
    void relocate_paths(std::string base);
    std::string replace_path(std::string path, std::string new_base);

    virtual bool read(BinaryStreamReader& r) override;
    virtual void write(BinaryStreamWriter& w) const override;
};

struct StrokeSample
{
    glm::vec3 col = { 0, 0, 0 };
    glm::vec3 pos = { 0, 0, 0 };
    glm::vec3 origin = { 0, 0, 0 };
    glm::vec2 scale = { 1, 1 };
    float size = 0;
    float flow = 0;
    float opacity = 0;
    float angle = 0;
    bool valid() const
    {
        return !(
            glm::isnan(angle) ||
            glm::isinf(angle) ||
            glm::isnan(flow) ||
            glm::isinf(flow) ||
            glm::isnan(opacity) ||
            glm::isinf(opacity) ||
            glm::isnan(size) ||
            glm::isinf(size) ||
            glm::any(glm::isnan(col)) ||
            glm::any(glm::isnan(pos)) ||
            glm::any(glm::isnan(scale)) ||
            glm::any(glm::isnan(origin))
        );
    }
};

class Stroke
{
public:
    struct Keypoint
    {
        glm::vec3 pos = { 0, 0, 0 };
        float pressure = 0;
        float dist = 0;
        glm::vec2 dir = { 0, 0 };
    };
    struct Camera
    {
        glm::mat4 rot;
        float fov = 0;
    };
    struct SamplesInterpolator
    {
        Keypoint last;
        std::vector<Keypoint> keypoints;
        bool initial_kp_done;
        bool ready_first;
        bool first;
        float spacing;
        float dist;
        bool calc_dir;
        SamplesInterpolator() noexcept;
        SamplesInterpolator(float sp, bool compute_dir) noexcept;
        void add(const Keypoint& p) noexcept;
        bool ready() const noexcept;
        void reset(bool clear_keypoints) noexcept;
        std::vector<Keypoint> compute() noexcept;
    };
    bool m_dir_valid = false;
    glm::vec2 m_dir_ref = { 1, 0 };
    float m_dir_ref_angle = 0;
    float m_dir_angle = 0;
    float m_dir_init = 0;
    float m_curve = 0;
    float m_step = 0;
    float m_max_size = FLT_MAX;
    bool m_filter_points = true;
    glm::vec3 m_tip_color;
    Camera m_camera;
    std::shared_ptr<Brush> m_brush;
    cbuffer<float> m_direction{ 1 };
    cbuffer<float> m_pressure_buff{ 10 };
    cbuffer<glm::vec3> m_hsv_jitter{ 3 };
    StrokeSample m_prev_sample;
    std::vector<Keypoint> m_keypoints;
    std::vector<std::pair<glm::vec3, float>> m_hold_points;
    std::vector<StrokeSample> m_samples;
    std::mt19937 prng;
    SamplesInterpolator m_interp_main;
    SamplesInterpolator m_interp_dir;

    void start(const std::shared_ptr<Brush>& brush);
    void add_point(glm::vec3 pos, float pressure);
    void reset(bool clear_keypoints = false);
    bool has_sample();
    bool need_dir() const noexcept;
    std::vector<StrokeSample> compute_samples();
    StrokeSample randomize_sample(const glm::vec3& pos, float pressure, float curve_angle);

    void randomize_prng();
    float rnd_nor() { return float((double)prng() / (double)prng.max()); };                     // normalized [0, +1]
    float rnd_neg() { return float((double)prng() / (double)prng.max() * 2.0 - 1.0); };         // normalized [-1, +1]
    float rnd_rad() { return float((double)prng() / (double)prng.max() * M_PI * 2.0); };        // normalized [0, 2pi]
    glm::vec3 rnd_vec() { float rad = rnd_rad(); return glm::vec3(cosf(rad), sinf(rad), 0); };  // normalized direction vector
    float rnd_bneg() { return prng() % 2 == 0 ? -1.f : 1.f; };                                  // -1 or 1
};
