#pragma once
#include "image.h"

enum class kShapeType : uint16_t
{
    Quad = const_hash("rect"),
    Poly = const_hash("poly"),
    RoundRect = const_hash("round-rect"),
    Slice9 = const_hash("slice9"),
};

class Shape
{
protected:
    GLuint buffers[2]{ 0, 0 };
    GLuint arrays[2]{ 0, 0 };
    GLuint count[2]{ 0, 0 };
    GLvoid* ioff[2]{ 0, 0 };
    GLenum index_type = 0;
    bool use_idx = true;
    bool create_buffers_imp(GLvoid* idx, GLvoid* vertices, int isize, int vsize);
public:
    bool create_buffers(GLushort* idx, GLvoid* vertices, int isize, int vsize);
    bool create_buffers(GLuint* idx, GLvoid* vertices, int isize, int vsize);
    bool create_buffers(GLvoid* vertices, int vsize);
    void draw_fill() const;
    void draw_stroke() const;
    void destroy();
    virtual bool create_attrib() { return true; };
    ~Shape();
protected:
    glm::vec2 quad_mid_point(glm::vec2 a, glm::vec2 b, glm::vec2 c, glm::vec2 d)
    {
        float den = (a.x-c.x)*(b.y-d.y) - (a.y-c.y)*(b.x-d.x);
        float numx = (a.x*c.y-a.y*c.x)*(b.x-d.x) - (a.x-c.x)*(b.x*d.y-b.y*d.x);
        float numy = (a.x*c.y-a.y*c.x)*(b.y-d.y) - (a.y-c.y)*(b.x*d.y-b.y*d.x);
        return glm::vec2(numx, numy) / den;
    }
    void adjust_quad_uvs(vertex_t& va, vertex_t& vb, vertex_t& vc, vertex_t& vd)
    {
        static float d[4];
        static vertex_t* v[4];
        v[0] = &va; v[1] = &vb; v[2] = &vc; v[3] = &vd;
        auto mid = quad_mid_point(va.pos, vb.pos, vc.pos, vd.pos);
        for (int i = 0; i < 4; i++)
            d[i] = glm::distance(glm::vec2(v[i]->pos), mid);
        for (int i = 0; i < 4; i++)
        {
            float q = (d[i] + d[(i + 2) % 4]) / d[(i + 2) % 4];
            v[i]->uvs2 = v[i]->uvs = glm::vec2(v[i]->uvs) * q;
            v[i]->pos.w = q;
        }
    }
};

class LineSegment : public Shape
{
public:
    bool create()
    {
        static vertex_t vertices[2];
        count[0] = 2;
        count[1] = 2;
        ioff[0] = (GLvoid*)0;
        ioff[1] = (GLvoid*)0;
        vertices[0] = { { 0, 0, 0, 1 }, { 0, 0 }, { 0, 0 } }; // A
        vertices[1] = { { 0, 0, 0, 1 }, { 0, 1 }, { 0, 1 } }; // B
        return create_buffers(vertices, sizeof(vertices));
    }
    void update_vertices(const glm::vec4 data[2]);
};

class DynamicShape : public Shape
{
public:
    bool create(vertex_t* vertices = nullptr, int vcount = 0)
    {
        count[0] = vcount;
        count[1] = vcount;
        ioff[0] = (GLvoid*)0;
        ioff[1] = (GLvoid*)0;
        return create_buffers(vertices, sizeof(vertex_t) * vcount);
    }
    void update_vertices(vertex_t* vertices, int vcount);
    void clear() { update_vertices(nullptr, 0); }
};

