
#ifndef INC_GAME_LOOP
#include "GameLoop.h"
#endif

#include <allegro.h>
#include <string>

//#include <crtdbg.h>

//----------------------------------------------------------------------------

void fatalError( const char *str )
{
    allegro_message(str);
    exit(0);
}

void fatalError( const char*msg, const char*fname, int line_no )
{
    std::string msg2;
    msg2 = msg;
    msg2 += " ";
    msg2 += fname;
    msg2 += " ";
    msg2 += line_no;
    fatalError( msg2.c_str() );
}

//----------------------------------------------------------------------------

static volatile int timer_counter = 0;

static void timerCounterUpdater()
{
    ++timer_counter;
}

static volatile int fps_counter = 0;

static void fpsCounterUpdater()
{
    ++fps_counter;
}

static volatile int perf_counter = 0;

static void perfCounterUpdater()
{
    ++perf_counter;
}

//----------------------------------------------------------------------------

struct FPSInfo
{
    int logic;
    int displayed;
    int iteration;
    int advance_cputime;
    int display_cputime;
    int show_cputime;
    int perf_counter_;
};


//----------------------------------------------------------------------------

// frame counter
static FPSInfo cur        = { 0, 0, 0, 0, 0, 0, 0 };
static FPSInfo displayed  = { 0, 0, 0, 0, 0, 0, 0 };

//----------------------------------------------------------------------------

GameLoop::GameLoop( DisplayEngine&ade )
        : display_engine(ade)
        , max_skip(2)
        , rest_dur_ms(1)
        , normal_fps(30)
{}

void GameLoop::add_view_port( ViewPortIfc&vp )
{
    v_viewports.push_back( &vp );
}

void GameLoop::add_scroll_handler( ScrollingIfc&sc )
{
    v_scroll_hndls.push_back( &sc );
}

void GameLoop::add_kbd_handler( KbdHndlIfc&kh )
{
    v_kbd_hndls.push_back( &kh );
}

void GameLoop::add_gcl_handler( GameClockIfc&gc )
{
    v_game_clock_hndls.push_back( &gc );
}

//----------------------------------------------------------------------------

void GameLoop::config( const char*ini_fn )
{
    // reset timers
    timer_counter = 0;
    fps_counter   = 0;
    perf_counter  = 0;

    assertCondition( ini_fn );

    push_config_state();
    set_config_file(ini_fn);
    // init timer
    max_skip = get_config_int( "timer", "max_skip", max_skip );
    rest_dur_ms = get_config_int( "timer", "rest_dur_ms", rest_dur_ms );
    normal_fps = get_config_int( "timer", "normal_fps", normal_fps );
    {
        /* Timer related functions and vars need to be locked */
        LOCK_FUNCTION(timerCounterUpdater);
        LOCK_VARIABLE(timer_counter);
        const int rc = install_int_ex( timerCounterUpdater, BPS_TO_TIMER( normal_fps ) );
        assertCondition(rc==0);
    }
    {
        LOCK_FUNCTION(fpsCounterUpdater);
        LOCK_VARIABLE(fps_counter);
        const int rc = install_int_ex( fpsCounterUpdater, SECS_TO_TIMER(1) );
        assertCondition(rc==0);
    }
    {
        LOCK_FUNCTION(perfCounterUpdater);
        LOCK_VARIABLE(perf_counter);
        const int rc = install_int_ex( perfCounterUpdater, BPS_TO_TIMER(10000) );
        assertCondition(rc==0);
    }
    pop_config_state();
}

