#include "Spiel.h"
#include <time.h>
#include <DateiSystem.h>
#include "Define.h"
#include "SSKlient.h"
#include "KartenLeser.h"
#include "Statistik.h"
#include "Team.h"
#include "Laser.h"
#ifdef WIN32
#include <random>
#else
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#define Sleep( x )    usleep( (x) * 1000 )
#endif

// Inhalt der Spiel Klasse aus Spiel.h
// Konstruktor
Spiel::Spiel()
{
	zAccounts = 0;
	zKlients = 0;
	stat = new Statistik();
	teams = new RCArray< Team >();
	teamAnzahl = 0;
	spieler = new RCArray< Spieler >();
	shots = new RCArray< Laser >();
	objekte = new RCArray< SpielObjekt >();
    welt = new Welt2D();
    welt->setCircular( 1 );
    welt->setAirResistance( 0.01f );
	psqldb = 0;
	spielId = 0;
	karteId = 0;
	spielerAnzahl = 0;
	karte = 0;
	InitializeCriticalSection( &cs );
	sts = 0;
	log = 0;
	isRunning = 0;
	spielZeit = 0;
	ende = 1;
	nextSchussId = 1;
	ref = 1;
}

// Destruktor
Spiel::~Spiel()
{
	teams->release();
	stat->release();
	spieler->release();
	objekte->release();
    welt->release();
	if( psqldb )
		psqldb->release();
	if( karte )
		karte->release();
	if( sts )
		sts->release();
	DeleteCriticalSection( &cs );
}

// privat
int Spiel::getTeamVonSpieler( int sNum )
{
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp && tmp->getSpielerNummer() == sNum )
			return tmp->zTeam() ? tmp->zTeam()->id : 0;
	}
	return 0;
}

// nicht constant
void Spiel::setPSQLK( SSDatenbankV *psqldb )
{
	if( this->psqldb )
		this->psqldb->release();
	this->psqldb = psqldb;
	stat->setPSQLDB( psqldb->getThis() );
}

void Spiel::setSpielId( int id )
{
	spielId = id;
	stat->setSpielId( id );
}

void Spiel::setKarteId( int karteId )
{
	this->karteId = karteId;
	stat->setKarteId( karteId );
}

void Spiel::setTempPfad( char *pfad )
{
    mapPfad = pfad;
    mapPfad += "/";
}

void Spiel::setAccounts( int anzahl, Array< int > *zAccounts )
{
	this->zAccounts = zAccounts->getThis();
	spielerAnzahl = anzahl;
}

void Spiel::setKlients( int anzahl, RCArray< SSKlientV > *zKlients )
{
	this->zKlients = zKlients->getThis();
}

void Spiel::setSpielerNummern( int anzahl, Array< int > *spielerNummern )
{
	if( !sts )
	{
		sts = new SpielerTeamStruktur();
		KartenLeser *reader = new KartenLeser( karteId, psqldb->getThis(), mapPfad );
		reader->ladeSpielerTeamStruktur( sts );
		if( karte )
			karte->release();
		karte = reader->ladeKarte( sts );
		reader->release();
		for( int i = 0; i < sts->teamAnzahl; i++ )
			teams->set( karte->createTeam( i ), i );
		stat->setTeamNamen( sts->teamName->getThis() );
		int objektAnzahl = karte->getObjektAnzahl();
        for( int i = 0; i < objektAnzahl; i++ )
        {
            SpielObjekt *obj = karte->createObjekt( i );
            welt->addObject( obj->getThis() );
            objekte->set( obj, i );
        }
	}
	for( int i = 0; i < anzahl; i++ )
	{
		Spieler *tmp = karte->createSpieler( spielerNummern->get( i ) );
		if( tmp )
		{
			int team = 0;
			int von = 0;
			int bis = 0;
			for( int j = 0; j < sts->teamAnzahl; j++ )
			{
				bis = von + ( sts->teamSize->hat( j ) ? sts->teamSize->get( j ) : 0 );
				if( tmp->getSpielerNummer() >= von && tmp->getSpielerNummer() < bis )
				{
					team = j;
					break;
				}
				von = bis;
			}
			tmp->setTeam( teams->get( team ) );
			teams->z( team )->spieler->add( tmp );
		}
        welt->addObject( tmp->getThis() );
		spieler->set( tmp, i );
	}
	for( int i = 0; i < anzahl; i++ )
		spieler->z( i )->setAccountId( zAccounts->hat( i ) ? zAccounts->get( i ) : 0 );
	for( int i = 0; i < anzahl; i++ )
		spieler->z( i )->setKlient( new Klient( zKlients->get( i ) ) );
	stat->setSpieler( spielerAnzahl, spieler );
	zAccounts = zAccounts->release();
	zKlients = zKlients->release();
	teamAnzahl = 0;
	for( int i = 0; i < sts->teamAnzahl; i++ )
		if( teams->z( i )->spieler->getEintragAnzahl() )
			teamAnzahl++;
}

