#include "HistorieServer.h"
#include <iostream>
#include "SpielServerKlient.h"
#include <Globals.h>

// Inhalt der HistorieServer Klasse aus HistorieServer.h
// Konstruktor 
HistorieServer::HistorieServer( InitDatei *zIni )
    : Thread()
{
    Network::Start( 100 );
    std::cout << "HS: Verbindung mit Datenbank wird hergestellt...\n";
    db = new HSDatenbank( zIni );
    klientAnzahl = 0;
    klients = new RCArray< HSKlient >();
    empfangen = 0;
    gesendet = 0;
    fehler = new Text();
    ini = zIni->getThis();
    id = *zIni->zWert( "ServerId" );
    server = new Server();
    aServer = new SSLServer();
    aServer->setPrivateKeyPassword( zIni->zWert( "SSLPasswort" )->getText() );
    aServer->setCertificateFile( zIni->zWert( "SSLCert" )->getText() );
    aServer->setPrivateKeyFile( zIni->zWert( "SSLKey" )->getText() );
    std::cout << "HS: Starten des Admin Servers...\n";
    if( !aServer->verbinde( (unsigned short)TextZuInt( ini->zWert( "AdminServerPort" )->getText(), 10 ), 10 ) )
    {
        std::cout << "HS: Der Admin Server konnte nicht gestartet werden. Das Programm wird beendet.\n";
        exit( 1 );
    }
    db->setServerStatus( id, 2 );
    end = 0;
    nichtPausiert = 0;
    InitializeCriticalSection( &cs );
    if( zIni->zWert( "Aktiv" )->istGleich( "TRUE" ) )
    {
        serverStarten();
        serverFortsetzen();
    }
}

// Destruktor 
HistorieServer::~HistorieServer()
{
    fehler->release();
    server->trenne();
    server->release();
    aServer->trenne();
    aServer->release();
    if( klients )
        klients->release();
    ini->release();
    db->release();
    DeleteCriticalSection( &cs );
}

// nicht constant 
void HistorieServer::runn()
{
    while( !end && aServer->isConnected() )
    {
        SSLSKlient *klient;
        klient = aServer->getKlient();
        if( end && klient )
        {
            klient->trenne();
            klient = klient->release();
            Sleep( 1000 );
            return;
        }
        if( !klient )
            continue;
        HSAKlient * clHandle = new HSAKlient( klient, (HistorieServer *)getThis() );
        clHandle->start();
    }
}

void HistorieServer::thread()
{
    while( server->isConnected() )
    {
        SKlient *klient;
        klient = server->getKlient();
        if( !klient )
            continue;
        Framework::getThreadRegister()->cleanUpClosedThreads();
        HSKlient * clHandle = new HSKlient( klient, (HistorieServer *)getThis() );
        EnterCriticalSection( &cs );
        klients->set( clHandle, klientAnzahl );
        klientAnzahl++;
        LeaveCriticalSection( &cs );
        clHandle->start();
    }
}

void HistorieServer::close()
{
    db->setServerStatus( id, 1 );
    server->trenne();
    EnterCriticalSection( &cs );
    for( int i = 0; i < klientAnzahl; i++ )
        klients->z( i )->absturz();
    klients = klients->release();
    klientAnzahl = 0;
    LeaveCriticalSection( &cs );
    ende();
    run = 0;
    end = 1;
    Klient * klient = new Klient();
    klient->verbinde( aServer->getPort(), "127.0.0.1" );
    Sleep( 500 );
    aServer->trenne();
    klient->release();
}

bool HistorieServer::serverStarten()
{
    if( nichtPausiert )
    {
        fehler->setText( "Der Server konnte nicht gestartet werden: Der Server läuft bereits." );
        return 0;
    }
    if( server )
        server->release();
    server = new Server();
    if( server->verbinde( (unsigned short)TextZuInt( ini->zWert( "ServerPort" )->getText(), 10 ), 10 ) )
    {
        nichtPausiert = 1;
        start();
        return 1;
    }
    else
    {
        serverBeenden();
        fehler->setText( "Der Server konnte nicht gestartet werden: Eventuell ist der Port in benutzung." );
        return 0;
    }
}

