#include <GL/glew.h>
#include <memory.h>
#include <cmath>
#include "render.h"
#include "primitives.h"
#include <window.h>
#include <render/utils/buffer.h>

const uint32_t primBufferSize = 0xfff;

struct prim2DVert
{
    vec2f position;
    color::rgba color;
};

render::utils::buffer<prim2DVert> shapeBuffer(0xFFF);

namespace render{
namespace primitives2d{

uint32_t drawMode;

render::vertexArray VAO;
render::vertexBuffer VBO;
render::shader prim2DShader;

void init()
{
    VAO.gen();
    VBO.gen();

    VBO.createEmptyBuffer(primBufferSize*sizeof(prim2DVert), GL_STREAM_DRAW);

    VAO.bind();
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(2);
    VBO.bind();
    glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(prim2DVert), (GLuint*)(offsetof(prim2DVert, position))); //vertex
    glVertexAttribPointer(2, 4, GL_FLOAT, 0, sizeof(prim2DVert), (GLuint*)(offsetof(prim2DVert, color))); //color rgba
    VAO.unbind();
    VBO.unbind();

    prim2DShader.load("data/shaders/primitive.xml");
    drawMode = 0;
}

void free()
{
    VAO.free();
    VBO.free();
    prim2DShader.free();
}

void flush()
{
    if(!shapeBuffer.count) return;
    prim2DShader.bind();
    render::loadCurrentCamera();
    VBO.subData(shapeBuffer.count, shapeBuffer.data);
    switch(drawMode)
    {
    case 0:
        VAO.draw(GL_LINES, 0, shapeBuffer.count); break;
    case 1:
        VAO.draw(GL_TRIANGLES, 0, shapeBuffer.count); break;
    }
    shapeBuffer.count = 0;
}

void addVertex(uint32_t type, const prim2DVert *vert, uint32_t count)
{
    render::setMode(0);
    if(type != drawMode)
        flush();
    drawMode = type;
    if(!shapeBuffer.add(vert, count))
    {
        flush();
        shapeBuffer.add(vert, count);
    }
}

/*******************************************
* Простые примитивы
*
* линии, треугольники
********************************************/

void line(const vec2f &p1, const vec2f &p2, const rgba &c)
{
    line(p1, p2, c, c);
}

void line(const vec2f &p1, const vec2f &p2, const rgba &c1, const rgba &c2)
{
    prim2DVert t[2] = {{p1, c1}, {p2, c2}};
    addVertex(0, t, 2);
}

//да, я знаю что это кусок говна
inline void triangle(const vec2f &p1, const vec2f &p2, const vec2f &p3,
              const rgba &c1, const rgba &c2, const rgba &c3)
{
    prim2DVert t[3] = {{p1, c1}, {p2, c2}, {p3, c3}};
    addVertex(1, t, 3);
}

inline void triangle(const vec2f &p1, const vec2f &p2, const vec2f &p3, const rgba &color)
{
    prim2DVert t[3] = {{p1, color}, {p2, color}, {p3, color}};
    addVertex(1, t, 3);
}

/*******************************************
* Сложные примитивы
*
* Квадраты, круги, окружности, кривые, и т.д.
********************************************/

void quad(const vec2f &pos, const vec2f &size, const rgba &color)
{
    constexpr vec2f offset[6] = {vec2f(0, 1), vec2f(0), vec2f(1, 0),
                                 vec2f(1, 0), vec2f(1), vec2f(0, 1)};
    prim2DVert data[6];

    for(int i = 0; i < 6; i++)
    {
        data[i].position = pos+size*offset[i];
        data[i].color = color;
    }
    addVertex(1, data, 6);
}

static inline void mathCirclePointPosition(const vec2f &sinCos, const vec2f &length, float angle, vec2f *out)
{
    vec2f pointVal;
    sincosf(angle, &pointVal.y, &pointVal.x);

    pointVal *= length;

    out->y = pointVal.x * sinCos.y - pointVal.y * sinCos.x;
    out->x = pointVal.y * sinCos.y + pointVal.x * sinCos.x;
}

void circle(const vec2f &pos, const vec2f &length, const rgba &color, float angle, uint32_t samples)
{
    if(!samples)
        samples = (length.x + length.y)*3.1415;

    vec2f sinCos;
    sincosf(angle * DTOR, &sinCos.y, &sinCos.x);

    vec2f temp, result;
    mathCirclePointPosition(sinCos, length, angle * DTOR, &temp);
    temp += pos;
    for(int i = 1; i <= samples; i++)
    {
        float ta;
        /******************************
        * Костыль для устранения артефакта неполного рисования круга при angle отличном от 0
        *******************************/
        if(i == samples)
            ta = angle*DTOR;
        else
            ta = (i*360.0/samples+angle)*DTOR;

        mathCirclePointPosition(sinCos, length, ta, &result);
        result += pos;

        triangle(pos, temp, result, color);
        temp = result;
    }
}

void round(const vec2f &pos, float length, const rgba &color, float angle, uint32_t samples)
{
// @todo (pelmenka#1#): И это тоже
    if(!samples)
        samples = length*3.1415;

    vec2f temp = vec2f(cos(angle*DTOR)*length+pos.x, sin(angle*DTOR)*length+pos.y);
    for(int i = 1; i <= samples; i++)
    {
        vec2f result;
        result.x = cos((i*360.0/samples+angle)*DTOR)*length+pos.x;
        result.y = sin((i*360.0/samples+angle)*DTOR)*length+pos.y;
        line(temp, result, color);
        temp = result;
    }
}

void rect(const vec2f &pos, const vec2f &size, const rgba &color)
{
    const vec2f offset[4] = {vec2f(0, 1), vec2f(1), vec2f(1, 0), vec2f(0)};
    for(int i = 0, n; i < 4; i++)
    {
        if((n = i+1) == 4) n = 0;
        line(pos+size*offset[i], pos+size*offset[n], color);
    }
}

void curve(uint32_t size, const vec2f *data, const rgba &color, float quality)
{
    if(size < 2)
        return;

    if(size == 2)
    {
        line(data[0], data[1], color);
        return;
    }

    if(quality <= 0)
        return;

    float length = 0;

    for(int i = 0; i < size; i++)
        length += data[i].x*data[i].x + data[i].y*data[i].y;

    length = sqrt(length);

    size--;

    vec2f temp = data[0];

    double add = quality/length;
    for(double t = add; t <= 1.0; t+=add)
    {
        vec2f current = {0, 0};
        for(int i = size; i >= 0; i--)
        {
            double mul = pow(1.0-t, size-i)*pow(t, i);
            if(i < size
            && i > 0)
                mul *= size;

            current += data[i] * mul;
        }

        line(temp, current, color);
        temp = current;
    }
}

void ark(const vec2f &pos, float radius, float angle, float value, const rgba &color, uint32_t quality)
{
    if(!quality)
        quality = radius*3.1415*value/180.0f;

    vec2f temp = vec2f(sin(angle*DTOR)*radius+pos.x, cos(angle*DTOR)*radius+pos.y);
    for(int i = 1; i <= quality; i++)
    {
        vec2f result;
        sincosf((i*value/quality+angle)*DTOR, &result.x, &result.y);
        result *= radius;
        result += pos;
        line(temp, result, color);
        temp = result;
    }
}

void arkSolid(const vec2f &pos, float radius, float angle, float value, const rgba &color, uint32_t quality)
{
    if(!quality)
        quality = radius*3.1415*value/180.0f;
    vec2f temp = vec2f(sin(angle*DTOR)*radius+pos.x, cos(angle*DTOR)*radius+pos.y);
    for(int i = 1; i <= quality; i++)
    {
        vec2f result;
        sincosf((i*value/quality+angle)*DTOR, &result.x, &result.y);
        result *= radius;
        result += pos;
        triangle(temp, pos, result);
        temp = result;
    }
}

void arkBorder(const vec2f &pos, float radius, float angle, float value, const rgba &color, uint32_t quality)
{
    if(!quality)
        quality = radius*3.1415*value/180.0f;

    vec2f temp = vec2f(sin(angle*DTOR)*radius+pos.x, cos(angle*DTOR)*radius+pos.y);
    line(temp, pos, color);
    for(int i = 1; i <= quality; i++)
    {
        vec2f result;
        sincosf((i*value/quality+angle)*DTOR, &result.x, &result.y);
        result *= radius;
        result += pos;
        line(temp, result, color);
        temp = result;
    }
    line(temp, pos, color);
}

void ring(const vec2f &pos, float radius, float radius2, const rgba &color, float angle, uint32_t quality)
{
    if(radius < radius2)
        std::swap(radius, radius2);
    if(!quality)
        quality = radius*3.1415;

    vec2f temp, result;
    sincosf(angle*DTOR, &temp.x, &temp.y);
    for(int i = 1; i <= quality; i++)
    {
        sincosf((i*360.0f/quality+angle)*DTOR, &result.x, &result.y);
        vec2f p1, p2, p3, p4;
        p1 = temp*radius+pos;
        p2 = temp*radius2+pos;
        p3 = result*radius+pos;
        p4 = result*radius2+pos;
        triangle(p1, p2, p3, color);
        triangle(p2, p3, p4, color);
        temp = result;
    }
}

void ringBorder(const vec2f &pos, float radius, float radius2, const rgba &color, float angle, uint32_t quality)
{
    round(pos, radius, color, quality);
    round(pos, radius2, color, quality);
}

void ringArk(const vec2f &pos, float radius, float radius2, float angle, float value, const rgba &color, uint32_t quality)
{
    if(radius < radius2)
        std::swap(radius, radius2);
    if(!quality)
        quality = radius*value/180.0f;
    vec2f temp, result;
    sincosf(angle*DTOR, &temp.x, &temp.y);

    for(int i = 1; i <= quality; i++)
    {
        sincosf((i*value/quality+angle)*DTOR, &result.x, &result.y);
        vec2f p1, p2, p3, p4;
        p1 = temp*radius+pos;
        p2 = temp*radius2+pos;
        p3 = result*radius+pos;
        p4 = result*radius2+pos;
        triangle(p1, p2, p3, color);
        triangle(p2, p3, p4, color);
        temp = result;
    }
}

void ringArkRadius(const vec2f &pos, vec2f rPair1, vec2f rPair2, float angle, float value, const color::rgba &color, uint32_t quality)
{
    if(rPair1.x < rPair1.y)
        std::swap(rPair1.x, rPair1.y);
    if(rPair2.x < rPair2.y)
        std::swap(rPair2.x, rPair2.y);

    if(!quality)
        quality = max(rPair1.y, rPair2.y)*value/180.0f;
    vec2f temp, result;
    sincosf(angle*DTOR, &temp.x, &temp.y);

    float r1 = rPair1.x, r2 = rPair1.y;
    float a1 = rPair2.x/quality, a2 = rPair2.y/quality;

    for(int i = 1; i <= quality; i++)
    {
        sincosf((i*value/quality+angle)*DTOR, &result.x, &result.y);
        vec2f p1, p2, p3, p4;
        p1 = temp*r1+pos;
        p2 = temp*r2+pos;
        r1 += a1;
        r2 += a2;


        p3 = result*r1+pos;
        p4 = result*r2+pos;
        triangle(p1, p2, p3);
        triangle(p2, p3, p4);
        temp = result;
    }
}

void ringArkBorder(const vec2f &pos, float radius, float radius2, float angle, float value, const rgba &color, uint32_t quality)
{
    vec2f temp;
    sincosf(angle*DTOR, &temp.x, &temp.y);
    line(temp*radius+pos, temp*radius2+pos, color);
    sincosf((angle+value)*DTOR, &temp.x, &temp.y);
    line(temp*radius+pos, temp*radius2+pos, color);
    ark(pos, radius, angle, value, color, quality);
    ark(pos, radius2, angle, value, color, quality);
}

namespace _polygone
{
    bool triCollide(const vec2f &p, const vec2f &A, const vec2f &B, const vec2f &C);
    bool isTriangleOK(const std::list<vec2f>&, const std::list<vec2f>::iterator &v, const rgba &color);
    float getAngle(const std::list<vec2f>&, const std::list<vec2f>::iterator &v, float modAngle);
}

void polygone(const vec2f *vertices, uint32_t count, const rgba &color, bool ccwOrder)
{
    std::list<vec2f> vertData;
    for(int i = 0; i < count; i++)
        vertData.push_back(vertices[i]);
    while(vertData.size() >= 3)
    {
        auto leftIndex = vertData.begin();
        for(auto i = std::next(vertData.begin()); i != vertData.end(); i++)
        {
            if(i->x < leftIndex->x)
                leftIndex = i;
        }

        if(_polygone::isTriangleOK(vertData, leftIndex, color))
        {
            vertData.erase(leftIndex);
            continue;
        }
        float modAngle = _polygone::getAngle(vertData, leftIndex, 0);
        float lastAngle = _polygone::getAngle(vertData, std::prev(vertData.end(), 1), modAngle);
        for(auto i = vertData.begin(); i != vertData.end(); i++)
        {
            if(i == leftIndex)
                continue;
            float angle;
            if((angle = _polygone::getAngle(vertData, i, modAngle)) <= lastAngle)
                if(_polygone::isTriangleOK(vertData, i, color))
                {
                    vertData.erase(i);
                    break;
                }
            lastAngle = angle;
        }
    }
}
namespace _polygone
{
bool isTriangleOK(const std::list<vec2f> &vertData, const std::list<vec2f>::iterator &v, const rgba &color)
{
    std::list<vec2f>::const_iterator prev, next;
    if(v == vertData.begin())
        prev = std::prev(vertData.end(), 1);
    else
        prev = std::prev(v, 1);

    next = std::next(v, 1);
    if(next == vertData.end())
        next = vertData.begin();

    for(auto i = vertData.begin(); i != vertData.end(); i++)
    {
        if(i == v || i == next || i == prev)
            continue;
        if(triCollide(*i, *prev, *v, *next))
            return 0;
    }
    triangle(*prev, *v, *next, color);
    return 1;
}

bool triCollide(const vec2f &p, const vec2f &A, const vec2f &B, const vec2f &C)
{
    vec3f r;
    r.x = (A.x-p.x)*(B.y-A.y)-(B.x-A.x)*(A.y-p.y);
    r.y = (B.x-p.x)*(C.y-B.y)-(C.x-B.x)*(B.y-p.y);
    r.z = (C.x-p.x)*(A.y-C.y)-(A.x-C.x)*(C.y-p.y);

    if((r.x < 0 && r.y < 0 && r.z < 0)
    || (r.x ==0 && r.y ==0 && r.z ==0)
    || (r.x > 0 && r.y > 0 && r.z > 0))
        return 1;
    return 0;
}

float getAngle(const std::list<vec2f> &vertData, const std::list<vec2f>::iterator &v, float modAngle)
{
    std::list<vec2f>::const_iterator next = std::next(v, 1);
    if(next == vertData.end())
        next = vertData.begin();
    vec2f tempvec = (*next)-(*v);
    return atan2(tempvec.x, tempvec.y)-modAngle;
}

}
/*
void drawGui(const vec2f &pos, float type, const vec2f &size, const rgba &background, const rgba &borderColor, const vec4f &border)
{
    const vec2f offset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0),
                             vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    if(size.x == 0)
        return;
    const vec2f texCoord[9][6] =
        {{vec2f(0, 0), vec2f(1, 0), vec2f(1, 1), vec2f(1, 1), vec2f(0, 1), vec2f(0, 0)},
         {vec2f(1, 0), vec2f(1, 0), vec2f(1, 1), vec2f(1, 1), vec2f(1, 1), vec2f(1, 0)},
         {vec2f(1, 0), vec2f(0, 0), vec2f(0, 1), vec2f(0, 1), vec2f(1, 1), vec2f(1, 0)},

         {vec2f(0, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(0, 0), vec2f(0, 0)},
         {vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0)},
         {vec2f(1, 0), vec2f(0, 0), vec2f(0, 0), vec2f(0, 0), vec2f(1, 0), vec2f(1, 0)},

         {vec2f(0, 1), vec2f(1, 1), vec2f(1, 0), vec2f(1, 0), vec2f(0, 0), vec2f(0, 1)},
         {vec2f(1, 1), vec2f(1, 1), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 1)},
         {vec2f(1, 1), vec2f(0, 1), vec2f(0, 0), vec2f(0, 0), vec2f(1, 0), vec2f(1, 1)}};

    vec2f posList[9] = {vec2f(-border.left, -border.top), vec2f(0, -border.top), vec2f(size.x, -border.top),
                        vec2f(-border.left, 0), vec2f(0, 0), vec2f(size.x, 0),
                        vec2f(-border.left, size.y), vec2f(0, size.y), vec2f(size.x, size.y)};

    vec2f sizeList[9] = {vec2f(border.left, border.top), vec2f(size.x, border.top), vec2f(border.right, border.top),
                         vec2f(border.left, size.y), vec2f(size.x, size.y), vec2f(border.right, size.y),
                         vec2f(border.left, border.bottom), vec2f(size.x, border.bottom), vec2f(border.right, border.bottom)};

    type::guiVert data[9*6];
    uint32_t addr = 0;
    for(int c = 0; c < 9; c++)
        for(int i = 0; i < 6; i++)
        {
            data[addr].vert = vec3f(offset[i]*sizeList[c]+posList[c]+pos, type);
            data[addr].texCoord = texCoord[c][i];
            data[addr].color = background;
            data[addr++].color2 = borderColor;
        }
    render::defaultResources::guiShader.bind();
    render::defaultResources::guiTextures[0].bind(0);
    render::defaultResources::guiTextures[1].bind(1);
    render::_internal::_addGui(data, addr);
}
*/
}}
