/*
 * DG.cc:
 *
 * Copyright (C) 2000 John Watson, jwatson@tempusmud.com
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include <iostream>

#include "DG.h"
#include "Graphics.h"
#include "widgets/EventListeners.h"
#include "widgets/WidgetManager.h"
#include "GraphicsState.h"

#ifndef WIN32
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#endif

#ifndef WIN32

struct itimerval vtalarm_timer;

static DG * theDG = 0;
static const int checkpoint_time = 30;

void checkpoint_shutdown( int sig = 0 ) {
        cout << " :: CHECKPOINT shutdown: alarm expired." << endl;

        if ( theDG ) {
                delete theDG;
        }

        cout << " :: SDL_Quit executed. " << endl;
        raise( SIGKILL );
}

void crash_handler( int sig ) {
        cout << " :: crash caught (signal " << sig << "), shutting down cleanly?" << endl; 
        
        if ( theDG ) {
                delete theDG;
        }
        
        cout << " :: SDL_Quit executed. " << endl;
        raise( SIGKILL );
}
#endif

DG::DG( int argc, char **argv )
        : _film_session( false ),
          _run( true )
{
        
        ok = false;
        
#ifndef WIN32
        theDG = this;

        signal( SIGSEGV, crash_handler );
        signal( SIGPIPE, crash_handler );
        signal( SIGVTALRM,  checkpoint_shutdown );

        vtalarm_timer.it_interval.tv_sec  = checkpoint_time;
        vtalarm_timer.it_interval.tv_usec = 0;
        vtalarm_timer.it_value = vtalarm_timer.it_interval;
        setitimer( ITIMER_VIRTUAL, &vtalarm_timer, 0 );

#endif

        _escape_down = false;
        _hasInputFocus = true;
        _graphics = new Graphics( this, argc, argv );
        
        if ( _graphics->ok == false ) {
                cout << " :: DG could not create Graphics" << endl;
                return;
        }

        //
        // reset the signal handler after SDL and/or Mesa unsets it
        //

#ifndef WIN32
        signal( SIGSEGV, crash_handler );
        signal( SIGPIPE, crash_handler );

        signal( SIGVTALRM, checkpoint_shutdown );

#endif
        //        SDL_EnableKeyRepeat( 200, 30 );

        ok = true;
}

DG::~DG() {
        if ( _graphics )
                delete _graphics;
}

/**
 * DG::HandleEvent is the SDL Event handler
 * @return nonzero if the system should terminate
 */

int DG::handleEvent( SDL_Event *event ) 
{
	bool done = false;
        
        SDLMod modstate = SDL_GetModState();

        class WidgetManager * wm = _graphics->getWidgetManager();

	switch( event->type ) {
        case SDL_MOUSEMOTION:
                // flip the y axis
                
                event->motion.y = _graphics->getVideoModeHeight() - event->motion.y;
                event->motion.yrel = - event->motion.yrel;

                wm->mouseMoved( &( event->motion ) );
                break;
        case SDL_MOUSEBUTTONDOWN:
                // flip the y axis
                event->button.y = _graphics->getVideoModeHeight() - event->button.y;
                wm->mousePressed( &( event->button ) );
                break;
        case SDL_MOUSEBUTTONUP:
                // flip the y axis
                event->button.y = _graphics->getVideoModeHeight() - event->button.y;
                wm->mouseReleased( &( event->button ) );
                break;
        case SDL_ACTIVEEVENT:
		/* See what happened */
                //		printf( "app %s ", event->active.gain ? "gained" : "lost" );
		if ( event->active.state & SDL_APPACTIVE ) {
                        //			printf( "active " );

                        _graphics->setLoopRunning( event->active.gain );

                        //                        wm->mouseEntered( 

		} else if ( event->active.state & SDL_APPMOUSEFOCUS ) {
                        _hasInputFocus = event->active.gain;

		} else if ( event->active.state & SDL_APPINPUTFOCUS ) {
                        _hasInputFocus = event->active.gain;
		}
                //		printf( "focus\n" );
		break;

        case SDL_KEYUP:
		if ( event->key.keysym.sym == SDLK_ESCAPE ) {
                        _escape_down = false;
                }
                break;

        case SDL_KEYDOWN:
                
                if ( event->key.keysym.sym == SDLK_F10 ) {

                        if( modstate & KMOD_CTRL ) {
                                _film_session = !_film_session;
                                cout << " :: DG toggled _file_session " << _film_session << endl;
                        }
                        else {
                                _graphics->dumpScreenshot();
                        }
                        break;
                }

                //                cout << "key pressed: " << SDL_GetKeyName(event->key.keysym.sym) 
                //<< " : " << (int) event->key.keysym.scancode << endl;

		if ( event->key.keysym.sym == SDLK_ESCAPE ) {
                        _escape_down = true;
                }
                
                else if ( event->key.keysym.sym == SDLK_F1 ) {
                        done = true;
                        break;
                }
                
                
                else if ( event->key.keysym.sym == SDLK_EQUALS ) {
                        //cout << " plus" << endl;
                        //                        _graphics->increaseRes();
                }

                else if ( event->key.keysym.sym == SDLK_MINUS ) {
                        //        cout << " MINUS" << endl;
                }
                
                //
                // toggle the console
                //

                else if ( event->key.keysym.sym == SDLK_BACKQUOTE ) {
                        wm->toggleConsole();
                        break;
                }
                
                //
                // toggle input grab
                //

                else if ( event->key.keysym.sym == SDLK_g ) {
                        if ( modstate & KMOD_CTRL ) {
                                _graphics->setGrab( ! g_state.grab_input );
                        }
                        break;
                }
                

                int x, y;
                SDL_GetMouseState( &x, &y );
                y = _graphics->getVideoModeHeight() - y;
                wm->keyPressed( &( event->key ), x, y );

		break;


        case SDL_QUIT:
                cout << " :: SDL_QUIT" << endl;
		done = true;
		break;
	}

	return done;
}