bool HistorieServer::serverPause()
{
    if( !nichtPausiert )
    {
        fehler->setText( "Der Server konnte nicht pausiert werden: Der Server läuft nicht." );
        return 0;
    }
    if( !db->setServerStatus( id, 2 ) )
    {
        fehler->setText( "Der Server konnte nicht pausiert werden: " );
        fehler->append( db->getLetzterFehler() );
        return 0;
    }
    return 1;
}

bool HistorieServer::serverFortsetzen()
{
    if( !nichtPausiert )
    {
        fehler->setText( "Der Server konnte nicht fortgesetzt werden: Der Server läuft nicht." );
        return 0;
    }
    if( !db->setServerStatus( id, 3 ) )
    {
        fehler->setText( "Der Server konnte nicht fortgesetzt werden: " );
        fehler->append( db->getLetzterFehler() );
        return 0;
    }
    return 1;
}

bool HistorieServer::serverBeenden()
{
    if( !nichtPausiert )
    {
        fehler->setText( "Der Server konnte nicht beendet werden: Der Server läuft nicht." );
        return 0;
    }
    if( db->serverIstNichtPausiert( id ) )
    {
        fehler->setText( "Der Server konnte nicht beendet werden: Der Server muss erst pausiert werden." );
        return 0;
    }
    nichtPausiert = 0;
    ende();
    if( server )
        server->trenne();
    return 1;
}

bool HistorieServer::setMaxSpiele( int ms )
{
    if( !db->setMaxSpiele( id, ms ) )
    {
        fehler->setText( "Die maximale Anzahl der Spiele konnte nicht gesetzt werden:\n" );
        fehler->append( db->getLetzterFehler() );
        return 0;
    }
    ini->setWert( "MaxSpiele", Text() += ms );
    return 1;
}

bool HistorieServer::absturzKlient( int klientId )
{
    bool gefunden = 0;
    EnterCriticalSection( &cs );
    for( int i = 0; i < klientAnzahl; i++ )
    {
        if( klients->z( i ) && klients->z( i )->getKlientNummer() == klientId )
        {
            klients->z( i )->absturz();
            klients->remove( i );
            klientAnzahl--;
            gefunden = 1;
            break;
        }
    }
    LeaveCriticalSection( &cs );
    return gefunden;
}

bool HistorieServer::removeKlient( HSKlient * zKlient )
{
    bool gefunden = 0;
    EnterCriticalSection( &cs );
    for( int i = 0; i < klientAnzahl; i++ )
    {
        if( klients->z( i ) == zKlient )
        {
            klients->remove( i );
            klientAnzahl--;
            gefunden = 1;
            break;
        }
    }
    LeaveCriticalSection( &cs );
    return gefunden;
}

void HistorieServer::addGesendet( int bytes )
{
    gesendet += bytes;
}

void HistorieServer::addEmpfangen( int bytes )
{
    empfangen += bytes;
}

// constant
bool HistorieServer::istAn() const
{
    return db->serverIstNichtPausiert( id );
}

Server *HistorieServer::zServer() const
{
    return server;
}

HSDatenbank *HistorieServer::zDB() const
{
    return db;
}

bool HistorieServer::hatClients() const
{
    return klientAnzahl > 0;
}

int HistorieServer::getId() const
{
    return id;
}

char *HistorieServer::getLetzterFehler() const
{
    return fehler->getText();
}

InitDatei *HistorieServer::zIni() const
{
    return ini;
}


// Inhalt der HSAKlient Klasse aus HistorieServer.h
// Konstruktor 
HSAKlient::HSAKlient( SSLSKlient * klient, HistorieServer * hs )
    : Thread()
{
    this->klient = klient;
    name = new Text( "" );
    passwort = new Text( "" );
    adminId = 0;
    version = 0;
    this->hs = hs;
}

