/*
 * client/Client.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 <iomanip>

#include "Client.h"
#include "ClientState.h"
#include "GenericMenu.h"
#include "CharacterScreen.h"
#include "DungeonScreen.h"
#include "ControlsMenu.h"
#include "SoundMenu.h"
#include "VideoMenu.h"

#include "../widgets/Widget.h"
#include "../widgets/WidgetManager.h"
#include "../util/TextureManager.h"
#include "../widgets/ButtonWidget.h"

#include "../server/World.h"
#include "../server/Player.h"
#include "../server/Item.h"
#include "../server/Zone.h"

#include "../DG.h"
#include "../GraphicsState.h"

#include <SDL/SDL_mixer.h>

Mix_Music * music;
Mix_Chunk * slam_wav;
Mix_Chunk * button_wav;

int music_channel = 0;

class Client * Client::_instance = 0;
class World  * Client::_world    = 0;
class Player * Client::_player   = 0;

class WidgetManager * Client::_wm = 0;

static const char * _start_map = "Dungeon Breach";

Client::~Client() {
        // TODO: deallocate sounds
        
        cout << " ++ deleting Client" << endl;

        if ( _player ) {
                class Zone * zone = _player->getContainerZone();
                
                if ( zone ) {
                        _dungeonScreen->unloadZone( _player, zone );
                        
                        zone->removeEntity( _player );
                        _world->removeZone( zone );   
                        delete zone;
                }
        }

        delete _dungeonScreen;
        delete _world;
        delete _player;
        delete _mainMenu;
        delete _newGameMenu;
        delete _difficultyMenu;
}

/**
 * utility method to destroy the client instance
 */

void Client::shutdown() {
        if ( _instance ) {
                delete _instance;
                _instance = 0;
        }
}

/**
 * constructor
 */

