#include "Spiel.h"
#include <Zeit.h>
#ifdef WIN32
#include "..\..\Datenbank\Datenbank.h"
#include "..\SpielServer.h"
#include "Reader\KartenLeser.h"
#else
#include "Datenbank.h"
#include "SpielServer.h"
#include "KartenLeser.h"
#include <unistd.h>
#define Sleep( x )               usleep( (x) * 1000 )
#include <dlfcn.h>
#define LoadLibrary( x )         dlopen( (x), RTLD_LAZY )
#define GetProcAddress           dlsym
#define FreeLibrary              dlclose
#endif

typedef SpielKlasse*( *DllStart )( );

// Inhalt der Spiel Klasse aus Spiel.h
// Konstruktor
Spiel::Spiel( int id, SpielServer *ss )
    : Thread()
{
    this->ss = ss;
    this->id = id;
    klients = new RCArray< SSKlient >();
    sts = new SpielerTeamStruktur();
    accounts = new Array< int >();
    spielerNummern = new Array< int >();
    status = new Array< int >();
    prozent = new Array< int >();
    teamAuswahlFertig = new Array< bool >();
    spiel = 0;
    state = 0;
    spielerAnzahl = 0;
    karte = 0;
    spielDll = 0;
    statistik = 0;
    ref = 1;
}

// Destruktor
Spiel::~Spiel()
{
    klients->release();
    sts->release();
    accounts->release();
    spielerNummern->release();
    status->release();
    prozent->release();
    teamAuswahlFertig->release();
    if( spiel )
        spiel->release();
    if( statistik )
        statistik->release();
    if( spielDll )
        FreeLibrary( spielDll );
    ss->release();
}

int Spiel::findFreeTeam()
{
    int free = 0;
    int teamIndex = 0;
    int teamSize = sts->teamSize->get( teamIndex );
    double max = 0;
    Array< int > teams;
    for( int i = 0; i < sts->spielerAnzahl; i++ ) // suche nach teams die prozentual am wenigsten voll sind
    {
        bool used = 0;
        for( int j = 0; j < spielerAnzahl; j++ )
        {
            if( spielerNummern->hat( j ) && spielerNummern->get( j ) == i + 1 )
                used = 1;
        }
        if( !used )
            free++;
        teamSize--;
        if( !teamSize )
        {
            double teamVal = (double)free / sts->teamSize->get( teamIndex );
            if( teamVal > max )
            {
                teams.leeren();
                max = teamVal;
            }
            if( teamVal >= max )
                teams.add( teamIndex );
            free = 0;
            if( !sts->teamSize->hat( ++teamIndex ) )
                break;
            else
                teamSize = sts->teamSize->get( teamIndex );
        }
    } // suche aus den teams eines zufällig aus
    double randomVal = rand() / ( (double)RAND_MAX + 1 );
    double diff = 1.0 / teams.getEintragAnzahl();
    teamIndex = -1;
    while( randomVal >= 0 )
    {
        randomVal -= diff;
        teamIndex++;
    }
    if( teams.hat( teamIndex ) )
        return teams.get( teamIndex );
    else
        return teams.get( 0 );
}

void Spiel::teamAusgleich()
{
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        int sNum = spielerNummern->hat( i ) ? spielerNummern->get( i ) : 0;
        if( !sNum )
        {
            int team = findFreeTeam();
            for( int j = 0; j < team; j++ )
                sNum += sts->teamSize->get( j );
            while( true )
            {
                sNum++;
                bool gefunden = 0;
                for( int k = 0; k < spielerAnzahl; k++ )
                {
                    if( spielerNummern->hat( k ) && spielerNummern->get( k ) == sNum )
                    {
                        gefunden = 1;
                        break;
                    }
                }
                if( !gefunden )
                    break;
            }
            spielerNummern->set( sNum, i );
        }
    }
}

// nicht constant
void Spiel::setAccounts( int anzahl, Array< int > *zAccounts )
{
    for( int i = 0; i < anzahl; i++ )
        accounts->set( zAccounts->hat( i ) ? zAccounts->get( i ) : 0, i );
    spielerAnzahl = anzahl;
}

void Spiel::setKlients( int anzahl, RCArray< SSKlient > *zKlients )
{
    for( int i = 0; i < anzahl; i++ )
        klients->set( zKlients->get( i ), i );
}