void Spiel::klientOffline( int accountId )
{
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		if( spieler->z( i )->getAccountId() == accountId )
			spieler->z( i )->offline();
	}
}

void Spiel::klientOnline( int accountId, SSKlientV *zKlient )
{
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		if( spieler->z( i )->getAccountId() == accountId )
		{
			EnterCriticalSection( &cs );
			Spieler *s = spieler->z( i );
			s->online( zKlient );
			Klient *tmp = spieler->z( i )->zKlient();
			tmp->sendeSpielerNummer( s->getSpielerNummer(), 0 );
			//--------------------------
			LeaveCriticalSection( &cs );
		}
	}
}

void Spiel::nachricht( int accountId, int len, char *bytes )
{
	if( !isRunning || ende )
		return;
	EnterCriticalSection( &cs );
	if( ende )
	{
		LeaveCriticalSection( &cs );
		return;
	}
	char *msgBeg = bytes;
	int msgLen = len;
	int msgAccount = accountId;
	bool saveMSG = 1;
	len--;
	switch( *bytes )
	{
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:
	case 5:
	case 6:
	case 7:
		for( int i = 0; i < spielerAnzahl; i++ )
		{
			Spieler *tmp = spieler->z( i );
			if( tmp && tmp->getAccountId() == accountId )
			{
				if( !tmp->istAmLeben() )
					break;
				if( !tmp->setTastataturStatus( (TastaturStatus)( (int)( *bytes ) / 2 ), ( (int)( *bytes ) % 2 ) == 0 ) )
				{
					saveMSG = 0;
					break;
				}
				if( *bytes != 6 && *bytes != 7 )
				{
					for( int j = 0; j < spielerAnzahl; j++ )
					{
						Spieler *s = spieler->z( j );
						if( s && s->zKlient() )
							s->zKlient()->sendeTastaturStatus( tmp->getSpielerNummer(), (TastaturStatus)( (int)( *bytes ) / 2 ), ( (int)( *bytes ) % 2 ) == 0, spielZeit );
					}
				}
				break;
			}
		}
		break;
	case 8: // chat Nachricht
		if( 1 )
		{
			bytes++;
			Text *txt = psqldb->getAccountRufName( accountId );
			txt->append( ": " );
			txt->append( bytes, len );
			for( int i = 0; i < spielerAnzahl; i++ )
			{
				Spieler *tmp = spieler->z( i );
				if( tmp && tmp->zKlient() )
					tmp->zKlient()->sendeChatNachricht( txt->getText(), spielZeit );
			}
			txt->release();
			len = 0;
		}
		break;
	case 9: // Skill verwendung
		if( 1 )
		{
			bytes++;
			char art = *bytes;
			len--;
			for( int i = 0; i < spielerAnzahl; i++ )
			{
				Spieler *tmp = spieler->z( i );
				if( tmp && tmp->getAccountId() == accountId )
				{
					if( !tmp->istAmLeben() )
						break;
					if( !tmp->setSkill( art ) )
					{
						saveMSG = 0;
						break;
					}
					for( int j = 0; j < spielerAnzahl; j++ )
					{
						Spieler *s = spieler->z( j );
						if( s && s->zKlient() )
							s->zKlient()->sendeSkillNachricht( tmp->getSpielerNummer(), art, spielZeit );
					}
					break;
				}
			}
			break;
		}
		break;
	default:
		saveMSG = 0;
	}
	if( len )
	{
		// error
	}
	if( log && log->istOffen() && saveMSG )
	{
		char c = 1;
		log->schreibe( &c, 1 );
		int spielerNum = 0;
		for( int i = 0; i < spielerAnzahl; i++ )
		{
			Spieler *tmp = spieler->z( i );
			if( tmp && tmp->getAccountId() == msgAccount )
			{
				spielerNum = tmp->getSpielerNummer();
				break;
			}
		}
		log->schreibe( (char*)&spielerNum, 4 );
		short l = (short)msgLen;
		log->schreibe( (char*)&l, 2 );
		log->schreibe( msgBeg, l );
	}
	LeaveCriticalSection( &cs );
}