// Destruktor 
HSAKlient::~HSAKlient()
{
    klient->trenne();
    klient->release();
    hs->release();
    name->release();
    passwort->release();
}

// nicht constant 
void HSAKlient::thread()
{
    while( 1 )
    {
        char c = 0;
        if( !klient->getNachricht( &c, 1 ) )
            break;
        else
        {
            bool br = 0;
            switch( c )
            {
            case 1: // Login
                if( 1 )
                {
                    klient->sende( "\1", 1 );
                    unsigned char nLen = 0;
                    klient->getNachricht( (char *)& nLen, 1 );
                    char *n = new char[ nLen + 1 ];
                    n[ (int)nLen ] = 0;
                    if( nLen )
                        klient->getNachricht( n, nLen );
                    unsigned char pLen = 0;
                    klient->getNachricht( (char *)& pLen, 1 );
                    char *p = new char[ pLen + 1 ];
                    p[ (int)pLen ] = 0;
                    if( pLen )
                        klient->getNachricht( p, pLen );
                    int adminId = hs->zDB()->istAdministrator( n, p );
                    if( adminId )
                    {
                        klient->sende( "\1", 1 );
                        name->setText( n );
                        passwort->setText( p );
                        this->adminId = adminId;
                    }
                    else
                        errorZuKlient( "Falsche Kombination aus Name und Passwort." );
                    delete[] n;
                    delete[] p;
                }
                break;
            case 2: // Logout
                adminId = 0;
                name->setText( "" );
                passwort->setText( "" );
                klient->sende( "\1", 1 );
                break;
            case 3: // Trennen
                br = 1;
                klient->sende( "\1", 1 );
                break;
            case 4: // Server starten
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSStarten ) )
                    {
                        if( !hs->serverStarten() )
                        {
                            Text *err = new Text();
                            err->append( hs->getLetzterFehler() );
                            errorZuKlient( err->getText() );
                            err->release();
                        }
                        else
                            klient->sende( "\1", 1 );
                    }
                    else
                        errorZuKlient( "Du bist nicht berechtigt den Server zu starten." );
                }
                break;
            case 5: // Server beenden
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSBeenden ) )
                    {
                        if( hs->serverBeenden() )
                            klient->sende( "\1", 1 );
                        else
                        {
                            Text *err = new Text();
                            err->append( hs->getLetzterFehler() );
                            errorZuKlient( err->getText() );
                            err->release();
                        }
                    }
                    else
                        errorZuKlient( "Du bist nicht berechtigt den Server zu beenden." );
                }
                break;
            case 6: // Programm Schließen
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    bool ok = 0;
                    if( hs->isRunning() )
                    {
                        if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSBeenden ) )
                        {
                            if( hs->serverBeenden() )
                                ok = 1;
                            else
                            {
                                Text *err = new Text();
                                err->append( hs->getLetzterFehler() );
                                errorZuKlient( err->getText() );
                                err->release();
                            }
                        }
                        else
                            errorZuKlient( "Du bist nicht berechtigt den Server zu beenden." );
                    }
                    else
                        ok = 1;
                    if( ok &&hs->hatClients() )
                    {
                        errorZuKlient( "Es sind noch Klients Online. Bitte versuche es später erneut." );
                        break;
                    }
                    if( ok )
                    {
                        klient->sende( "\1", 1 );
                        std::cout << "HS: Der Server wird von Benutzer " << adminId << " heruntergefahren.\n";
                        hs->close();
                        br = 1;
                    }
                }
                break;
            case 7: // Progtamm abstürzen
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    bool ok = 0;
                    if( hs->isRunning() )
                    {
                        if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSBeenden ) )
                        {
                            hs->serverBeenden();
                            ok = 1;
                        }
                        else
                            errorZuKlient( "Du bist nicht berechtigt den Server zu beenden." );
                    }
                    else
                        ok = 1;
                    if( ok )
                    {
                        klient->sende( "\1", 1 );
                        std::cout << "HS: Der Server wurde von Benutzer " << adminId << " terminiert.\n";
                        hs->close();
                        br = 1;
                    }
                }
                break;
            case 8: // Status Frage
                if( 1 )
                {
                    char status = 0;
                    if( hs->isRunning() )
                    {
                        status = 1;
                        if( hs->istAn() )
                            status = 2;
                    }
                    klient->sende( "\1", 1 );
                    klient->sende( &status, 1 );
                }
                break;
            case 9: // Server pausieren
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    klient->sende( "\1", 1 );
                    char pause = 0;
                    klient->getNachricht( &pause, 1 );
                    if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSPausieren ) )
                    {
                        bool ok = 0;
                        if( pause )
                            ok = hs->serverPause();
                        else
                            ok = hs->serverFortsetzen();
                        if( ok )
                            klient->sende( "\1", 1 );
                        else
                        {
                            Text *err = new Text();
                            err->append( hs->getLetzterFehler() );
                            errorZuKlient( err->getText() );
                            err->release();
                        }
                    }
                    else
                    {
                        if( pause )
                            errorZuKlient( "Du bist nicht berechtigt den Server zu pausieren." );
                        else
                            errorZuKlient( "Du bist nicht berechtigt den Server fortzusetzen." );
                    }
                }
                break;
            case 0xA: // maximale Anzahl der Spiele setzen
                if( !adminId )
                    errorZuKlient( "Du musst dich einloggen." );
                else
                {
                    klient->sende( "\1", 1 );
                    int maxS = 0;
                    klient->getNachricht( (char *)& maxS, 4 );
                    if( hs->zDB()->adminHatRecht( adminId, Admin_Recht::HSMSChange ) )
                    {
                        if( hs->setMaxSpiele( maxS ) )
                            klient->sende( "\1", 1 );
                        else
                        {
                            Text *err = new Text();
                            err->append( hs->getLetzterFehler() );
                            errorZuKlient( err->getText() );
                            err->release();
                        }
                    }
                    else
                        errorZuKlient( "Du bist nicht berechtigt die maximale Anzahl der Spiele zu verändern." );
                }
                break;
            case 0xC: // klient absturtz
                if( 1 )
                {
                    klient->sende( "\1", 1 );
                    int klientId = 0;
                    klient->getNachricht( (char *)& klientId, 4 );
                    if( klientId &&hs->absturzKlient( klientId ) )
                        klient->sende( "\1", 1 );
                    else
                        klient->sende( "\0", 1 );
                }
                break;
            default:
                errorZuKlient( "Unbekannte Nachricht!" );
                break;
            }
            if( br )
                break;
            hs->addEmpfangen( klient->getDownloadBytes( 1 ) );
            hs->addGesendet( klient->getUploadBytes( 1 ) );
        }
    }
    hs->addEmpfangen( klient->getDownloadBytes( 1 ) );
    hs->addGesendet( klient->getUploadBytes( 1 ) );
    delete this;
}

