/****************************************************************
**
** Attal : Lords of Doom
**
** aiInterface.cpp
** interface for the ia-client
**
** Version : $Id: aiInterface.cpp,v 1.12 2004/09/25 19:50:49 lusum Exp $
**
** Author(s) : Pascal Audoux
**
** Date : 05/02/2001
**
** Licence :    
**	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, 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 "aiInterface.h"

// generic include files
#include <stdarg.h>
// include files for QT
#include <qaction.h>
#include <qapplication.h>
#include <qmultilinedit.h>
#include <qmenubar.h>
#include <qsignalmapper.h>
#include <qstatusbar.h>
#include <qpopupmenu.h>
#include <qmessagebox.h>
#include <qregexp.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qpushbutton.h>
// application specific includes
#include "conf.h"

#include "libCommon/log.h"
#include "libCommon/attalSocket.h"
#include "libCommon/dataTheme.h"

#include "libServer/fightAnalyst.h"

#include "ai/analyst.h"

extern QString DATA_PATH;
extern QString THEME;
extern DataTheme DataTheme;
extern Log aifLog;
extern QString PORT;
extern bool FAST;

QMultiLineEdit * screen;
IALogLevel iaLogLevel;


/** add comments here */
AiInterface::AiInterface()
{
	_socket = 0;
	iaLogLevel = LOG_NORMAL;
	if( ! DataTheme.init() ) {
		/// XXX: we have to manage better this case
		QMessageBox::critical( this, tr( "Can't load theme" ), tr( "Theme " ) + THEME + tr( " has not been loaded successfully" ) );
	}

	_analyst = new Analyst();

	setCaption( tr( "Attal: IA client" ) );
	initActions();
	initMenuBar();
	initStatusBar();
	
	_edit = new QMultiLineEdit( this );
	_edit->setReadOnly( true );
	screen = _edit;
	setCentralWidget( _edit );
	_config=0;

	connect( &aifLog, SIGNAL( sig_print( QString ) ), SLOT( slot_log( QString ) ) );

	if(FAST)
		actionConnect(FAST);
}

AiInterface::~AiInterface()
{
	delete _analyst;
	if(_config){
		delete _config;
		_config=0;
	}
}

void AiInterface::slot_log( QString msg )
{
	screen->append( msg );
}

void AiInterface::initStatusBar()
{
	statusBar()->message( tr( "Log level : Normal" ), 0 );
}

void AiInterface::initActions()
{
	_actions.resize( NB_ACTIONS );

	QAction * action;
	QSignalMapper * sigmap = new QSignalMapper( this );

	action = new QAction( tr( "Connect to server" ), tr( "&Connect to server" ), QKeySequence( tr( "CTRL+C" ) ), this );
	_actions.insert( ACTION_CONNECT, action );
	sigmap->setMapping( action, ACTION_CONNECT );
	connect( action, SIGNAL( activated() ), sigmap, SLOT( map() ) );

	action = new QAction( tr( "Disconnect" ), tr( "&Disconnect" ), QKeySequence( tr( "CTRL+D" ) ), this );
	action->setEnabled( false );
	_actions.insert( ACTION_DISCONNECT, action );
	sigmap->setMapping( action, ACTION_DISCONNECT );
	connect( action, SIGNAL( activated() ), sigmap, SLOT( map() ) );

	action = new QAction( tr( "Quit" ), tr( "&Quit" ), QKeySequence( tr( "CTRL+Q" ) ), this );
	_actions.insert( ACTION_QUIT, action );
	sigmap->setMapping( action, ACTION_QUIT );
	connect( action, SIGNAL( activated() ), sigmap, SLOT( map() ) );

	connect( sigmap, SIGNAL( mapped( int ) ), SLOT( slot_action( int ) ) );
}
/*!
  define menus
*/

