#include "crBatchedSprites.h"

#include "crSpriteAtlas.h"
#include "crGraphics.h"
#include "crGraphicsStats.h"

void crBatchedSprites::Init( const char* name )
{
    _name = name;

    glGenVertexArrays( 1, &_vao );
    glGenBuffers( 1, &_quadVbo );
    glGenBuffers( 1, &_instanceVbo );

    glBindVertexArray( _vao );

    // shared unit quad (TRIANGLE_STRIP): pos.xy + texcoord.uv, centered, Y-up; v=0 = image top
    const float quad[] =
    {
        -0.5f, -0.5f,   0.0f, 1.0f,
         0.5f, -0.5f,   1.0f, 1.0f,
        -0.5f,  0.5f,   0.0f, 0.0f,
         0.5f,  0.5f,   1.0f, 0.0f,
    };
    glBindBuffer( GL_ARRAY_BUFFER, _quadVbo );
    glBufferData( GL_ARRAY_BUFFER, sizeof( quad ), quad, GL_STATIC_DRAW );

    glEnableVertexAttribArray( 0 );
    glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), reinterpret_cast<void*>( 0 ) );
    glEnableVertexAttribArray( 1 );
    glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), reinterpret_cast<void*>( 2 * sizeof( float ) ) );

    // per-instance stream — locations 2..6, divisor 1
    glBindBuffer( GL_ARRAY_BUFFER, _instanceVbo );

    const GLsizei stride = static_cast<GLsizei>( sizeof( SpriteInstance ) );
    glEnableVertexAttribArray( 2 );
    glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 0 ) );
    glEnableVertexAttribArray( 3 );
    glVertexAttribPointer( 3, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 8 ) );    // size + cosSin
    glEnableVertexAttribArray( 4 );
    glVertexAttribPointer( 4, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 24 ) );
    glEnableVertexAttribArray( 5 );
    glVertexAttribPointer( 5, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 40 ) );   // uvMin + uvMax
    glEnableVertexAttribArray( 6 );
    glVertexAttribPointer( 6, 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 56 ) );

    glVertexAttribDivisor( 2, 1 );
    glVertexAttribDivisor( 3, 1 );
    glVertexAttribDivisor( 4, 1 );
    glVertexAttribDivisor( 5, 1 );
    glVertexAttribDivisor( 6, 1 );

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

    _instances.Reserve( INITIAL_RESERVE );
    _atlases.Reserve( INITIAL_RESERVE );
}

void crBatchedSprites::Cleanup()
{
    glDeleteBuffers( 1, &_instanceVbo );
    glDeleteBuffers( 1, &_quadVbo );
    glDeleteVertexArrays( 1, &_vao );
    _instanceVbo = 0;
    _quadVbo     = 0;
    _vao         = 0;
}

void crBatchedSprites::Begin()
{
    _instances.Clear();
    _atlases.Clear();
}

void crBatchedSprites::Add( const SpriteInstance& instance, int32_t atlasId )
{
    _instances.Add( instance );
    _atlases.Add( static_cast<uint8_t>( atlasId ) );
}

void crBatchedSprites::Flush( GLuint program, float4x4 proj, const crGraphics* graphics )
{
    const int32_t count = _instances.Size();
    if( count <= 0 )
    {
        _runsPrev = 0;
        return;
    }

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

    glActiveTexture( GL_TEXTURE0 );

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );   // declare, don't inherit — a mirrored sprite (negative size) flips winding
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

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

    char    seq[ 48 ];
    int32_t seqLen = 0;
    int32_t runs   = 0;

    int32_t runStart = 0;
    while( runStart < count )
    {
        const uint8_t atlas = _atlases.At( runStart );

        int32_t runEnd = ( runStart + 1 );
        while( ( runEnd < count ) && ( _atlases.At( runEnd ) == atlas ) )
            ++runEnd;

        const int32_t runCount = ( runEnd - runStart );

        glBindTexture( GL_TEXTURE_2D, graphics->SpriteAtlas( atlas )->Texture() );
        glBufferData( GL_ARRAY_BUFFER,
                      static_cast<GLsizeiptr>( runCount ) * static_cast<GLsizeiptr>( sizeof( SpriteInstance ) ),
                      &_instances.At( runStart ),
                      GL_STREAM_DRAW );
        glDrawArraysInstanced( GL_TRIANGLE_STRIP, 0, 4, static_cast<GLsizei>( runCount ) );

        ++crGraphicsStats::drawCalls;
        crGraphicsStats::vertices += ( runCount * 4 );

        if( seqLen < static_cast<int32_t>( sizeof( seq ) - 8 ) )
            seqLen += SDL_snprintf( ( seq + seqLen ), ( sizeof( seq ) - seqLen ), "%s%d", ( ( runs > 0 ) ? "," : "" ), atlas );
        ++runs;

        runStart = runEnd;
    }

    glBindVertexArray( 0 );
    glBindTexture( GL_TEXTURE_2D, 0 );

    if( ( runs > _runsPrev ) && ( runs > 1 ) )
        SDL_LogWarn( SDL_LOG_CATEGORY_RENDER, "crBatchedSprites[%s]: atlas runs increased( %d -> %d, instances:%d, atlases:[%s] )", _name, _runsPrev, runs, count, seq );
    _runsPrev = runs;

    _instances.Clear();
    _atlases.Clear();
}
