#include "crDevConsole.h"

#include <stdarg.h>
#ifdef __EMSCRIPTEN__
#include <stdio.h>
#endif

#include <imgui.h>

#include "crApp.h"
#include "crEcs.h"

void crDevConsole::Init( crApp* app )
{
    _app  = app;

    _commands.Reserve( 16 );

    Register( "help",      "list commands",                       CmdHelp,      app );
    Register( "timescale", "timescale <x> — sim speed factor",    CmdTimescale, app );
    Register( "hash",      "log the transform determinism hash",  CmdHash,      app );
    Register( "runbg",     "runbg <0|1> — run while backgrounded", CmdRunBg,     app );
    Register( "fullscr",   "fullscr [0|1] — toggles when given no argument", CmdFullscreen, app );
    Register( "vsync",     "vsync [0|1] — toggles when given no argument",   CmdVSync,     app );
}
void crDevConsole::Cleanup()
{
    _app = nullptr;
}

void crDevConsole::Register( const char* name, const char* help, crConsoleCommandFn fn, void* context )
{
    Command cmd = {};
    SDL_strlcpy( cmd.name, name, sizeof( cmd.name ) );
    SDL_strlcpy( cmd.help, help, sizeof( cmd.help ) );
    cmd.fn      = fn;
    cmd.context = context;
    _commands.Add( cmd );
}
void crDevConsole::UnregisterByContext( void* context )
{
    for( int32_t i = _commands.Size() - 1; i >= 0; --i )
    {
        if( _commands.At( i ).context == context )
        {
            _commands.At( i ) = _commands.At( _commands.Size() - 1 );
            _commands.Resize( _commands.Size() - 1 );
        }
    }
}

void crDevConsole::Execute( const char* line )
{
    Print( "> %s", line );

    char buf[ LINE_LEN ];
    SDL_strlcpy( buf, line, sizeof( buf ) );

    const char* argv[ MAX_ARGS ];
    int32_t     argc = 0;

    char* save = nullptr;
    for( char* tok = SDL_strtok_r( buf, " ", &save ); ( tok != nullptr ) && ( argc < MAX_ARGS ); tok = SDL_strtok_r( nullptr, " ", &save ) )
    {
        argv[ argc ] = tok;
        ++argc;
    }

    if( argc == 0 )
        return;

    const int32_t n = _commands.Size();
    for( int32_t i = 0; i < n; ++i )
    {
        if( SDL_strcmp( _commands.At( i ).name, argv[ 0 ] ) == 0 )
        {
            _commands.At( i ).fn( _commands.At( i ).context, argc, argv );
            return;
        }
    }

    Print( "unknown command: %s", argv[ 0 ] );
}
void crDevConsole::Print( const char* fmt, ... )
{
    va_list args;
    va_start( args, fmt );
    SDL_vsnprintf( _history[ _historyNext ], LINE_LEN, fmt, args );
    va_end( args );

#ifdef __EMSCRIPTEN__
    printf( "%s\n", _history[ _historyNext ] );   // stdout is the shell's on-page log; the ImGui panel takes no keyboard on a phone
#endif

    _historyNext = ( ( _historyNext + 1 ) % HISTORY_LINES );
    if( _historyCount < HISTORY_LINES )
        ++_historyCount;
}

