#include "crBatchedLines2D.h"

#include "crGraphicsStats.h"
#include "crMath.h"

void crBatchedLines2D::Init()
{
    glGenVertexArrays( 1, &_vao );
    glGenBuffers( 1, &_vbo );

    glBindVertexArray( _vao );
    glBindBuffer( GL_ARRAY_BUFFER, _vbo );

    const GLsizei stride = static_cast<GLsizei>( sizeof( LineVertex ) );
    glEnableVertexAttribArray( 0 );
    glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 0 ) );
    glEnableVertexAttribArray( 1 );
    glVertexAttribPointer( 1, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 8 ) );
    glEnableVertexAttribArray( 2 );
    glVertexAttribPointer( 2, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 24 ) );
    glEnableVertexAttribArray( 3 );
    glVertexAttribPointer( 3, 1, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 40 ) );

    glBindVertexArray( 0 );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );

    _verts.Reserve( INITIAL_RESERVE );
}
void crBatchedLines2D::Cleanup()
{
    glDeleteBuffers( 1, &_vbo );
    glDeleteVertexArrays( 1, &_vao );
    _vbo = 0;
    _vao = 0;
}

void crBatchedLines2D::Begin()
{
    _verts.Clear();
}
void crBatchedLines2D::Line( float2 a, float2 b, float halfWidth, color4 color )
{
    const float dx     = ( b.x - a.x );
    const float dy     = ( b.y - a.y );
    const float lenSqr = ( ( dx * dx ) + ( dy * dy ) );
    if( lenSqr <= 0.0f )
        return;   // no direction to expand along

    const float invLen = ( 1.0f / crMath::Sqrt( lenSqr ) );
    const float extent = ( halfWidth + AA_MARGIN );

    const float alongX = ( dx * invLen * extent );   // round caps need the quad to overhang both ends
    const float alongY = ( dy * invLen * extent );
    const float perpX  = ( -dy * invLen * extent );
    const float perpY  = ( dx * invLen * extent );

    const float2 c0 = float2( ( a.x - alongX ) + perpX, ( a.y - alongY ) + perpY );
    const float2 c1 = float2( ( a.x - alongX ) - perpX, ( a.y - alongY ) - perpY );
    const float2 c2 = float2( ( b.x + alongX ) - perpX, ( b.y + alongY ) - perpY );
    const float2 c3 = float2( ( b.x + alongX ) + perpX, ( b.y + alongY ) + perpY );

    AddVertex( c0, a, b, halfWidth, color );
    AddVertex( c1, a, b, halfWidth, color );
    AddVertex( c2, a, b, halfWidth, color );

    AddVertex( c0, a, b, halfWidth, color );
    AddVertex( c2, a, b, halfWidth, color );
    AddVertex( c3, a, b, halfWidth, color );
}
void crBatchedLines2D::Flush( GLuint program, float4x4 proj )
{
    const int32_t count = _verts.Size();
    if( count <= 0 )
        return;

    glUseProgram( program );
    glUniformMatrix4fv( glGetUniformLocation( program, "u_projection" ), 1, GL_FALSE, proj.m );

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );   // declare, don't inherit — the quad winding flips with the segment direction
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

    glBindVertexArray( _vao );
    glBindBuffer( GL_ARRAY_BUFFER, _vbo );
    glBufferData( GL_ARRAY_BUFFER, static_cast<GLsizeiptr>( count ) * static_cast<GLsizeiptr>( sizeof( LineVertex ) ), _verts.Data(), GL_STREAM_DRAW );

    glDrawArrays( GL_TRIANGLES, 0, static_cast<GLsizei>( count ) );

    glBindVertexArray( 0 );

    ++crGraphicsStats::drawCalls;
    crGraphicsStats::vertices += count;

    _verts.Clear();
}
void crBatchedLines2D::ClearUnusedMemory()
{
    _verts.Clear();
    _verts.Shrink();

    glBindBuffer( GL_ARRAY_BUFFER, _vbo );
    glBufferData( GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );
}

void crBatchedLines2D::AddVertex( float2 pos, float2 p0, float2 p1, float halfWidth, color4 color )
{
    LineVertex v;
    v.pos       = pos;
    v.color     = color;
    v.p0        = p0;
    v.p1        = p1;
    v.halfWidth = halfWidth;

    _verts.Add( v );
}