void Spiel::setKarteId( int karteId )
{
    karte = karteId;
    KartenLeser *reader = new KartenLeser( id, karte, ss->getThis() );
    reader->ladeSpielerTeamStruktur( sts );
    reader->release();
    start();
}

bool Spiel::klientVerbunden( SSKlient *klient )
{
    int accId = klient->getAccountId();
    bool ret = 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accId )
        {
            if( state == 1 )
            {
                klient->erstellungInitialisierung( sts );
                for( int j = 0; j < spielerAnzahl; j++ )
                {
                    if( accounts->hat( j ) && accounts->get( j ) != accId )
                    {
                        SSKlient *tmp = klients->z( j );
                        tmp->erstellungAddSpieler( accId );
                        klient->erstellungAddSpieler( accounts->get( j ) );
                        if( spielerNummern->hat( j ) )
                            klient->erstellungSpielerTeam( accounts->get( j ), spielerNummern->get( j ) );
                    }
                }
            }
            if( state == 5 && spiel )
                spiel->klientOnline( klient->getAccountId(), klient );
            klients->set( klient, i );
            status->set( 1, i );
            klient->setSpiel( this );
            ret = 1;
            break;
        }
    }
    if( !ret )
        klient->release();
    return ret;
}

bool Spiel::klientWechseltTeam( int accountId, int team )
{
    if( state != 1 )
        return 0;
    if( team == 0 )
    {
        for( int i = 0; i < spielerAnzahl; i++ )
        {
            if( accounts->hat( i ) && accounts->get( i ) == accountId )
                spielerNummern->set( 0, i );
            SSKlient *tmp = klients->z( i );
            if( tmp )
                tmp->erstellungSpielerTeam( accountId, 0 );
        }
        return 1;
    }
    int von = 1;
    for( int i = 0; i < team - 1; i++ )
    {
        if( sts->teamSize->hat( i ) )
            von += sts->teamSize->get( i );
    }
    int bis = von + ( sts->teamSize->hat( team - 1 ) ? sts->teamSize->get( team - 1 ) : 0 );
    for( int i = von; i < bis; i++ )
    {
        bool verwendet = 0;
        for( int j = 0; j < spielerAnzahl; j++ )
        {
            if( spielerNummern->hat( j ) && spielerNummern->get( j ) == i )
            {
                verwendet = 1;
                break;
            }
        }
        if( !verwendet )
        {
            for( int j = 0; j < spielerAnzahl; j++ )
            {
                if( accounts->hat( j ) && accounts->get( j ) == accountId )
                    spielerNummern->set( i, j );
                SSKlient *tmp = klients->z( j );
                if( tmp )
                    tmp->erstellungSpielerTeam( accountId, i );
            }
            return 1;
        }
    }
    return 0;
}

bool Spiel::chatNachricht( char *nachricht )
{
    if( state != 1 )
        return 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
            tmp->erstellungChatNachricht( nachricht );
    }
    return 1;
}

bool Spiel::klientGetrennt( SSKlient *zKlient )
{
    int accId = zKlient->getAccountId();
    bool ret = 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accId )
        {
            if( status->hat( i ) && status->get( i ) != 0 )
            {
                if( state == 1 )
                {
                    for( int j = 0; j < spielerAnzahl; j++ )
                    {
                        if( accounts->hat( j ) && accounts->get( j ) != accId )
                        {
                            SSKlient *tmp = klients->z( j );
                            tmp->erstellungRemoveSpieler( accId );
                        }
                    }
                }
                if( state == 5 && spiel )
                    spiel->klientOffline( zKlient->getAccountId() );
                if( state == 6 )
                {
                    if( statistik )
                        statistik->klientOffline( zKlient->getAccountId() );
                }
                zKlient->setSpielErstellt( 0 );
                spielerNummern->set( 0, i );
                status->set( 0, i );
                klients->set( 0, i );
                ret = 1;
            }
            break;
        }
    }
    return ret;
}

bool Spiel::klientTeamAuswahlFertig( int accountId )
{
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accountId )
        {
            teamAuswahlFertig->set( 1, i );
            return 1;
        }
    }
    return 0;
}

bool Spiel::klientBereitZumLaden( int accountId )
{
    if( state != 2 )
        return 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accountId )
        {
            status->set( 2, i );
            return 1;
        }
    }
    return 0;
}