void AiInterface::initMenuBar()
{
	_menuLog = new QPopupMenu();
	CHECK_PTR( _menuLog );
	_menuLog->insertItem( tr( "Verbose" ), LOG_VERBOSE );
	_menuLog->insertItem( tr( "Normal" ), LOG_NORMAL );
	_menuLog->insertItem( tr( "Critical" ), LOG_CRITICAL );
	_menuLog->insertItem( tr( "Quiet" ), LOG_QUIET );
	_menuLog->setItemChecked( LOG_NORMAL, true );
	connect( _menuLog, SIGNAL( activated( int ) ), SLOT( slot_menuLogActivated( int ) ) );

	QPopupMenu * menuFile = new QPopupMenu();
	CHECK_PTR( menuFile );
	_actions[ ACTION_CONNECT ]->addTo( menuFile );
	_actions[ ACTION_DISCONNECT ]->addTo( menuFile );
	menuFile->insertItem( tr( "Log Level" ), _menuLog );
	_actions[ ACTION_QUIT ]->addTo( menuFile );

	menuBar()->insertItem( tr( "&File" ), menuFile );
}

void AiInterface::slot_action( int num )
{
	switch( num ) {
	case ACTION_CONNECT:
		actionConnect(false);
		break;
	case ACTION_DISCONNECT:
		actionDisconnect();
		break;
	case ACTION_QUIT:
		actionQuit();
		break;
	}
}

void AiInterface::actionConnect(bool fast)
{
	if(!_socket ) {
		if( _config == 0 ) {
			_config = new ConfigConnection( this );
		}
		_config->setHost( "localhost" );
		_config->setPort( PORT.toInt() );
		_config->setPlayerName( "IA" );
		if(fast){
			_config->accept();
		} else { 
			_config->exec();
		}
		if( _config->result() == QDialog::Accepted ) {
			_socket = new AttalSocket;
			_socket->connectToHost( _config->getHost(), _config->getPort() );
			_analyst->setSocket( _socket );
			connect( _socket, SIGNAL( readyRead() ), SLOT( slot_readSocket() ) );
			connect( _socket, SIGNAL(connectionClosed()),SLOT(slot_connectionClosed()) );
			connect( _socket, SIGNAL(error(int)),SLOT(slot_error(int)) );
				
			_actions[ ACTION_CONNECT ]->setEnabled( false );
			_actions[ ACTION_DISCONNECT ]->setEnabled( true );
		}
	} else {
		QMessageBox::critical( this, tr( "Can't connect" ), tr( "You're already connected to a server. Please disconnect first." ) );
	}
}

void AiInterface::slot_connectionClosed()
{
	actionDisconnect();
}

void AiInterface::slot_error(int e)
{
	logEE("Cannot connect to server");
	switch (e){
		case QSocket::ErrConnectionRefused: 
			logEE("Connection Refused");
			break;
		case QSocket::ErrHostNotFound:
			logEE("Host not found");
			break;
		case QSocket::ErrSocketRead: 
			logEE("Read from the socket failed");
			break;
	}
	actionDisconnect();
}

void AiInterface::actionDisconnect()
{
	if( _socket ) {
		_analyst->setSocket( 0 );
		_socket->close();
		delete _socket;
		_socket = 0;
		_actions[ ACTION_CONNECT ]->setEnabled( true );
		_actions[ ACTION_DISCONNECT ]->setEnabled( false );
	}
}

void AiInterface::actionQuit()
{
	if( _socket ) {
		_analyst->setSocket( 0 );
		_socket->close();
		delete _socket;
		_socket = 0;
	}
	qApp->quit();
}

void AiInterface::slot_menuLogActivated( int num )
{
	_menuLog->setItemChecked( iaLogLevel, false );
	switch( num ) {
	case LOG_VERBOSE:
		statusBar()->message( tr( "Log level : Verbose" ), 0 );
		iaLogLevel = LOG_VERBOSE;
		break;
	case LOG_NORMAL:
		statusBar()->message( tr( "Log level : Normal" ), 0 );
		iaLogLevel = LOG_NORMAL;
		break;
	case LOG_CRITICAL:
		statusBar()->message( tr( "Log level : Critical" ), 0 );
		iaLogLevel = LOG_CRITICAL;
		break;
	case LOG_QUIET:
		statusBar()->message( tr( "Log level : Quiet" ), 0 );
		iaLogLevel = LOG_QUIET;
		break;
	}
	_menuLog->setItemChecked( iaLogLevel, true );
}