void GameLoop::loop()
{
    while(true)
    {
        bool needsRefresh = false;
        cur_skip = 0;
        while( timer_counter > 0 )
        {
            if( key[KEY_ESC] )
                exit(0);

            cur.advance_cputime -= perf_counter;
            // handle user input
            bool have_scrolled = false;
            if( v_scroll_hndls.size() > 0 )
            {
                for( std::vector<ScrollingIfc*>::iterator it = v_scroll_hndls.begin(); it != v_scroll_hndls.end(); ++it )
                {
                    have_scrolled = (*it)->handle_scroll() || have_scrolled;
                }
            }
            if( !have_scrolled && keypressed() )
            {
                const int scan_code = readkey() >> 8;
                const int shift_flags = key_shifts & ( KB_SHIFT_FLAG | KB_CTRL_FLAG | KB_ALT_FLAG );
                for( std::vector<KbdHndlIfc*>::iterator it = v_kbd_hndls.begin(); it != v_kbd_hndls.end(); ++it )
                {
                    (*it)->handle_kbd( scan_code, shift_flags );
                }
            }
            clear_keybuf();

            // Book keeping
            ++cur.logic;
            --timer_counter;
            ++cur_skip;
            {
                for( std::vector<GameClockIfc*>::iterator it = v_game_clock_hndls.begin(); it != v_game_clock_hndls.end(); ++it )
                {
                    (*it)->advance_game_time();
                }
            }
            cur.advance_cputime += perf_counter;
            if( cur_skip >= max_skip )
            {
                timer_counter = 0;
                break;
            }
            needsRefresh = true;
        }
        if( needsRefresh )
        {
            cur.display_cputime -= perf_counter;
            // draw backbuffer
            {
                BITMAP*const draw_buffer = display_engine.draw_buffer();
                if( is_video_bitmap(draw_buffer) )
                    acquire_bitmap( draw_buffer );
                clear_bitmap( draw_buffer );
                {
                    for( std::vector<ViewPortIfc*>::iterator it = v_viewports.begin(); it != v_viewports.end(); ++it )
                    {
                        (*it)->display( draw_buffer );
                    }
                }
                if( is_video_bitmap(draw_buffer) )
                    release_bitmap( draw_buffer );
            }
            cur.display_cputime += perf_counter;

            // display backbuffer
            needsRefresh = false;
            cur.show_cputime -= perf_counter;
            display_engine.display_draw_buffer();
            cur.show_cputime += perf_counter;
            cur.displayed++;
        }
        else
        {
            if( rest_dur_ms > 0 )
                rest( rest_dur_ms );
        }
        cur.iteration++;
        if( fps_counter > 0 )
        {
            displayed  = cur;
            displayed.perf_counter_ = perf_counter;
            cur.logic  = cur.iteration = cur.displayed = 0;
            cur.advance_cputime = cur.display_cputime = cur.show_cputime = 0;
            fps_counter = 0;
            perf_counter = 0;
        }
    }
}

//----------------------------------------------------------------------------

GameLoopDebugView::GameLoopDebugView( const ScreenRect&asr, const GameLoop&agl )
        : ViewPortBase( asr )
        , game_loop(agl)
{}

void GameLoopDebugView::display( BITMAP*const db )
{
    rectfill( db, area.x, area.y, area.x+area.w-1, area.y+area.h-1, makecol(200,200,0) );
    rect(     db, area.x, area.y, area.x+area.w-1, area.y+area.h-1, makecol(0,0,255) );
    if( displayed.perf_counter_ > 0 )
    {
        textprintf_ex( db, font, area.x+3, area.y+3, makecol(0,0,0), -1
                       , "FPS: %d-%d-%d / CPU: %d%%-%d%%-%d%% %d%% %d"
                       , displayed.logic
                       , displayed.displayed
                       , displayed.iteration
                       , (displayed.advance_cputime*100)/displayed.perf_counter_
                       , (displayed.display_cputime*100)/displayed.perf_counter_
                       , (displayed.show_cputime*100)/displayed.perf_counter_
                       , ((displayed.advance_cputime+displayed.display_cputime+displayed.show_cputime)*100)/displayed.perf_counter_
                       , displayed.perf_counter_
                     );
    }
}

//----------------------------------------------------------------------------