Client::Client( class WidgetManager * wm, class DG * dg, const char * start_map )
        : Widget( wm->getSize()[0], wm->getSize()[1]),
          _isMultiplayer( false )
{

        if ( start_map )
                _start_map = start_map;

        _bg[0] = _bg[1] = _bg[2] = 1;
        setBGColor4fv( _bg );
        setOpaque( true );

        const char * sdl_error = 0;

        if( *( sdl_error = SDL_GetError() ) ) {
                cout <<  " :: SDL error: Client::Client() preprocessing: " << sdl_error << endl;
                SDL_ClearError();
        }

        _name = "Client";

        _dg = dg;
        _wm = wm;

        Mix_ReserveChannels( 1 );
        Mix_Volume( -1, MIX_MAX_VOLUME / 4 );

        if( *( sdl_error = SDL_GetError() ) ) {
                cout <<  " :: SDL error: Client::Client() after mixup: " << sdl_error << endl;
                SDL_ClearError();
        }

        _currentState = CLIENT_ERROR;

        //_desiredState = CLIENT_START_HARD;
        //_desiredState = CLIENT_OPTIONS_VIDEO;
        _desiredState = CLIENT_SPLASH;

        music = Mix_LoadMUS( "lib/music/magicrai.s3m" );

        if( *( sdl_error = SDL_GetError() ) ) {
                cout <<  " :: SDL error: music load: " << sdl_error << endl;
                SDL_ClearError();
        }

        slam_wav = Mix_LoadWAV( "lib/sfx/menu_switch.wav" );

        if ( slam_wav == 0 )
                cout << " no slam : " << SDL_GetError() << endl;

        button_wav = Mix_LoadWAV( "lib/sfx/menu_button_press.wav" );

        if ( button_wav == 0 )
                cout << " no button : " << SDL_GetError() << endl;

        if( *( sdl_error = SDL_GetError() ) ) {
                cout <<  " :: SDL error: wavloading: " << sdl_error << endl;
                SDL_ClearError();
        }

        //
        // preload textures
        //

        TextureManager::getTexture( "icons/intro", GL_RGB );

        //
        // create the dungeon screen
        //

        _dungeonScreen = new DungeonScreen( _size[0], _size[1], CLIENT_MENU_IN_GAME, CLIENT_MENU_IN_GAME );

        //
        // create the character screen
        //

        _characterScreen = new CharacterScreen( _size[0], _size[1] );

        if ( !_world ) {
                _world = new World( "The World", "A World" );
                _player = new Player( "Jimbo", "jimbo" );
                //                        _player->setBoundingBox( 10, 10, 20, 20, 100, 100 );
                _player->setBoundingRadius( 15 );
                _player->setHeight( 50 );
                _player->setEyeLevel( 40 );

                _player->setStepHeight( 24 );
                        

                class Item * item;

                item = new Item( _world->getProtoItem( 1 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 2 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 3 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 4 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 5 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 6 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 7 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 8 ) );
                _player->itemToInventory( item, -1 );

                item = new Item( _world->getProtoItem( 9 ) );
                _player->itemToInventory( item, -1 );

                //                _dungeonScreen->resetWorld();
        }
        
        //
        // main menu
        //

        static const char * mainMenuItems[] = { 
                "Settings",
                "Load Game",
                "New Game" 
        };

        static const State mainMenuStates[] = {
                CLIENT_MENU_SETTINGS,
                CLIENT_MENU_LOAD_GAME,
                CLIENT_MENU_NEW_GAME
        };

        _mainMenu    = new GenericMenu( _size[0], _size[1],
                                        "Main Menu",
                                        3,
                                        mainMenuItems,
                                        mainMenuStates,
                                        "Quit",
                                        CLIENT_QUIT );
        //
        // new game menu
        //

        static const  char * newGameMenuItems[] = { 
                "Multiplayer",
                "Single Player"
        };
        
        static const State newGameMenuStates[] = {
                CLIENT_MENU_DIFFICULTY_MULTI,
                CLIENT_MENU_DIFFICULTY_SINGLE
        };

        _newGameMenu    = new GenericMenu( _size[0], _size[1],
                                           "New Game Menu",
                                           2,
                                           newGameMenuItems,
                                           newGameMenuStates,
                                           "Back",
                                           CLIENT_MENU_MAIN );
        
        //
        // difficulty menu
        //

        static const  char * difficultyMenuItems[] = { 
                "Fearsome",
                "Heroic",
                "Timid"
        };
        
        static const State difficultyMenuStates[] = {
                CLIENT_START_HARD,
                CLIENT_START_NORMAL,
                CLIENT_START_EASY
        };
        
        _difficultyMenu    = new GenericMenu( _size[0], _size[1],
                                              "Difficulty Menu",
                                              3,
                                              difficultyMenuItems,
                                              difficultyMenuStates,
                                              "Back",
                                              CLIENT_MENU_NEW_GAME );

        static const char * inGameMenuItems[] = {
                "Flee the Dungeon",
                "Sound Options",
                "Video Options",
                "Controls Settings"
        };

        static const State inGameStates[] = {
                CLIENT_EXIT_GAME,
                CLIENT_OPTIONS_SOUND,
                CLIENT_OPTIONS_VIDEO,
                CLIENT_OPTIONS_CONTROLS
        };

        _inGameMenu = new GenericMenu( _size[0], _size[1],
                                       "Dungeon Menu",
                                       4,
                                       inGameMenuItems,
                                       inGameStates,
                                       "Return to Dungeon",
                                       CLIENT_IN_GAME );
        
        static const char * settingsMenuItems[] = {
                "Sound Options",
                "Video Options",
                "Controls Settings"
        };
        
        static const State settingsStates[] = {
                CLIENT_OPTIONS_SOUND,
                CLIENT_OPTIONS_VIDEO,
                CLIENT_OPTIONS_CONTROLS
        };

        _settingsMenu = new GenericMenu( _size[0], _size[1],
                                         "Settings",
                                         3,
                                         settingsMenuItems,
                                         settingsStates,
                                         "Back",
                                         CLIENT_MENU_MAIN );
        
        _controlsMenu = new ControlsMenu( _size[0], _size[1], CLIENT_MENU_IN_GAME, CLIENT_MENU_IN_GAME );
        
        _soundMenu = new SoundMenu( _size[0], _size[1], CLIENT_MENU_SETTINGS, CLIENT_MENU_SETTINGS );
        _videoMenu = new VideoMenu( _size[0], _size[1], CLIENT_MENU_SETTINGS, CLIENT_MENU_SETTINGS );

        setOpaque( true );
        _wm->add( this, 0, 0 );

        _wm->addUpdateWidget( this );
        setUpdateOk( true );

        sdl_error = SDL_GetError( );
        
        if( *sdl_error ) {
                cout <<  " :: SDL error: Client::Client(): " << sdl_error << endl;
                SDL_ClearError();
        }

}

/**
 * UI updater thread func
 */