void Spiel::tick( double zeit )
{
    // Objekte updaten
    welt->tick( zeit );
	// Spieler Updaten
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp )
		{
			if( tmp->doNeedWiederbelebung() )
			{ // Wiederbelebung
                tmp->wiederbeleben();
				for( int j = 0; j < spielerAnzahl; j++ )
				{
					Spieler *s = spieler->z( j );
					if( s )
						s->zKlient()->sendeWiederbelebung( tmp->getSpielerNummer(), spielZeit );
				}
			}
		}
	}
	// Schüsse Collision
    int anz = shots->getEintragAnzahl();
	for( int i = 0; i < anz; i++ )
	{
		Laser *tmp = shots->z( i );
		if( tmp )
		{
			if( tmp->getIntensity() <= 0 )
			{ // Schuss existiert nicht mehr
				for( int j = 0; j < spielerAnzahl; j++ )
				{
					Spieler *s = spieler->z( j );
					if( s )
						s->zKlient()->sendeTreffer( tmp->getId(), -1, spielZeit );
				}
                welt->removeObject( tmp );
				shots->remove( i );
				anz--;
				i--;
			}
			else
			{ // Schuss existiert noch
				int team = getTeamVonSpieler( tmp->getSpieler() );
				for( int j = 0; j < spielerAnzahl; j++ )
				{
					Spieler *s = spieler->z( j );
					if( s && s->zTeam() && s->zTeam()->id != team && ( s->istTreffer( tmp->getPosition() ) ||
						s->istTreffer( tmp->getPosition() + karte->getSize() ) || s->istTreffer( tmp->getPosition() - karte->getSize() ) ) )
					{ // Treffer
                        double intens = tmp->getIntensity() * ( ( tmp->getSpeed() - s->getSpeed() ).getLength() / 200 );
						bool kill = s->nimmSchaden( intens );
						for( int k = 0; k < spielerAnzahl; k++ )
						{
							Spieler *sp = spieler->z( k );
							if( sp )
								sp->zKlient()->sendeTreffer( tmp->getId(), s->getSpielerNummer(), spielZeit );
							if( sp && sp->getSpielerNummer() == tmp->getSpieler() )
								sp->machSchaden( intens, kill );
							if( kill && sp )
								sp->zKlient()->sendeTod( s->getSpielerNummer(), tmp->getSpieler(), spielZeit );
						}
						if( kill )
						{
							if( teams->z( team - 1 ) )
								teams->z( team - 1 )->punkte++;
							if( teams->z( team - 1 )->punkte == teams->z( team - 1 )->maxPunkte )
								ende = 1;
						}
                        welt->removeObject( tmp );
						shots->remove( i );
						anz--;
						i--;
						break;
					}
				}
			}
		}
	}
	// Neue Schüsse hinzufügen
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp )
		{
			Laser *nL = tmp->getLaser( nextSchussId++ );
			if( nL )
			{
                welt->addObject( nL->getThis() );
				shots->add( nL );
				for( int j = 0; j < spielerAnzahl; j++ )
				{
					if( spieler->z( j ) && spieler->z( j )->zKlient() )
						spieler->z( j )->zKlient()->sendeSchuss( nL->getId(), nL->getSpieler(), nL->getPosition(), nL->getSpeed(), nL->getIntensity(), spielZeit );
				}
			}
		}
	}
}