void HSAKlient::errorZuKlient( const char *nachricht ) const // sendet eine Fehlernachricht zum Klient
{
    klient->sende( "\3", 1 );
    char len = (char)textLength( nachricht );
    klient->sende( &len, 1 );
    klient->sende( nachricht, len );
}


// Inhalt der HSKlient aus HistorieServer.h
// Konstruktor
HSKlient::HSKlient( SKlient * klient, HistorieServer * hs )
    : Thread()
{
    this->klient = klient;
    unsigned char key[ 20 ] = { 207, 30, 72, 46, 30, 50, 56, 213, 82, 107, 14, 201, 149, 58, 110, 138, 228, 241, 52, 54 };
    klient->setSendeKey( (char *)key, 20 );
    klient->setEmpfangKey( (char *)key, 20 );
    klientNummer = 0;
    this->hs = hs;
}

// Destruktor 
HSKlient::~HSKlient()
{
    klient->release();
    hs->release();
}

// privat
void HSKlient::sendeVerzeichnis( char *pfad, int gpl )
{
    if( DateiIstVerzeichnis( pfad ) )
    {
        Datei *d = new Datei();
        d->setDatei( pfad );
        int dAnz = d->getUnterdateiAnzahl();
        RCArray< Text > *dList = d->getDateiListe();
        for( int i = 0; i < dAnz; i++ )
        {
            Text *pf = new Text( pfad );
            pf->append( "/" );
            pf->append( dList->z( i )->getText() );
            sendeVerzeichnis( *pf, gpl );
            pf->release();
        }
        dList->release();
        d->release();
    }
    else
    {
        Datei *d = new Datei();
        d->setDatei( pfad );
        if( !d->open( Datei::Style::lesen ) )
        {
            d->release();
            return;
        }
        char len = (char)( textLength( pfad ) - gpl );
        if( len <= 0 )
        {
            d->release();
            return;
        }
        klient->sendeEncrypted( &len, 1 );
        klient->sendeEncrypted( pfad + gpl, len );
        __int64 gr = d->getSize();
        klient->sendeEncrypted( (char *)& gr, 8 );
        char *bytes = new char[ 2048 ];
        while( gr > 0 )
        {
            int bLen = gr > 2048 ? 2048 : (int)gr;
            d->lese( bytes, bLen );
            klient->sende( bytes, bLen );
            gr -= bLen;
        }
        delete[] bytes;
        d->close();
        d->release();
    }
}