int DG::startGameLoop( int argc, char **argv ) {

        SDL_Event event;
        Uint32 loop_begin_time;
        Uint32 loop_diff;

        _graphics->setLoopRunning( true );

        int num_loops = 0;
        loop_begin_time = SDL_GetTicks();
        
        Uint32 num_fps_counts = 0;
        Uint32 fps_total = 0;

        Uint32 last_update_tick = SDL_GetTicks();

        _graphics->setScreenshotCacheSize( 100 );

        Uint8 num_graphics_ticks = 0;

        Uint32 total_graphics_time = 0;
        
        int loops_since_event = 0;
        
        // the main game loop
        
        while ( _run ) {
                
#ifndef WIN32
                setitimer( ITIMER_VIRTUAL, &vtalarm_timer, 0 );
#endif

                //
                // Check if there's a pending event.
                //
                
                // do a little pause if no events have appeared for a long time,
                // to keep from starving the input queue
                
                if ( ++loops_since_event > 10 ) {
                        SDL_Delay( 25 );
                        loops_since_event = 0;
                }
                
                bool quit_now = false;
                while ( SDL_PollEvent( &event ) ) {
                        
                        loops_since_event = 0;
                        //
                        // DG::handleEvent returns true if we must stop processing
                        //
                        
                        if ( DG::handleEvent( &event ) ) {
                                quit_now = true;
                                break;
                        }
                }
                
                if ( quit_now ) {
                        break;
                }

                // do the drawing
                //

                Uint32 predraw_tick = SDL_GetTicks();
                
                _graphics->updateGraphics();
                
                Uint32 tick = SDL_GetTicks();
                
                //
                // keep track of the graphics performance
                //

                total_graphics_time += ( tick - predraw_tick );

                if ( ++num_graphics_ticks > 10 ) {
                        g_state.graphics_time = total_graphics_time / num_graphics_ticks;
                        g_state.graphics_fps = ( g_state.graphics_time ? ( 1000 / g_state.graphics_time ) : -1 );
                        num_graphics_ticks = 0;
                        total_graphics_time = 0;
                }
                
                //
                // update UI every .025 sec at most ( 40 FPS )
                //
                
                if ( ( tick - last_update_tick ) > 25 ) {

                        // TODO: make this work right when no actual physics is occuring
                        // e.g. when a map is loaded and it takes several seconds

                        //                        last_update_tick += 25;
                        //                        g_state.physics_deficit = tick - last_update_tick;

                        last_update_tick = tick;
                        _graphics->getWidgetManager()->updateWidgets();
                }
                
                //
                // keep track of the overall performance of this game loop
                //

                if ( ++num_loops > 40 ) {
                        loop_diff = SDL_GetTicks() - loop_begin_time;
                        int num_fps = num_loops * 1000 / loop_diff;

                        g_state.total_fps = num_fps;
                        
                        loop_begin_time = SDL_GetTicks();
                        num_loops = 0;
                        fps_total += num_fps;
                        ++num_fps_counts;
                }

                if ( _film_session ) {
                        _graphics->takeScreenshot();
                }
        }
       
        // the loop is finished, clean up and go away
        
        _graphics->flushScreenshotCache();


        if ( g_state.physics_deficit > 100 )
                cout << " :: DG Physics update deficit: " << g_state.physics_deficit << " ms." << endl;
        
        if ( num_fps_counts > 0 ) {
                cout << " :: DG Average FPS: " << ( fps_total / num_fps_counts ) << endl;
        }
        else {
                cout << " :: DG Average FPS: no samples." << endl;
        }

        return 0;
}