void Spiel::run()
{
	log = new Datei();
	Text *pfad = new Text( "../spiel log/" );
	pfad->append( spielId );
	pfad->append( "/verlauf.ksggl" );
	log->setDatei( pfad );
	log->remove();
	log->erstellen();
	log->open( Datei::Style::schreiben );
	time_t t;
	time( &t );
	srand( (unsigned int)t );
	log->schreibe( (char*)&spielerAnzahl, 4 );
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp && tmp->zKlient() )
		{
			tmp->zKlient()->sendeInit( spieler, 0 );
			log->schreibe( (char*)&i, 4 );
			int sNum = tmp->getSpielerNummer();
			log->schreibe( (char*)&sNum, 4 );
			tmp->zKlient()->sendeSpielerNummer( sNum, 0 );
			int team = tmp->zTeam()->id;
			log->schreibe( (char*)&team, 4 );
			int farbe = tmp->getSpielerFarbe();
			log->schreibe( (char*)&farbe, 4 );
			farbe = tmp->zTeam()->farbe;
			log->schreibe( (char*)&farbe, 4 );
			Text *name = psqldb->getAccountRufName( tmp->getAccountId() );
			char len = (char)( name ? name->getLength() : 0 );
			log->schreibe( &len, 1 );
			if( len )
				log->schreibe( name->getText(), len );
			if( name )
				name->release();
			name = sts->teamName->z( team );
			len = (char)( name ? name->getLength() : 0 );
			log->schreibe( &len, 1 );
			if( len )
				log->schreibe( name->getText(), len );
		}
	}
	Array< char > spielerStatus;
	ZeitMesser *zeit = new ZeitMesser();
	zeit->messungStart();
	isRunning = 1;
	ende = 0;
	double ausgleich = 0;
	double sZ = 0;
	spielZeit = 0;
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp && tmp->zKlient() )
			tmp->zKlient()->sendeStart( 0 );
	}
	double rZeit = 0;
	while( !ende )
	{
		zeit->messungEnde();
		zeit->messungStart();
		double z = zeit->getSekunden();
		ausgleich += 1.0 / 25 - z;
		if( ausgleich > 0 )
			Sleep( (int)( ausgleich * 1000 ) );
		rZeit += z;
		while( sZ + TICK < rZeit && !ende )
		{
			EnterCriticalSection( &cs );
			sZ += TICK;
			spielZeit++;
			char c = 0;
			log->schreibe( &c, 1 );
			tick( TICK );
			LeaveCriticalSection( &cs );
		}
	}
	zeit->messungEnde();
	zeit->release();
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		if( spieler->z( i ) && spieler->z( i )->zKlient() )
		{
			if( spieler->z( i )->zTeam()->punkte < spieler->z( i )->zTeam()->maxPunkte )
			{
				spielerStatus.set( 1, i ); // Datenbank Verloren
				spieler->z( i )->zKlient()->sendeSpielEnde( 0, 0 );
			}
			else
			{
				spielerStatus.set( 2, i ); // Datenbank Gewonnen
				spieler->z( i )->zKlient()->sendeSpielEnde( 1, 0 );
			}
		}
		if( spieler->z( i ) && ( !spieler->z( i )->zKlient() || !spieler->z( i )->istOnline() ) )
			spielerStatus.set( 3, i );
	}
	if( teamAnzahl == 1 )
		psqldb->setSpielStatusBeendet( spielId, 6 );
	else
		psqldb->setSpielStatusBeendet( spielId, 5 );
	for( int i = 0; i < spielerAnzahl; i++ )
	{
		Spieler *tmp = spieler->z( i );
		if( tmp )
		{
			psqldb->setSpielSpielerStatus( spielId, tmp->getAccountId(), tmp->getPunkte(), spielerStatus.get( i ) );
			if( teamAnzahl > 1 )
				psqldb->addSpielerStatistik( tmp->getAccountId(), spielId );
		}
	}
	log->close();
	log = log->release();
	isRunning = 0;
}

// constant
StatistikV *Spiel::getStatistik() const
{
	return stat->getThis();
}

// Reference Counting 
SpielKlasse *Spiel::getThis()
{
	ref++;
	return this;
}

SpielKlasse *Spiel::release()
{
	ref--;
	if( !ref )
		delete this;
	return 0;
}