// Description:
//   High level infrastructure for game.
//
// Copyright (C) 2003 Frank Becker
//
// 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
//
#include <time.h>

#include <Trace.hpp>

#include <Game.hpp>
#include <Constants.hpp>
#include <Config.hpp>
#include <zStream.hpp>

#include <Audio.hpp>
#include <GameState.hpp>
#include <BlockViewGLSmooth.hpp>

Game::Game( void):
    _model(0),
    _controller(0),
    _view(0),
    _zo(0),
    _oStream(0),
    _zi(0),
    _iStream(0)
{
    XTRACE();
}

Game::~Game()
{
    XTRACE();

    LOG_INFO << "Shutting down..." << endl;
    if( _view) _view->close();

    AudioS::cleanup();

    delete _model;
    delete _controller;
    delete _view;

    delete _zo;
    delete _oStream;
    delete _zi;
    delete _iStream;

    // save config stuff
    ConfigS::instance()->saveToFile();
}

bool Game::init( void)
{
    XTRACE();
    bool result = true;

    if( ! AudioS::instance()->init()) return false;

    EventInjector *ei = 0;
    string play;
    if( ConfigS::instance()->getString( "play", play))
    {
	_iStream = new ifstream( play.c_str(), ios::in | ios::binary);
	_zi = new ziStream( *_iStream);

	string line;
	getline( *_zi, line);
	getline( *_zi, line);

	Tokenizer token(line);
	unsigned int version = strtoul(token.next().c_str(), 0, 10);
	unsigned int seed = strtoul(token.next().c_str(), 0, 10);

	LOG_INFO << "Line [" << line << "] version=" << version << " seed=" << seed << endl;

	GameState::r250.reset(seed);

	ei = new EventInjector(*_zi);
    }

    EventWatcher *ew = 0;
    string record;
    if( ConfigS::instance()->getString( "record", record))
    {
	_oStream = new ofstream( record.c_str(), ios::out | ios::binary);
	ostream *os = _oStream;

	bool compress = false;
	ConfigS::instance()->getBoolean( "compress", compress);

	if( compress)
	{
	    _zo = new zoStream( *_oStream);
	    os = _zo;
	}

	char rTime[128];
	time_t t;
	time(&t);
        strftime( rTime, 127, "%a %d-%b-%Y at %H:%M", localtime(&t));

	(*os) << "# Game recorded on " << rTime << "\n";
	(*os) << "1 " << GameState::r250.getSeed() << "\n";

	ew = new EventWatcher(*os);
    }

    int dimx = 5;
    int dimy = 5;
    int dimz = 12;
    int startLevel = 1;
    ConfigS::instance()->getInteger( "shaftWidth", dimx);
    ConfigS::instance()->getInteger( "shaftHeight", dimy);
    ConfigS::instance()->getInteger( "shaftDepth", dimz);
    ConfigS::instance()->getInteger( "startLevel", startLevel);

    _model = new BlockModel( dimx, dimy, dimz, startLevel, GameState::r250);

    if( ! _model->init())
    {
	return false;
    }

    _view = new BlockViewGLSmooth( *_model);
    _model->registerView( _view);
    _controller = new BlockController( *_model, *_view);

    if( ew) _controller->setEventWatcher( ew);
    if( ei) _controller->setEventInjector( ei);

    if( ! _view->init()) return false;

    //reset our stopwatch
    GameState::stopwatch.reset();
    GameState::stopwatch.pause();

    LOG_INFO << "Initialization complete OK." << endl;

    return result;
}

void Game::reset( void)
{
    //reset in order to start new game
    GameState::stopwatch.reset();
    GameState::startOfGameStep = GameState::stopwatch.getTime();
    GameState::gameTick = 0;
}

void Game::updateOtherLogic( void)
{
    int stepCount = 0;
    float currentTime = Timer::getTime();
    while( (currentTime - GameState::startOfStep) > GAME_STEP_SIZE)
    {
	//advance to next start-of-game-step point in time
	GameState::startOfStep += GAME_STEP_SIZE;
	currentTime = Timer::getTime();

	stepCount++;
	if( stepCount > MAX_GAME_STEPS) break;
    }

    GameState::frameFractionOther =
	(currentTime - GameState::startOfStep) / GAME_STEP_SIZE;

    if( stepCount > 1)
    {
	LOG_WARNING << "Skipped " << stepCount << " frames." << endl;

	if( GameState::frameFractionOther > 1.0)
	{
	    //Our logic is still way behind where it should be at this
	    //point in time. If we get here we already ran through
	    //MAX_GAME_STEPS logic runs trying to catch up.

	    //We clamp the value to 1.0, higher values would try
	    //to predict were we are visually. Maybe not a bad idead
	    //to allow values up to let's say 1.3...
	    GameState::frameFractionOther = 1.0;
	}
    }
}

void Game::updateInGameLogic( void)
{
    int stepCount = 0;
    float currentGameTime = GameState::stopwatch.getTime();
    while( (currentGameTime - GameState::startOfGameStep) > GAME_STEP_SIZE)
    {
	if( ! _controller->update())
	    GameState::requestExit = true;

	if( GameState::isAlive)
	{
	    if( !  _model->update())
		GameState::isAlive = false;
	}
	_view->update(); //XXX: keep seperate view state?

	//advance to next start-of-game-step point in time
	GameState::startOfGameStep += GAME_STEP_SIZE;
	GameState::gameTick++;
	currentGameTime = GameState::stopwatch.getTime();

	stepCount++;
	if( stepCount > MAX_GAME_STEPS) break;
    }

    GameState::frameFraction =
	(currentGameTime - GameState::startOfGameStep) / GAME_STEP_SIZE;

    if( stepCount > 1)
    {
	if( GameState::frameFraction > 1.0)
	{
	    //Our logic is still way behind where it should be at this
	    //point in time. If we get here we already ran through
	    //MAX_GAME_STEPS logic runs trying to catch up.

	    //We clamp the value to 1.0, higher values would try
	    //to predict were we are visually. Maybe not a bad idead
	    //to allow values up to let's say 1.3...
	    GameState::frameFraction = 1.0;
	}
    }
}

void Game::run( void)
{
    XTRACE();
    Audio &audio = *AudioS::instance();

    // Here it is: the main loop.
    LOG_INFO << "Entering Main loop." << endl;

    reset();

    GameState::startOfStep = Timer::getTime();
    GameState::startOfGameStep = GameState::stopwatch.getTime();
    while( ! GameState::requestExit)
    {
#if 0
	switch( GameState::context)
	{
	    case Context::eInGame:
		//stuff that only needs updating when game is actually running
		updateInGameLogic();
		break;

	    default:
		break;
	}
#else
	updateInGameLogic();
#endif

	//stuff that should run all the time
	updateOtherLogic();

	audio.update();
	_view->draw();
    }

    LOG_INFO << "Game Over!\n";
    LOG_INFO << "Score: " << _model->getScore() << "\n";
    LOG_INFO << "Blocks: " << _model->getElementCount() << "\n";
}