bool Spiel::klientSpielLadenProzent( int accountId, int prozent )
{
    if( state != 3 && state != 2 )
        return 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accountId )
        {
            this->prozent->set( prozent, i );
            return 1;
        }
    }
    return 0;
}

bool Spiel::klientBereitZumSpiel( int accountId )
{
    if( state != 4 )
        return 0;
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accountId )
        {
            status->set( 3, i );
            return 1;
        }
    }
    return 0;
}

bool Spiel::spielNachricht( int accountId, int len, char *bytes )
{
    if( state != 5 )
        return 0;
    if( spiel )
    {
        spiel->nachricht( accountId, len, bytes );
        return 1;
    }
    return 0;
}

bool Spiel::statistikNachricht( int accountId, int len, char *bytes )
{
    if( state != 6 )
        return 0;
    if( statistik )
    {
        statistik->statistikAnfrage( accountId, (short)len, bytes );
        return 1;
    }
    return 0;
}

void Spiel::thread()
{
    // Klients aktualisieren
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
        {
            tmp->setSpielErstellt( 0 );
            tmp->setSpiel( this );
        }
    }
    // Vortsetzung senden
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
            tmp->erstellungInitialisierung( sts );
    }
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
            tmp->erstellungFortsetzen();
    }
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
        {
            for( int j = 0; j < spielerAnzahl; j++ )
            {
                if( accounts->hat( j ) )
                    tmp->erstellungAddSpieler( accounts->get( j ) );
            }
        }
    }
    // warten bis alle ein Team gewählt haben
    double time = 0;
    state = 1;
    ZeitMesser *zeit = new ZeitMesser();
    while( time < 40 )
    {
        zeit->messungStart();
        Sleep( 100 );
        char verbleibend = (char)( 40 - (char)time );
        bool weiter = 1;
        for( int i = 0; i < spielerAnzahl; i++ )
            weiter &= teamAuswahlFertig->hat( i ) && teamAuswahlFertig->get( i );
        if( verbleibend < 0 || weiter )
        {
            verbleibend = 0;
            time = 40;
        }
        for( int i = 0; i < spielerAnzahl; i++ )
        {
            SSKlient *tmp = klients->z( i );
            if( tmp )
                tmp->zeitVerbleibend( verbleibend );
        }
        zeit->messungEnde();
        time += zeit->getSekunden();
    } // Spielernummern Aufteilen
    zeit->release();
    state = 2;
    teamAusgleich();
    Array< int > *teams = new Array< int >();
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        int sNum = spielerNummern->hat( i ) ? spielerNummern->get( i ) : 0;
        int tg = 1;
        for( int j = 0; j < sts->teamAnzahl; j++ )
        {
            tg += sts->teamSize->hat( j ) ? sts->teamSize->get( j ) : 0;
            if( sNum < tg )
            {
                teams->set( j, i );
                break;
            }
        }
    }
    ss->zDB()->spielSetTeams( id, spielerAnzahl, accounts, teams );
    ss->zDB()->spielSetSpielerNummern( id, spielerAnzahl, accounts, spielerNummern );
    teams->release();
    // Fortsetzung senden
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
            tmp->spielLadenBeginn();
    }
    // Auf bereitschaft warten
    while( 1 )
    {
        Sleep( 100 );
        bool br = 1;
        for( int i = 0; i < spielerAnzahl; i++ )
        {
            if( klients->z( i ) )
                br = br && status->hat( i ) && status->get( i ) == 2;
        }
        if( br )
            break;
    } // Spieler hinzufügen
    for( int i = 0; i < spielerAnzahl; i++ )
        spielerNummern->set( ( spielerNummern->hat( i ) ? spielerNummern->get( i ) : 0 ) - 1, i );
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
        {
            for( int j = 0; j < spielerAnzahl; j++ )
                tmp->spielLadenSpielerAdd( accounts->hat( j ) ? accounts->get( j ) : 0, spielerNummern->hat( j ) ? spielerNummern->get( j ) : 0 );
        }
    }
    state = 3;
    // Warten auf laden
    while( 1 )
    {
        Sleep( 1000 );
        bool br = 1;
        for( int i = 0; i < spielerAnzahl; i++ )
        {
            SSKlient *tmp = klients->z( i );
            if( tmp )
            {
                int ping = (int)( tmp->ping() * 1000 + 0.5 );
                for( int j = 0; j < spielerAnzahl; j++ )
                {
                    SSKlient *k = klients->z( j );
                    if( k )
                    {
                        k->spielLadenSetSpielerPing( accounts->hat( i ) ? accounts->get( i ) : 0, ping );
                        k->spielLadenSetSpielerProzent( accounts->hat( i ) ? accounts->get( i ) : 0, prozent->hat( i ) ? prozent->get( i ) : 0 );
                    }
                }
                br = br && prozent->hat( i ) && prozent->get( i ) == 100;
            }
        }
        if( br )
            break;
    }
    state = 4;
    // Sende Fortsetzung
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        SSKlient *tmp = klients->z( i );
        if( tmp )
            tmp->spielLadenFertig();
    }
    // Spiel Initialisieren
    Text *pfad = new Text( ss->zInit()->zWert( "SpielePfad" )->getText() );
    pfad->append( "/" );
    pfad->append( ss->zDB()->getSpielArtId( karte ) );
    pfad->append( "/bin/game" );