void AiInterface::slot_readSocket()
{
	_socket->readData();
	ialogV( "Socket received %d | %d | %d", _socket->getCla1(), _socket->getCla2(), _socket->getCla3() );

	switch( _socket->getCla1() ) {
	case SO_MSG:
		_analyst->socketMsg();
		break;
	case SO_CONNECT:
		_analyst->socketConnect();
		break;
	case SO_MVT:
		_analyst->socketMvt();
		break;
	case SO_TECHNIC:
		_analyst->socketTechnic();
		break;
	case SO_FIGHT:
		_analyst->socketFight();
		break;
	case SO_QR:
		_analyst->socketQR();
		break;
	case SO_EXCH:
		_analyst->socketExch();
		break;
	case SO_MODIF:
		_analyst->socketModif();
		break;
	case SO_TURN:
		_analyst->socketTurn();
		break;
	case SO_GAME:
		_analyst->socketGame();
		break;
	default:
		ialogC( "Socket class unknown" );
		break;
	}


	if( _socket->bytesAvailable() > 0 )
		slot_readSocket();
}

#ifdef WIN32
void ialog( char * cformat, ... )
{
#else
void ialog( IALogLevel level, char * cformat, ... )
{
	if( ( !screen ) || ( level < iaLogLevel ) )
		return;
#endif
	// This code has been taken from qt 2.3.2 sources (QString::sprintf) (which is under GPL)
	va_list ap;
	va_start( ap, cformat );

	if ( !cformat || !*cformat ) {
		return;
    	}
	QString format = QString::fromLatin1( cformat );

	static QRegExp *escape = 0;
	if( !escape ) {
		escape = new QRegExp( "%#?0?-? ?\\+?'?[0-9*]*\\.?[0-9*]*h?l?L?q?Z?" );
	}

	QString result;
	uint last = 0;

	int len = 0;
	int pos;
	for (;;) {
		pos = escape->match( format, last, &len );
		// Non-escaped text
		if ( pos > (int)last ) {
			result += format.mid(last,pos-last);
		}
		if ( pos < 0 ) {
			// The rest
			if ( last < format.length() ) {
				result += format.mid(last);
			}
			break;
		}
		last = pos + len + 1;

		// Escape
		QString f = format.mid( pos, len );
		uint width, decimals;
		int params = 0;
		int wpos = f.find('*');
		if ( wpos >= 0 ) {
			params++;
			width = va_arg( ap, int );
			if ( f.find('*', wpos + 1) >= 0 ) {
				decimals = va_arg( ap, int );
				params++;
			} else {
				decimals = 0;
			}
		} else {
			decimals = width = 0;
		}
		QString replacement;
		if ( format[pos+len] == 's' ||
			format[pos+len] == 'S' ||
			format[pos+len] == 'c' ) {
			bool rightjust = ( f.find('-') < 0 );
			// Yes, %-5s really means left adjust in sprintf

			if ( wpos < 0 ) {
				QRegExp num( QString::fromLatin1("[0-9]+") );
				QRegExp dot( QString::fromLatin1("\\.") );
				int nlen;
				int p = num.match( f, 0, &nlen );
				int q = dot.match( f, 0 );
				if ( q < 0 || (p < q && p >= 0) ) {
					width = f.mid( p, nlen ).toInt();
				}
				if ( q >= 0 ) {
					p = num.match( f, q );
					// "decimals" is used to specify string truncation
					if ( p >= 0 ) {
						decimals = f.mid( p, nlen ).toInt();
					}
				}
			}

			if ( format[pos+len] == 's' ) {
				QString s = QString::fromLatin1(va_arg(ap, char*));
				if( decimals <= 0 ) {
					replacement = s;
				} else {
					replacement = s.left(decimals);
				}
			} else {
				int ch = va_arg(ap, int);
				replacement = QChar((ushort)ch);
			}
			if( replacement.length() < width ) {
				replacement = rightjust
				? replacement.rightJustify(width)
				: replacement.leftJustify(width);
			}
		} else if ( format[pos+len] == '%' ) {
			replacement = '%';
		} else if ( format[pos+len] == 'n' ) {
			int* n = va_arg(ap, int*);
			*n = result.length();
		} else {
			char in[64], out[330] = "";
			strncpy(in,f.latin1(),63);
			char fch = format[pos+len].latin1();
			in[f.length()] = fch;
			switch ( fch ) {
			case 'd':
			case 'i':
			case 'o':
			case 'u':
			case 'x':
			case 'X': {
				int value = va_arg(ap, int);
				switch (params) {
				case 0: ::sprintf( out, in, value ); break;
				case 1: ::sprintf( out, in, width, value ); break;
				case 2: ::sprintf( out, in, width, decimals, value ); break;
				}
			} break;
			case 'e': case 'E': case 'f': case 'g': case 'G': {
			double value = va_arg(ap, double);
			switch (params) {
				case 0: ::sprintf( out, in, value ); break;
				case 1: ::sprintf( out, in, width, value ); break;
				case 2: ::sprintf( out, in, width, decimals, value ); break;
				}
			} break;
			case 'p': {
			void* value = va_arg(ap, void*);
				switch (params) {
					case 0: ::sprintf( out, in, value ); break;
					case 1: ::sprintf( out, in, width, value ); break;
					case 2: ::sprintf( out, in, width, decimals, value ); break;
					}
				} break;
			}
		replacement = QString::fromLatin1(out);
		}
		result += replacement;
	}

	va_end( ap );

    	screen->append( result );
	screen->setCursorPosition( screen->numLines(), 0 );
}

//
// ----- ConfigConnection -----
//

ConfigConnection::ConfigConnection( QWidget * parent, const char * name )
	: QDialog( parent, name, true )
{
	setCaption( tr( "Connection to server" ) );
	QVBoxLayout * layout = new QVBoxLayout( this );

	QHBoxLayout * layH1 = new QHBoxLayout();
	layH1->addSpacing( 5 );
	QLabel * labHost = new QLabel( tr( "Host : " ), this );
	FIXEDSIZE( labHost );
	layH1->addWidget( labHost );
	layH1->addSpacing( 5 );
	_host = new QLineEdit( this );
	_host->setFixedSize( 160, 20 );
	layH1->addWidget( _host );
	layH1->addStretch( 1 );
	layout->addLayout( layH1, 1 );

	QHBoxLayout * layH2 = new QHBoxLayout();
	layH2->addSpacing( 5 );
	QLabel * labPort = new QLabel( tr( "Port : " ), this );
	FIXEDSIZE( labPort );
	layH2->addWidget( labPort );
	layH2->addSpacing( 5 );
	_port = new QLineEdit( this );
	_port->setFixedSize( 80, 20 );
	layH2->addWidget( _port );
	layH2->addStretch( 1 );
	layout->addLayout( layH2, 1 );

	QHBoxLayout * layH4 = new QHBoxLayout();
	layH4->addSpacing( 5 );
	QLabel * labName = new QLabel( tr( "Name : " ), this );
	FIXEDSIZE( labName );
	layH4->addWidget( labName );
	layH4->addSpacing( 5 );
	_name = new QLineEdit( this );
	_name->setFixedSize( 80, 20 );
	layH4->addWidget( _name );
	layH4->addStretch( 1 );
	layout->addLayout( layH4, 1 );

	QHBoxLayout * layH3 = new QHBoxLayout();
	QPushButton * pbOk = new QPushButton( this );
	pbOk->setText( tr( "Connect" ) );
	FIXEDSIZE( pbOk );
	layH3->addStretch( 1 );
	layH3->addWidget( pbOk );
	layH3->addStretch( 1 );
	QPushButton * pbCan = new QPushButton( this );
	pbCan->setText( tr( "Cancel" ) );
	FIXEDSIZE( pbCan );
	layH3->addWidget( pbCan );
	layH3->addStretch( 1 );
	layout->addLayout( layH3, 2 );

	layout->activate();

	connect( pbOk, SIGNAL( clicked() ), SLOT( accept() ) );
	connect( pbCan, SIGNAL( clicked() ), SLOT( reject() ) );

	setFixedSize( 250, 150 );
}
