#pragma once

#include <Klient.h>
#include <Text.h>
#include "Keys.h"
#include <iostream>
#include <KSGTDatei.h>
#include <Datei.h>

class PSClient
{
private:
    int id;
    Network::Klient *klient;
    char *schlüssel;
    unsigned char schlüsselLän;
    int port;
    char *ip;
    int ref;

public:
    // Konstruktor
    PSClient( int klientId, unsigned short port, char *ip, char *key, unsigned char keyLen )
    {
        klient = 0;
        id = klientId;
        this->port = port;
        this->ip = new char[ textLength( ip ) + 1 ];
        memcpy( this->ip, ip, textLength( ip ) + 1 );
        schlüsselLän = keyLen;
        schlüssel = new char[ keyLen ];
        memcpy( schlüssel, key, keyLen );
        ref = 1;
    }
    // Destruktor
    ~PSClient()
    {
        delete[] schlüssel;
        if( klient )
            trenne( 1 );
        delete[] ip;
        if( klient )
            klient->release();
    }
    // nicht constant
    bool verbinde()
    {
        if( klient )
            return 1;
        klient = new Network::Klient();
        int l = 0;
        char *key;
        Keys::getServerKey( &key, l, Keys::PATCH, Keys::SENDEN );
        klient->setSendeKey( key, l );
        delete[] key;
        Keys::getServerKey( &key, l, Keys::PATCH, Keys::EMPFANGEN );
        klient->setEmpfangKey( key, l );
        delete[] key;
        if( klient->verbinde( port, ip ) )
        {
            klient->sende( "\0", 1 ); // Verschlüsselung Aktivieren
            if( klient->sendeEncrypted( "\1", 1 ) )
            {
                klient->sendeEncrypted( (char*)&id, 4 );
                klient->sendeEncrypted( "\2", 1 ); // 64 Bit Client
                char serverReturn = 0;
                klient->getNachrichtEncrypted( &serverReturn, 1 );
                if( serverReturn == 3 )
                {
                    char byte = 0;
                    klient->getNachrichtEncrypted( &byte, 1 );
                    char *f = new char[ byte + 1 ];
                    f[ byte ] = 0;
                    klient->getNachrichtEncrypted( f, byte );
                    std::cerr << "error while identifyin client Patch Server returned: " << f << "\n";
                    delete[]f;
                    klient->sendeEncrypted( "\3", 1 );
                    klient->getNachrichtEncrypted( &serverReturn, 1 );
                    klient->trenne();
                    return 0;
                }
                klient->setSendeKey( (char*)schlüssel, schlüsselLän );
                klient->setEmpfangKey( (char*)schlüssel, schlüsselLän );
            }
            else
            {
                std::cerr << "network error while sending to Patch Server\n";
                klient = klient->release();
                return 0;
            }
        }
        else
        {
            std::cerr << "network error while connecting to Patch Server\n";
            klient = klient->release();
            return 0;
        }
        return 1;
    }
    KSGTDatei *getDateiGruppenListe()
    {
        verbinde();
        if( !klient )
            return 0;
        klient->sendeEncrypted( "\xC", 1 );
        char ret = 0;
        klient->getNachrichtEncrypted( &ret, 1 );
        if( ret == 3 )
        { // error
            klient->getNachrichtEncrypted( &ret, 1 );
            char *msg = new char[ ret + 1 ];
            msg[ ret ] = 0;
            if( ret )
                klient->getNachrichtEncrypted( msg, ret );
            std::cerr << "error while getDateiGruppenListe Patch Server returned: " << msg << "\n";
            delete[] msg;
            return 0;
        }
        KSGTDatei *dgtb = new KSGTDatei();
        int id = 0;
        klient->getNachrichtEncrypted( (char*)&id, 4 );
        while( id )
        {
            RCArray< Text > *zeile = new RCArray< Text >();
            Text *idT = new Text();
            idT->append( id );
            zeile->set( idT, 0 );
            int län = 0;
            klient->getNachrichtEncrypted( (char*)&län, 4 );
            char *pfad = new char[ län + 1 ];
            pfad[ län ] = 0;
            if( län )
                klient->getNachrichtEncrypted( pfad, län );
            zeile->set( new Text( pfad ), 1 );
            delete[] pfad;
            int vs = 0;
            klient->getNachrichtEncrypted( (char*)&vs, 4 );
            Text *vsT = new Text();
            vsT->append( vs );
            zeile->set( vsT, 2 );
            dgtb->addZeile( 3, zeile );
            zeile->release();
            klient->getNachrichtEncrypted( (char*)&id, 4 );
        }
        klient->sendeEncrypted( "\1", 1 );
        return dgtb;
    }
    KSGTDatei *getDateiListe( int gruppe )
    {
        verbinde();
        if( !klient )
            return 0;
        klient->sendeEncrypted( "\x9", 1 );
        char ret = 0;
        klient->getNachrichtEncrypted( &ret, 1 );
        if( ret == 1 )
        {
            klient->sendeEncrypted( (char*)&gruppe, 4 );
            klient->getNachrichtEncrypted( &ret, 1 );
        }
        if( ret == 3 )
        { // error
            klient->getNachrichtEncrypted( &ret, 1 );
            char *msg = new char[ ret + 1 ];
            msg[ ret ] = 0;
            if( ret )
                klient->getNachrichtEncrypted( msg, ret );
            std::cerr << "error while getDateiListe Patch Server returned: " << msg << "\n";
            delete[] msg;
            return 0;
        }
        KSGTDatei *dtb = new KSGTDatei();
        int län = 0;
        klient->getNachrichtEncrypted( (char*)&län, 4 );
        while( län )
        {
            RCArray< Text > *zeile = new RCArray< Text >();
            zeile->set( new Text( "1" ), 0 );
            char *pfad = new char[ län + 1 ];
            pfad[ län ] = 0;
            klient->getNachrichtEncrypted( pfad, län );
            zeile->set( new Text( pfad ), 1 );
            delete[] pfad;
            int vs = 0;
            klient->getNachrichtEncrypted( (char*)&vs, 4 );
            Text *vsT = new Text();
            vsT->append( vs );
            zeile->set( vsT, 2 );
            dtb->addZeile( 3, zeile );
            zeile->release();
            klient->getNachrichtEncrypted( (char*)&län, 4 );
        }
        klient->getNachrichtEncrypted( (char*)&län, 4 );
        while( län )
        {
            RCArray< Text > *zeile = new RCArray< Text >();
            zeile->set( new Text( "0" ), 0 );
            char *pfad = new char[ län + 1 ];
            pfad[ län ] = 0;
            klient->getNachrichtEncrypted( pfad, län );
            zeile->set( new Text( pfad ), 1 );
            delete[] pfad;
            dtb->addZeile( 2, zeile );
            zeile->release();
            klient->getNachrichtEncrypted( (char*)&län, 4 );
        }
        return dtb;
    }
    __int64 getDateiGröße( int gruppe, const char *pfad )
    {
        verbinde();
        if( !klient )
            return 0;
        klient->sendeEncrypted( "\xA", 1 );
        char ret = 0;
        klient->getNachrichtEncrypted( &ret, 1 );
        if( ret == 1 )
        {
            klient->sendeEncrypted( (char*)&gruppe, 4 );
            klient->getNachrichtEncrypted( &ret, 1 );
        }
        if( ret == 3 )
        { // error
            klient->getNachrichtEncrypted( &ret, 1 );
            char *msg = new char[ ret + 1 ];
            msg[ ret ] = 0;
            if( ret )
                klient->getNachrichtEncrypted( msg, ret );
            std::cerr << "error while getDateiGröße Patch Server returned: " << msg << "\n";
            delete[] msg;
            return 0;
        }
        if( ret == 2 )
        { // verweis auf anderen Server
            klient->getNachrichtEncrypted( (char*)&port, 4 );
            int län = 0;
            klient->getNachrichtEncrypted( (char*)&län, 4 );
            delete[] ip;
            ip = new char[ län + 1 ];
            ip[ län ] = 0;
            klient->getNachrichtEncrypted( ip, län );
            if( !trenne( 0 ) )
            {
                delete[] ip;
                return 0;
            }
            if( !verbinde() )
            {
                delete[] ip;
                return 0;
            }
            delete[] ip;
            return getDateiGröße( gruppe, pfad );
        }
        int län = textLength( pfad );
        klient->sendeEncrypted( (char*)&län, 4 );
        klient->sendeEncrypted( pfad, län );
        __int64 größe = 0;
        klient->getNachrichtEncrypted( (char*)&größe, 8 );
        return größe;
    }
    bool downloadDatei( int gruppe, __int64 *start, const char *pfad, const char *zielPfad )
    {
        verbinde();
        if( !klient )
            return 0;
        Datei *zielD = new Datei();
        zielD->setDatei( zielPfad );
        if( !zielD->existiert() )
            zielD->erstellen();
        bool ok = 0;
        if( !( *start ) ) // datei neu erstellen
            ok = zielD->open( Datei::Style::schreiben );
        else // datei weiterschreiben
            ok = zielD->open( Datei::Style::schreiben | Datei::Style::lesen );
        if( !ok )
        {
            zielD->release();
            return 0;
        }
        zielD->setSPosition( *start, 0 );
        klient->sendeEncrypted( "\xB", 1 );
        char ret = 0;
        klient->getNachrichtEncrypted( &ret, 1 );
        if( ret == 1 )
        {
            klient->sendeEncrypted( (char*)&gruppe, 4 );
            klient->getNachrichtEncrypted( &ret, 1 );
        }
        if( ret == 3 )
        { // error
            klient->getNachrichtEncrypted( &ret, 1 );
            char *msg = new char[ ret + 1 ];
            msg[ ret ] = 0;
            if( ret )
                klient->getNachrichtEncrypted( msg, ret );
            std::cerr << "error while downloadDatei Patch Server returned: " << msg << "\n";
            delete[] msg;
            zielD->close();
            zielD->release();
            return 0;
        }
        if( ret == 2 )
        { // verweis auf anderen Server
            klient->getNachrichtEncrypted( (char*)&port, 4 );
            int län = 0;
            klient->getNachrichtEncrypted( (char*)&län, 4 );
            delete[] ip;
            ip = new char[ län + 1 ];
            ip[ län ] = 0;
            klient->getNachrichtEncrypted( ip, län );
            if( !trenne( 0 ) )
            {
                delete[] ip;
                zielD->close();
                zielD->release();
                return 0;
            }
            if( !verbinde() )
            {
                delete[] ip;
                zielD->close();
                zielD->release();
                return 0;
            }
            delete[] ip;
            zielD->close();
            zielD->release();
            return downloadDatei( gruppe, start, pfad, zielPfad );
        }
        int län = textLength( pfad );
        klient->sendeEncrypted( (char*)&län, 4 );
        klient->sendeEncrypted( pfad, län );
        klient->sendeEncrypted( (char*)start, 8 );
        char *bytes = new char[ 2048 ];
        unsigned short byteLän = 0;
        klient->getNachricht( (char*)&byteLän, 2 );
        if( byteLän )
        {
            int bps = 0;
            double sec = 0;
            while( 1 )
            {
                klient->getNachricht( bytes, byteLän );
                zielD->schreibe( bytes, byteLän );
                ( *start ) += byteLän;
                bps += byteLän;
                if( byteLän == 2048 )
                {
                    klient->getNachricht( (char*)&byteLän, 2 ); // sendelänge empfangen
                    if( !byteLän )
                        break;
                }
                else
                    break;
            }
        }
        delete[] bytes;
        klient->sendeEncrypted( "\1", 1 );
        zielD->close();
        zielD->release();
        return 1;
    }
    bool trenne( bool abmelden )
    {
        verbinde();
        if( !klient )
            return 1;
        if( abmelden )
        {
            klient->sendeEncrypted( "\4", 1 );
            char ret = 0;
            klient->getNachrichtEncrypted( &ret, 1 );
            if( ret == 3 )
            { // error
                klient->getNachrichtEncrypted( &ret, 1 );
                char *msg = new char[ ret + 1 ];
                msg[ ret ] = 0;
                if( ret )
                    klient->getNachrichtEncrypted( msg, ret );
                std::cerr << "error while unregister Client Patch Server returned: " << msg << "\n";
                delete[] msg;
            }
        }
        klient->sendeEncrypted( "\3", 1 );
        char ret = 0;
        klient->getNachrichtEncrypted( &ret, 1 );
        if( ret == 3 )
        { // error
            klient->getNachrichtEncrypted( &ret, 1 );
            char *msg = new char[ ret + 1 ];
            msg[ ret ] = 0;
            if( ret )
                klient->getNachrichtEncrypted( msg, ret );
            std::cerr << "error while trenne Patch Server returned: " << msg << "\n";
            delete[] msg;
        }
        klient->trenne();
        klient = klient->release();
        return 1;
    }
    // Reference Counting
    PSClient *getThis()
    {
        ref++;
        return this;
    }
    PSClient *release()
    {
        ref--;
        if( !ref )
            delete this;
        return 0;
    }
};