#ifdef WIN32
    pfad->append( ".dll" );
#else
    pfad->append( ".so" );
#endif
    spielDll = LoadLibrary( pfad->getText() );
    if( !spielDll )
    {
        Text tmp = ss->zInit()->zWert( "TempMapPfad" )->getText();
        tmp += "/";
        tmp += id;
        DateiRemove( tmp );
        for( int i = 0; i < spielerAnzahl; i++ )
            klients->z( i )->errorZuKlientSende( "Fehler beim laden des Spiels. Der Server kann das Spiel nict finden." );
        ss->zDB()->setSpielStatusBeendet( id, 4 );
        ss->removeSpiel( id ); // delete this
        return;
    }
    pfad->release();
    DllStart getSpielKlasse = (DllStart)GetProcAddress( spielDll, "getSpielKlasse" );
    if( !getSpielKlasse )
    {
        FreeLibrary( spielDll );
        spielDll = 0;
        Text tmp = ss->zInit()->zWert( "TempMapPfad" )->getText();
        tmp += "/";
        tmp += id;
        DateiRemove( tmp );
        for( int i = 0; i < spielerAnzahl; i++ )
            klients->z( i )->errorZuKlientSende( "Fehler beim laden des Spiels. Der Server kann das Spiel nict finden." );
        ss->zDB()->setSpielStatusBeendet( id, 4 );
        ss->removeSpiel( id ); // delete this
        return;
    }
    spiel = getSpielKlasse();
    spiel->setPSQLK( ss->zDB()->getThis() );
    spiel->setSpielId( id );
    spiel->setKarteId( karte );
    Text tmp = ss->zInit()->zWert( "TempMapPfad" )->getText();
    tmp += "/";
    tmp += id;
    spiel->setTempPfad( tmp );
    spiel->setAccounts( spielerAnzahl, accounts );
    spiel->setKlients( spielerAnzahl, ( RCArray< SSKlientV >* )klients );
    spiel->setSpielerNummern( spielerAnzahl, spielerNummern );
    // auf klients warten
    while( 1 )
    {
        Sleep( 100 );
        bool br = 1;
        for( int i = 0; i < spielerAnzahl; i++ )
        {
            if( klients->z( i ) )
                br = br && status->hat( i ) && status->get( i ) == 3;
        }
        if( br )
            break;
    }
    // Spiel starten
    state = 5;
    ss->zDB()->setSpielStatusIsRunning( id );
    spiel->run();
    // Spiel ist zuende
    state = 6;
    statistik = spiel->getStatistik();
    spiel = spiel->release();
    statistik->run();
    // Statistik ist zuende
    state = 7;
    statistik = statistik->release();
    FreeLibrary( spielDll );
    spielDll = 0;
    DateiRemove( tmp );
    ss->removeSpiel( id ); // delete this
}

// constant
int Spiel::getId() const
{
    return id;
}

int Spiel::getSpielerNummer( int accountId ) const
{
    for( int i = 0; i < spielerAnzahl; i++ )
    {
        if( accounts->hat( i ) && accounts->get( i ) == accountId )
            return spielerNummern->hat( i ) ? spielerNummern->get( i ) : 0;
    }
    return 0;
}

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

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