class Plane : public Shape
{
    void create_impl(float w, float h, int div, GLushort* idx, vertex_t* vertices);
public:
    template<int div>
    bool create(float w, float h)
    {
        static GLushort idx[div * div * 6 + (div + 1) * 4];
        static vertex_t vertices[(div+1)*(div+1)];
        create_impl(w, h, div, idx, vertices);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
    bool create(float w, float h, int div)
    {
        int idx_size = div * div * 6 + (div + 1) * 4;
        int vertices_size = (div + 1)*(div + 1);
        auto idx = std::make_unique<GLushort[]>(idx_size);
        auto vertices = std::make_unique<vertex_t[]>(vertices_size);
        create_impl(w, h, div, idx.get(), vertices.get());
        return create_buffers(idx.get(), vertices.get(), sizeof(GLushort) * idx_size, sizeof(vertex_t) * vertices_size);
    }
    void update_vertices(const glm::vec4* data, const glm::vec2* uvs = nullptr, const glm::vec2* uvs2 = nullptr);
};

class HeightmapPlane : public Shape
{
public:
    std::vector<GLuint> idx;
    std::vector<vertex_t> vertices;
    bool create(float w, float h, const Image& img, float scale, float height);
    bool create(float w, float h, int div);
};

class RectShape : public Shape
{
    bool create(float w, float h);
};

class Circle : public Shape
{
public:
    enum class kUVMapping: uint8_t { Planar, Tube };
    template<int div>
    bool create(float radius)
    {
        static GLushort idx[div*3 + div*2];
        static vertex_t vertices[div+1];
        create_impl(radius, div, idx, vertices);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
    template<int div>
    bool create(float radius, kUVMapping map)
    {
        static GLushort idx[(div+1)*3 + (div+1)*4];
        static vertex_t vertices[(div+1)*2];
        create_impl(radius, 0.f, (div+1), idx, vertices, map);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
    template<int div>
    bool create(float radius_out, float radius_in, kUVMapping map)
    {
        static GLushort idx[(div+1)*6 + (div+1)*4];
        static vertex_t vertices[(div+1) * 2];
        create_impl(radius_out, radius_in, (div+1), idx, vertices, map);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
private:
    void create_impl(float radius, int div, GLushort* idx, vertex_t* vertices);
    void create_impl(float radius_out, float radius_in, int div, GLushort* idx, vertex_t* vertices, kUVMapping map);
};

class Rounded : public Shape
{
    void create_impl(float w, float h, float r, int div, GLushort* idx, GLushort* idx_tmp, vertex_t* vertices);
public:
    template<int div>
    bool create(float w, float h, float r)
    {
        static GLushort idx[(10 + div * 4) * 3 + (4 + div * 4) * 2];
        static GLushort idx_tmp[div+1];
        static vertex_t vertices[12 + (div-1) * 4];
        create_impl(w, h, r, div, idx, idx_tmp, vertices);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
};

class Slice9 : public Shape
{
    void create_impl(float w, float h, float r, float tr, GLushort* idx, vertex_t* vertices);
public:
    bool create(float w, float h, float r, float tr)
    {
        static GLushort idx[3 * 3 * 6 + 4 * 2];
        static vertex_t vertices[4 * 4];
        create_impl(w, h, r, tr, idx, vertices);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
};

class Sphere : public Shape
{
    void create_impl(int rings, int sectors, float radius,
        float lat_start, float lat_end, float lon_start, float lon_end, 
        GLushort* idx, vertex_t* vertices);
public:
    template<int rings, int sectors>
    bool create(float radius)
    {
        static GLushort idx[rings * sectors * 6];
        static vertex_t vertices[rings * sectors];
        create_impl(rings, sectors, radius, -M_PI_2, M_PI_2, 0, M_PI * 2.0, idx, vertices);
        return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
    }
    bool create(float radius, float lat_start, float lat_end, 
        float lon_start, float lon_end, float polys_per_angle)
    {
        int rings = glm::ceil(glm::degrees(lat_end - lat_start) * polys_per_angle);
        int sectors = glm::ceil(glm::degrees(lon_end - lon_start) * polys_per_angle);
        auto idx = std::make_unique<GLushort[]>(rings * sectors * 6);
        auto vertices = std::make_unique<vertex_t[]>(rings * sectors);
        create_impl(rings, sectors, radius, lat_start, lat_end, lon_start, lon_end, idx.get(), vertices.get());
        return create_buffers(idx.get(), vertices.get(), rings * sectors * 6 * sizeof(GLushort), rings * sectors * sizeof(vertex_t));
    }
};