void crDevConsole::TryRenderPanel()
{
    if( ImGui::IsKeyPressed( ImGuiKey_GraveAccent, false ) )
    {
        enabled     = ( enabled == false );
        _focusInput = enabled;
    }

    if( enabled == false )
        return;

    const float fontSize = ImGui::GetFontSize();

    const ImGuiViewport* viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos( viewport->Pos );
    ImGui::SetNextWindowSize( ImVec2( viewport->Size.x, 15.0f * fontSize ));

    if( ImGui::Begin( "Console", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings ) )
    {
        const float footer = ImGui::GetFrameHeightWithSpacing();
        if( ImGui::BeginChild( "scrollback", ImVec2( 0.0f, -footer ), false, ImGuiWindowFlags_HorizontalScrollbar ) )
        {
            for( int32_t i = 0; i < _historyCount; ++i )
            {
                const int32_t idx = ( ( ( _historyNext - _historyCount ) + i + HISTORY_LINES ) % HISTORY_LINES );
                ImGui::TextUnformatted( _history[ idx ] );
            }

            if( ImGui::GetScrollY() >= ImGui::GetScrollMaxY() )
                ImGui::SetScrollHereY( 1.0f );
        }
        ImGui::EndChild();

        if( _focusInput )
        {
            ImGui::SetKeyboardFocusHere();
            _focusInput = false;
        }

        ImGui::SetNextItemWidth( -1.0f );
        if( ImGui::InputText( "##input", _input, sizeof( _input ), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCharFilter, &crDevConsole::InputFilter ) )
        {
            if( _input[ 0 ] != '\0' )
                Execute( _input );
            _input[ 0 ] = '\0';
            ImGui::SetKeyboardFocusHere( -1 );   // keep typing — refocus after enter
        }
    }
    ImGui::End();
}

/*static*/ int crDevConsole::InputFilter( ImGuiInputTextCallbackData* data )
{
    return ( data->EventChar == '`' ) ? 1 : 0;   // the toggle key must not type into the field
}

/*static*/ void crDevConsole::CmdHelp( void* context, int32_t argc, const char** argv )
{
    ( void )argc;
    ( void )argv;

    crApp*        app     = static_cast<crApp*>( context );
    crDevConsole* console = app->devConsole;

    const int32_t n = console->_commands.Size();
    for( int32_t i = 0; i < n; ++i )
        console->Print( "%-12s %s", console->_commands.At( i ).name, console->_commands.At( i ).help );
}
/*static*/ void crDevConsole::CmdTimescale( void* context, int32_t argc, const char** argv )
{
    crApp* app = static_cast<crApp*>( context );

    if( argc >= 2 )
        app->SetSimulationSpeed( static_cast<float>( SDL_atof( argv[ 1 ] ) ) );

    app->devConsole->Print( "timescale = %.3f", app->SimulationSpeed() );
}
/*static*/ void crDevConsole::CmdHash( void* context, int32_t argc, const char** argv )
{
    ( void )argc;
    ( void )argv;

    crApp* app = static_cast<crApp*>( context );
    app->devConsole->Print( "step %" SDL_PRIu64 " hash %016" SDL_PRIx64, app->SimulationStepIndex(), app->ecs->HashTransforms() );
}
/*static*/ void crDevConsole::CmdRunBg( void* context, int32_t argc, const char** argv )
{
    crApp* app = static_cast<crApp*>( context );

    if( argc >= 2 )
        app->SetRunInBackground( SDL_atoi( argv[ 1 ] ) != 0 );

    app->devConsole->Print( "runbg = %d", app->IsRunInBackground() ? 1 : 0 );
}
/*static*/ void crDevConsole::CmdFullscreen( void* context, int32_t argc, const char** argv )
{
    crApp* app = static_cast<crApp*>( context );

    const bool want = ( argc >= 2 ) ? ( SDL_atoi( argv[ 1 ] ) != 0 ) : ( app->IsFullscreen() == false );
    app->SetFullscreen( want );

    app->devConsole->Print( "fullscr = %d", want ? 1 : 0 );
}
/*static*/ void crDevConsole::CmdVSync( void* context, int32_t argc, const char** argv )
{
    crApp* app = static_cast<crApp*>( context );

    const bool want = ( argc >= 2 ) ? ( SDL_atoi( argv[ 1 ] ) != 0 ) : ( app->IsVSyncEnabled() == false );
    app->SetVSync( want );

    if( app->IsVSyncSupported() == false )
        app->devConsole->Print( "vsync = %d (inert here — the browser paces the loop, use timescale/fps)", want ? 1 : 0 );
    else
        app->devConsole->Print( "vsync = %d", app->IsVSyncEnabled() ? 1 : 0 );
}