// nicht constant 
void HSKlient::absturz()
{
    ende();
    klient->trenne();
    hs->zDB()->unregisterKlient( klientNummer, hs->getId() );
}

void HSKlient::thread()
{
    while( 1 )
    {
        char c = 0;
        if( !klient->getNachrichtEncrypted( &c, 1 ) )
            break;
        else
        {
            bool br = 0;
            switch( c )
            {
            case 1: // Klient identifikation
                klient->getNachrichtEncrypted( (char *)& klientNummer, 4 );
                if( !hs->zDB()->proveKlient( klientNummer, hs->getId() ) )
                {
                    klientNummer = 0;
                    errorZuKlient( "Du bist nicht für diesen Server eingetragen" );
                }
                else
                {
                    Text *key = hs->zDB()->getKlientKey( klientNummer );
                    if( !key )
                        errorZuKlient( "Es konnte kein Schlüssel ermittelt werden." );
                    else
                    {
                        klient->sendeEncrypted( "\1", 1 );
                        klient->setEmpfangKey( *key, key->getLength() );
                        klient->setSendeKey( *key, key->getLength() );
                        key->release();
                    }
                }
                break;
            case 2: // Main / Erhaltung Server message
                if( 1 )
                {
                    char befehl = 0;
                    klient->getNachrichtEncrypted( &befehl, 1 );
                    switch( befehl )
                    {
                    case 2: // klient absturtz
                        if( 1 )
                        {
                            int klientId = 0;
                            klient->getNachrichtEncrypted( (char *)& klientId, 4 );
                            if( klientId &&hs->absturzKlient( klientId ) )
                                klient->sendeEncrypted( "\1", 1 );
                            else
                                klient->sendeEncrypted( "\0", 1 );
                        }
                        break;
                    default:
                        errorZuKlient( "Befehl nicht bekannt!" );
                        break;
                    }
                }
                break;
            case 3: // Verbindungsende
                br = 1;
                klient->sendeEncrypted( "\1", 1 );
                break;
            case 4: // unregister Klient
                if( !klientNummer )
                {
                    errorZuKlient( "Du bist nicht Identifiziert." );
                    break;
                }
                hs->zDB()->unregisterKlient( klientNummer, hs->getId() );
                klient->sendeEncrypted( "\1", 1 );
                break;
            case 5: // Neue Spiel Historie hinzufügen
                if( klientNummer )
                {
                    errorZuKlient( "Zugriff verweigert." );
                    break;
                }
                else
                {
                    klient->sendeEncrypted( "\1", 1 );
                    int spielServerId = 0;
                    int spielId = 0;
                    klient->getNachrichtEncrypted( (char *)& spielServerId, 4 );
                    klient->getNachrichtEncrypted( (char *)& spielId, 4 );
                    if( !hs->zDB()->istSpielHistorieFrei( spielId ) )
                    {
                        errorZuKlient( "Die Spiel-Historie dieses Spiels ist bereits eingeordnet." );
                        break;
                    }
                    short port;
                    Text *ip = new Text();
                    if( !hs->zDB()->getSpielServerPortIp( spielServerId, &port, ip ) )
                    {
                        ip->release();
                        errorZuKlient( "Ungültige Server Id." );
                        break;
                    }
                    SpielServerKlient *k = new SpielServerKlient();
                    if( !hs->zIni()->wertExistiert( "SpielHistoriePfad" ) )
                        hs->zIni()->addWert( "SpielHistoriePfad", "../spiel_historie" );
                    if( !k->downloadSpielHistorie( port, *ip, spielId, hs->zIni()->zWert( "SpielHistoriePfad" )->getText() ) )
                    {
                        Text err( "Fehler beim download: " );
                        errorZuKlient( err += k->zLetzterFehler()->getText() );
                        k->release();
                        ip->release();
                        break;
                    }
                    k->release();
                    ip->release();
                    klient->sendeEncrypted( "\1", 1 );
                    hs->zDB()->addSpielHistorie( hs->getId(), spielId );
                }
            case 6: // Frage nach Spiel Historie
                if( klientNummer )
                {
                    klient->sendeEncrypted( "\1", 1 );
                    int spielId = 0;
                    klient->getNachrichtEncrypted( (char *)& spielId, 4 );
                    if( !hs->zIni()->wertExistiert( "SpielHistoriePfad" ) )
                        hs->zIni()->addWert( "SpielHistoriePfad", "../spiel_historie" );
                    Text * pf = new Text( hs->zIni()->zWert( "SpielHistoriePfad" )->getText() );
                    if( pf->getText()[ pf->getLength() - 1 ] != '/' )
                        pf->append( "/" );
                    pf->append( spielId );
                    if( !DateiIstVerzeichnis( *pf ) )
                    {
                        errorZuKlient( "Es existieren keine Daten zu diesem Spiel." );
                        break;
                    }
                    klient->sendeEncrypted( "\1", 1 );
                    sendeVerzeichnis( *pf, pf->getLength() );
                    pf->release();
                    klient->sendeEncrypted( "\0", 1 );
                }
                else
                    errorZuKlient( "Du bist nicht Identifiziert." );
                break;
            default:
                errorZuKlient( "Unbekannte Nachricht!" );
                break;
            }
            if( br )
                break;
            hs->addEmpfangen( klient->getDownloadBytes( 1 ) );
            hs->addGesendet( klient->getUploadBytes( 1 ) );
        }
    }
    hs->addEmpfangen( klient->getDownloadBytes( 1 ) );
    hs->addGesendet( klient->getUploadBytes( 1 ) );
    hs->removeKlient( this ); // delete this
}

// constant
void HSKlient::errorZuKlient( const char *nachricht ) const // sendet eine Fehlernachricht zum Klient
{
    klient->sendeEncrypted( "\3", 1 );
    char len = (char)textLength( nachricht );
    klient->sendeEncrypted( &len, 1 );
    klient->sendeEncrypted( nachricht, len );
}

int HSKlient::getKlientNummer() const // gibt die KlientId zurück
{
    return klientNummer;
}