void Client::updateWidget() {

        static int splash_ticks = 0;
        
        //
        // block for maintenance and animation of screens
        //
        
        if ( _desiredState == _currentState ) {
                
                switch ( _currentState ) {
                case CLIENT_SPLASH:
                        
                        //                        splash_ticks = 500;
                        if ( ++splash_ticks < 100 )
                                return;
                        
                        if ( splash_ticks > 150 ) {
                                _desiredState = CLIENT_MENU_MAIN;
                        }
                        else {
                                _bg[0] -= 0.05;
                                _bg[1] = _bg[2] = _bg[0];
                        }
                        
                        break;
                case CLIENT_ERROR:
                        _desiredState = CLIENT_SPLASH;
                        break;
                default:
                        break;
                }
                return;
        }
        
        //
        // block for handling screen state transitions
        //


        switch ( _currentState ) {
                
        case CLIENT_SPLASH:
                _bg[0] = _bg[1] = _bg[2] = _bg[3] = 1;
                setBGColor4fv( _bg );
                //                setBGTexture( 0, GL_RGB );
                setBGTexture( "textures/church_brick2", GL_RGB );
                setBGTextureCoords( 0, 0, float( _size[0] ) / 128, float( _size[1] ) / 128 );

                //                Mix_FadeOutMusic( 1000 );

                break;
        case CLIENT_MENU_MAIN:
                removeWidget( _mainMenu );
                break;
        case CLIENT_MENU_NEW_GAME:
                removeWidget( _newGameMenu );
                break;
        case CLIENT_MENU_DIFFICULTY_SINGLE:
                removeWidget( _difficultyMenu );
                break;
        case CLIENT_TOWN:
                break;
        case CLIENT_IN_GAME:
                g_state.show_cursor = true;

                _dungeonScreen->setUpdateOk( false );
                removeWidget( _dungeonScreen );
                setBGTexture( "textures/church_brick2", GL_RGB );
                //                setBGTexture( 0, GL_RGB );
                setBGTextureCoords( 0, 0, float( _size[0] ) / 128, float( _size[1] ) / 128 );
                //                setBGTexture( "icons/bg", GL_RGB );

                //                Mix_ResumeMusic();
                Mix_FadeInMusic( music, -1, 5000 );
                setOpaque( true );
                break;
        case CLIENT_CHARACTER_SCREEN:
                removeWidget( _characterScreen );
                break;

        case CLIENT_MENU_IN_GAME:
                removeWidget( _inGameMenu );
                break;
        case CLIENT_MENU_SETTINGS:
                removeWidget( _settingsMenu );
                break;
        case CLIENT_OPTIONS_CONTROLS:
                removeWidget( _controlsMenu );
                break;
        case CLIENT_OPTIONS_SOUND:
                removeWidget( _soundMenu );
                break;
        case CLIENT_OPTIONS_VIDEO:
                removeWidget( _videoMenu );
                break;
        default:
                break;
        }
        
        //
        // handle the second half of the transition
        //

        //                cout << " update with d=" << (int)_desiredState << ", c=" << (int)_currentState << endl;

        switch ( _desiredState ) {
        case CLIENT_SPLASH:
                
                setBGTexture( "icons/intro" , GL_RGB );
                setBGTextureCoords( 0, 0, 1, 1 );
                _bg[0] = _bg[1] = _bg[2] = _bg[3] = 1;
                setBGColor4fv( _bg );
                splash_ticks = 0;
                _currentState = CLIENT_SPLASH;


                Mix_RewindMusic();
                Mix_PlayMusic( music, -1 );
                Mix_VolumeMusic( cl_state.music_mix_vol );

                break;

        case CLIENT_MENU_MAIN:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );

                add( _mainMenu, 
                     _mainMenu->getPreferredPos()[0],
                     _mainMenu->getPreferredPos()[1] );
                _currentState = CLIENT_MENU_MAIN;
                break;
                        
        case CLIENT_MENU_NEW_GAME:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                
                add( _newGameMenu, 
                     _newGameMenu->getPreferredPos()[0],
                     _newGameMenu->getPreferredPos()[1] );
                _currentState = CLIENT_MENU_NEW_GAME;
                break;

        case CLIENT_MENU_DIFFICULTY_SINGLE:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );

                if ( _desiredState == CLIENT_MENU_DIFFICULTY_MULTI )
                        _isMultiplayer = true;
                else
                        _isMultiplayer = false;

                add( _difficultyMenu, 
                     _difficultyMenu->getPreferredPos()[0],
                     _difficultyMenu->getPreferredPos()[1] );
                _currentState = _desiredState;
                break;
                
        case CLIENT_EXIT_GAME:

                // TODO: delete the zone at this stage
                {
                        class Zone * zone = _player->getContainerZone();

                        _dungeonScreen->unloadZone( _player, zone );

                        zone->removeEntity( _player );

                        _world->removeZone( zone );

                        delete zone;
                }      
                
                _currentState = _desiredState;
                _desiredState = CLIENT_MENU_MAIN;
                break;

        case CLIENT_START_HARD:
        case CLIENT_START_NORMAL:
        case CLIENT_START_EASY:

                // TODO: load the zone at this stage
                {
                        class Zone * zone = _world->getZone( _start_map );

                        if ( zone == 0 ) {
                                cout << " Unable to load the start map!" << endl;
                                _currentState = CLIENT_ERROR;
                                _desiredState = CLIENT_MENU_MAIN;
                                break;
                        }
                        zone->addEntity( _player );
                        zone->spawnCharacter( _player, 0 );
                        _dungeonScreen->initZone( _player, zone );                 
                }       
                
                _currentState = _desiredState;
                _desiredState = CLIENT_IN_GAME;
                break;

        case CLIENT_IN_GAME:

                setBGTexture( 0, GL_RGB );

                if ( Mix_FadingMusic() )
                        Mix_HaltMusic();
                else
                        Mix_FadeOutMusic( 3000 );
                
                // TODO: make client widget not paint while dungeon screen is up
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _dungeonScreen, 0, 0 );
                _dungeonScreen->setUpdateOk( true );

                _currentState = _desiredState;
                setOpaque( false );
                break;
                
        case CLIENT_CHARACTER_SCREEN:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _characterScreen, 0, 0 );
                _characterScreen->updateWidget();
                _characterScreen->setEscapeState( _currentState );
                _characterScreen->setCloseState( _currentState );
                _currentState = _desiredState;
                break;

        case CLIENT_MENU_IN_GAME:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _inGameMenu,
                     _inGameMenu->getPreferredPos()[0],
                     _inGameMenu->getPreferredPos()[1] );
                _currentState = _desiredState;
                break;
                
        case CLIENT_MENU_SETTINGS:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _settingsMenu,
                     _settingsMenu->getPreferredPos()[0],
                     _settingsMenu->getPreferredPos()[1] );
                _currentState = _desiredState;
                break;
                
        case CLIENT_OPTIONS_CONTROLS:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _controlsMenu, 0, 0 );
                _controlsMenu->setEscapeState( _currentState );
                _controlsMenu->setCloseState( _currentState );
                _currentState = _desiredState;
                break;
                
        case CLIENT_OPTIONS_SOUND:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _soundMenu, 0, 0 );
                _soundMenu->setEscapeState( _currentState );
                _soundMenu->setCloseState( _currentState );
                _currentState = _desiredState;
                break;       

        case CLIENT_OPTIONS_VIDEO:
                Mix_Volume( Mix_PlayChannel( -1, slam_wav, 0 ), cl_state.sfx_mix_vol );
                add( _videoMenu, 0, 0 );
                _videoMenu->updateValues();
                _videoMenu->setEscapeState( _currentState );
                _videoMenu->setCloseState( _currentState );
                _currentState = _desiredState;
                break;       

        case CLIENT_QUIT:
                _dg->end();
                //                SDL_CloseAudio();
                //                SDL_Quit();
                //                exit( 1 );
                setUpdateOk( false );
                break;
        default:
                cout << " :: Client unhandled transition state " 
                     << (int) _currentState 
                     << " -> " 
                     << (int) _desiredState << endl;
                _desiredState = _currentState;
                _currentState = CLIENT_ERROR;
                
                Mix_Volume( Mix_PlayChannel( -1, button_wav, 0 ), cl_state.sfx_mix_vol );
                break;
                //                _currentState = CLIENT_ERROR;
                //                _desiredState = CLIENT_SPLASH;
        }
}


bool Client::keyPressed(   SDL_KeyboardEvent * evt, int x, int y ) {

        if ( _currentState == CLIENT_SPLASH ) {
                setDesiredState( CLIENT_MENU_MAIN );
        }
        else if ( evt->keysym.sym == SDLK_c ) {
                _desiredState = CLIENT_CHARACTER_SCREEN;
        }
       
        return Widget::keyPressed( evt, x, y );
}


bool Client::keyReleased(   SDL_KeyboardEvent * evt ) {
        return Widget::keyReleased( evt );

}
bool Client::mousePressed( SDL_MouseButtonEvent * evt ) {

        if ( _currentState == CLIENT_SPLASH ) {
                setDesiredState( CLIENT_MENU_MAIN );
                return true;
        }

        return Widget::mousePressed( evt );
}
bool Client::mouseReleased( SDL_MouseButtonEvent * evt ) {
        return Widget::mouseReleased( evt );
}

void Client::setDesiredState( State state ) {
        _desiredState = state;
}

class Client * Client::createInstance( class WidgetManager * wm, 
                                       class DG * dg, const char * start_map ) {
        return ( _instance = new Client( wm, dg, start_map ) );
}
class Client * Client::getInstance() {
        return ( _instance );
}
