/*
Copyright (C) 1998 Pyrosoft Inc. (www.pyrosoftgames.com), Matthew Bogue
 
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 <config.h>
#include "ClientConnectDaemon.hpp"

#include <stdio.h>

#include "PlayerInterface.hpp"
#include "UnitInterface.hpp"
#include "Client.hpp"
#include "NetworkClient.hpp"
#include "GameConfig.hpp"
#include "GameManager.hpp"

#include "Util/Log.hpp"
#include "ConnectNetMessage.hpp"
#include "ConsoleInterface.hpp"

#include "LobbyView.hpp"

enum { _connect_state_idle,
       _connect_state_wait_for_connect_start,
       _connect_state_send_connect_request,
       _connect_state_wait_for_connect_result,
       _connect_state_wait_for_server_game_setup,
       _connect_state_setup_client_game,
       _connect_state_connect_failure
     };


Timer ClientConnectDaemon::failure_display_timer;
Timer ClientConnectDaemon::time_out_timer;
int   ClientConnectDaemon::time_out_counter = 0;
unsigned char ClientConnectDaemon::connection_state;

#define _CLIENT_CONNECT_TIME_OUT_TIME (6.0)
#define _CLIENT_CONNECT_RETRY_LIMIT   (5)


void ClientConnectDaemon::startConnectDaemon()
{
    connection_state = _connect_state_idle;
}

void ClientConnectDaemon::shutdownConnectDaemon()
{
    ConnectMesgNetPanzerClientDisconnect client_disconnect;

    client_disconnect.setPlayerID(
            PlayerInterface::getLocalPlayerIndex());

    CLIENT->sendMessage(&client_disconnect,
            sizeof(ConnectMesgNetPanzerClientDisconnect));
}

void ClientConnectDaemon::startConnectionProcess( )
{
    failure_display_timer.changePeriod( 10 );
    time_out_timer.changePeriod( _CLIENT_CONNECT_TIME_OUT_TIME );
    time_out_counter = 0;
    connection_state = _connect_state_wait_for_connect_start;
}

void ClientConnectDaemon::netMessageLinkAck(const NetMessage* message)
{
    ClientConnectJoinRequestAck *join_request_ack_mesg;
    char buf[80];

    join_request_ack_mesg = (ClientConnectJoinRequestAck *) message;

    switch( join_request_ack_mesg->getResultCode() ) {
    case _join_request_result_success :
        lobbyView->scrollAndUpdate( "Link to Server Established" );
        sprintf( buf, "Protocol Version: %u",
                join_request_ack_mesg->getServerProtocolVersion());
        lobbyView->scrollAndUpdate( buf );
        break;

    case _join_request_result_invalid_protocol :
        lobbyView->scrollAndUpdate( "Link to Server FAILED!" );
        lobbyView->scrollAndUpdate( "Incorrect Network Protocol Revision" );
        lobbyView->scrollAndUpdate( "Please get a newer netPanzer version." );
        sprintf( buf, "Server Protocol Version: %u",
                join_request_ack_mesg->getServerProtocolVersion());
        lobbyView->scrollAndUpdate( buf );
        connection_state = _connect_state_connect_failure;
        failure_display_timer.reset();
        break;

    case _join_request_result_server_busy :
        lobbyView->scrollAndUpdate( "Link to Server FAILED!" );
        lobbyView->scrollAndUpdate( "Server is VERY busy" );
        lobbyView->scrollAndUpdate( "Please try in a few minutes" );
        connection_state = _connect_state_connect_failure;
        failure_display_timer.reset();
        break;

    default:
        LOG ( ("Unknown ACk result?!?") );
        break;
    }

}

void ClientConnectDaemon::netMessageConnectProcessUpdate(const NetMessage* message )
{
    ConnectProcessUpdate *process_update;

    process_update = (ConnectProcessUpdate *) message;

    char buf[80];
    snprintf(buf, sizeof(buf), "Your Position In Queue is %d ",
            process_update->getQueuePosition());

    lobbyView->scrollAndUpdate( buf );
}

void ClientConnectDaemon::netMessageConnectProcessMessage(const NetMessage* message)
{
    ConnectProcessStateMessage *state_mesg;

    char str_buf[128];

    state_mesg = (ConnectProcessStateMessage *) message;

    switch ( state_mesg->getMessageEnum() ) {
    case _connect_state_message_load_game_data : {
            lobbyView->scrollAndUpdate( "Loading Game Data ..." );
        }
        break;

    case  _connect_state_message_sync_player_info : {
            lobbyView->scrollAndUpdate( "Synchronizing Player Info ..." );
        }
        break;

    case _connect_state_message_sync_player_info_percent : {
            snprintf(str_buf, sizeof(str_buf),
                    "Synchronizing Player Info ... (%d%%)",
                    state_mesg->getPercentComplete());
            lobbyView->update( str_buf );
        }
        break;

    case  _connect_state_message_sync_units : {
            lobbyView->scrollAndUpdate( "Synchronizing Game Elements ..." );
        }
        break;

    case _connect_state_message_sync_units_percent : {
            snprintf(str_buf, sizeof(str_buf),
                    "Synchronizing Game Elements ... (%d%%)",
                    state_mesg->getPercentComplete());
            lobbyView->update( str_buf );
        }
        break;

    case  _connect_state_sync_complete : {
            lobbyView->scrollAndUpdate( "Game Synchronized" );
            lobbyView->toggleGameView();
        }
        break;
    }
}

void ClientConnectDaemon::netMessageConnectServerDisconnect(const NetMessage* )
{
    ConsoleInterface::postMessage("Server has terminated the game" );
}

void ClientConnectDaemon::processNetMessage(const NetMessage* message)
{
    switch (message->message_id) {
    case _net_message_id_connect_join_game_request_ack : {
            netMessageLinkAck( message );
        }
        break;

    case _net_message_id_client_connect_process_update : {
            netMessageConnectProcessUpdate( message );
        }
        break;

    case _net_message_id_client_connect_process_state_mesg : {
            netMessageConnectProcessMessage( message );
        }
        break;

    case _net_message_id_connect_netPanzer_server_disconnect : {
            netMessageConnectServerDisconnect( message );
        }
        break;

    default :
        connectFsm( message );
        break;
    } // ** switch

}

void ClientConnectDaemon::serverConnectionBroken()
{
    ConsoleInterface::postMessage( "CONNECTION TO SERVER HAS BEEN UNEXPECTEDLY BROKEN." );
}

void ClientConnectDaemon::connectFailureResult( unsigned char result_code )
{
    switch( result_code ) {
    case _connect_result_server_busy : {
            lobbyView->scrollAndUpdate( "Connect Failure: Server Too Busy" );
        }
        break;

    case _connect_result_server_full : {
            lobbyView->scrollAndUpdate( "Connect Failure: Server Full" );
        }
        break;
    } // ** switch

}

void ClientConnectDaemon::connectFsm(const NetMessage* message )
{
    bool end_cycle = false;

    do {
        switch ( connection_state ) {
        case _connect_state_idle :
            return;

        case _connect_state_wait_for_connect_start : {
                if ( message != 0 ) {
                    if ( message->message_id == _net_message_id_client_start_connect ) {
                        lobbyView->scrollAndUpdate( "Connecting ..." );
                        connection_state = _connect_state_send_connect_request;
                    } else {
                        end_cycle = true;
                    }
                } // ** if ( message != 0 )
                else {
                    end_cycle = true;
                }

            }
            break;

        case _connect_state_send_connect_request : {
                ClientConnectRequest connect_request;

                CLIENT->sendMessage(&connect_request, sizeof(ClientConnectRequest));

                connection_state = _connect_state_wait_for_connect_result;

                end_cycle = true;
            }
            break;

        case _connect_state_wait_for_connect_result : {
                if ( message != 0 ) {
                    if ( message->message_id == _net_message_id_client_connect_result ) {
                        ClientConnectResult *connect_result;

                        connect_result = (ClientConnectResult *) message;

                        if ( connect_result->result_code != _connect_result_success ) {
                            connectFailureResult( connect_result->result_code );

                            connection_state = _connect_state_connect_failure;
                            failure_display_timer.reset();
                        } else {
                            ConnectClientSettings client_setting;

                            client_setting.set( 
                                    gameconfig->playername.c_str(),
                                                gameconfig->getUnitColor(),
                                                gameconfig->playerflag );

                            CLIENT->sendMessage( &client_setting, sizeof(ConnectClientSettings));

                            connection_state = _connect_state_wait_for_server_game_setup;
                        }

                    } else {
                        if ( time_out_timer.count() ) {
                            if ( time_out_counter < _CLIENT_CONNECT_RETRY_LIMIT ) {
                                ClientConnectRequest connect_request;
                                CLIENT->sendMessage( &connect_request, sizeof(ClientConnectRequest));
                                time_out_counter++;
                            } else {
                                lobbyView->scrollAndUpdate( "Connection To Server Failed" );
                                connection_state = _connect_state_connect_failure;
                                failure_display_timer.reset();
                            }
                        }

                    }

                } // ** if ( message != 0 )
                else {
                    if ( time_out_timer.count() ) {
                        if ( time_out_counter < _CLIENT_CONNECT_RETRY_LIMIT ) {
                            ClientConnectRequest connect_request;
                            CLIENT->sendMessage( &connect_request, sizeof(ClientConnectRequest));
                            time_out_counter++;
                        } else {
                            lobbyView->scrollAndUpdate( "Connection To Server Failed" );
                            connection_state = _connect_state_connect_failure;
                        }
                    }

                }

                end_cycle = true;
            }
            break;

        case _connect_state_wait_for_server_game_setup : {
                if ( message != 0 ) {
                    if ( message->message_id == _net_message_id_connect_server_game_setup ) {
                        ConnectMesgClientGameSetupPing client_game_setup_ping;

                        int result_code;
                        char str_buf[128];
                        ConnectMesgServerGameSettings *game_setup;
                        game_setup = (ConnectMesgServerGameSettings *) message;

                        GameManager::startClientGameSetup( message, &result_code );

                        if( result_code == _mapload_result_no_map_file ) {
                            sprintf( str_buf, "MAP %s NOT FOUND!", game_setup->map_name );
                            lobbyView->scrollAndUpdate( str_buf);
                            connection_state = _connect_state_connect_failure;
                            failure_display_timer.reset();
                        } else
                            if( result_code == _mapload_result_no_wad_file ) {
                                lobbyView->scrollAndUpdate( "MAP TILE SET NOT FOUND!" );
                                lobbyView->scrollAndUpdate( "please download the appropriate tileset" );
                                lobbyView->scrollAndUpdate( "from www.pyrosoftgames.com" );
                                connection_state = _connect_state_connect_failure;
                                failure_display_timer.reset();
                            } else {
                                lobbyView->scrollAndUpdate( "Loading Game Data ..." );

                                CLIENT->sendMessage( &client_game_setup_ping, sizeof(ConnectMesgClientGameSetupPing));

                                connection_state = _connect_state_setup_client_game;
                            }
                    }
                }

                end_cycle = true;
            }
            break;

        case _connect_state_setup_client_game : {
                char str_buf[128];
                int percent_complete;

                if ( GameManager::clientGameSetup( &percent_complete ) == false ) {
                    ConnectMesgClientGameSetupAck client_game_setup_ack;

                    sprintf( str_buf, "Loading Game Data ... (%d%%)", percent_complete);
                    lobbyView->update( str_buf );

                    CLIENT->sendMessage( &client_game_setup_ack, sizeof(ConnectMesgClientGameSetupAck));
                    connection_state = _connect_state_idle;
                } else {
                    ConnectMesgClientGameSetupPing client_game_setup_ping;

                    sprintf( str_buf, "Loading Game Data ... (%d%%)", percent_complete);
                    lobbyView->update( str_buf );
                    CLIENT->sendMessage( &client_game_setup_ping, sizeof(ConnectMesgClientGameSetupPing));
                }

                end_cycle = true;
            }
            break;


        case _connect_state_connect_failure : {
                if ( failure_display_timer.count() == true ) {
                    lobbyView->toggleMainMenu();
                    connection_state = _connect_state_idle;
                }
                end_cycle = true;
            }
            break;

        } // ** switch

    } while( end_cycle == false );


}

void ClientConnectDaemon::connectProcess(const NetMessage* message)
{
    connectFsm( message );
}

void ClientConnectDaemon::connectProcess()
{
    connectFsm( 0 );
}
