#include "NewsServer.h"
#include <iostream>
#include <Klient.h>
#include <Globals.h>

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

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

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

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

void NewsServer::close()
{
	db->setServerStatus( id, 1 );
	server->trenne();
#ifdef WIN32
	warteAufThread( 1000 );
#endif
	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 NewsServer::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 NewsServer::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 NewsServer::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 NewsServer::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 NewsServer::setMaxKlients( int mc )
{
	if( !db->setMaxClients( id, mc ) )
	{
		fehler->setText( "Die maximale Anzahl der Clients konnte nicht gesetzt werden:\n" );
		fehler->append( db->getLetzterFehler() );
		return 0;
	}
	ini->setWert( "MaxClients", Text() += mc );
	return 1;
}

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

bool NewsServer::removeKlient( NSKlient *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 NewsServer::addGesendet( int bytes )
{
	gesendet += bytes;
}

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

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

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

NSDatenbank *NewsServer::zDB() const
{
	return db;
}

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

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

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

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

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


// Inhalt der NSAKlient Klasse aus NewsServer.h
// Konstruktor 
NSAKlient::NSAKlient( SKlient *klient, NewsServer *ns )
	: Thread()
{
	this->klient = klient;
	unsigned char key[ 20 ] = { 118, 24, 81, 121, 254, 35, 132, 34, 118, 165, 132, 212, 137, 41, 253, 153, 239, 26, 1, 72 };
	klient->setSendeKey( (char*)key, 20 );
	klient->setEmpfangKey( (char*)key, 20 );
	name = new Text( "" );
	passwort = new Text( "" );
	adminId = 0;
	this->ns = ns;
}

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

// nicht constant 
void NSAKlient::thread()
{
	while( 1 )
	{
		char c = 0;
		if( !klient->getNachrichtEncrypted( &c, 1 ) )
			break;
		else
		{
			bool br = 0;
			switch( c )
			{
			case 1: // Login
				if( 1 )
				{
					klient->sendeEncrypted( "\1", 1 );
					char nLen = 0;
					klient->getNachrichtEncrypted( &nLen, 1 );
					char *n = new char[ nLen + 1 ];
					n[ (int)nLen ] = 0;
					if( nLen )
						klient->getNachrichtEncrypted( n, nLen );
					char pLen = 0;
					klient->getNachrichtEncrypted( &pLen, 1 );
					char *p = new char[ pLen + 1 ];
					p[ (int)pLen ] = 0;
					if( pLen )
						klient->getNachrichtEncrypted( p, pLen );
					int adminId = ns->zDB()->istAdministrator( n, p );
					if( adminId )
					{
						klient->sendeEncrypted( "\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->sendeEncrypted( "\1", 1 );
				break;
			case 3: // Trennen
				br = 1;
				klient->sendeEncrypted( "\1", 1 );
				break;
			case 4: // Server starten
				if( !adminId )
					errorZuKlient( "Du musst dich einloggen." );
				else
				{
					if( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSStarten ) )
					{
						if( !ns->serverStarten() )
						{
							Text *err = new Text();
							err->append( ns->getLetzterFehler() );
							errorZuKlient( err->getText() );
							err->release();
						}
						else
							klient->sendeEncrypted( "\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( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSBeenden ) )
					{
						if( ns->serverBeenden() )
							klient->sendeEncrypted( "\1", 1 );
						else
						{
							Text *err = new Text();
							err->append( ns->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( ns->isRunning() )
					{
						if( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSBeenden ) )
						{
							if( ns->serverBeenden() )
								ok = 1;
							else
							{
								Text *err = new Text();
								err->append( ns->getLetzterFehler() );
								errorZuKlient( err->getText() );
								err->release();
							}
						}
						else
							errorZuKlient( "Du bist nicht berechtigt den Server zu beenden." );
					}
					else
						ok = 1;
					if( ok && ns->hatClients() )
					{
						errorZuKlient( "Es sind noch Klients Online. Bitte versuche es später erneut." );
						break;
					}
					if( ok )
					{
						klient->sendeEncrypted( "\1", 1 );
						std::cout << "NS: Der Server wird von Benutzer " << adminId << " heruntergefahren.\n";
						ns->close();
						br = 1;
					}
				}
				break;
			case 7: // Programm abstürzen
				if( !adminId )
					errorZuKlient( "Du musst dich einloggen." );
				else
				{
					bool ok = 0;
					if( ns->isRunning() )
					{
						if( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSBeenden ) )
						{
							ns->serverBeenden();
							ok = 1;
						}
						else
							errorZuKlient( "Du bist nicht berechtigt den Server zu beenden." );
					}
					else
						ok = 1;
					if( ok )
					{
						klient->sendeEncrypted( "\1", 1 );
						std::cout << "NS: Der Server wurde von Benutzer " << adminId << " terminiert.\n";
						ns->close();
						br = 1;
					}
				}
				break;
			case 8: // Status Frage
				if( 1 )
				{
					char status = 0;
					if( ns->isRunning() )
					{
						status = 1;
						if( ns->istAn() )
							status = 2;
					}
					klient->sendeEncrypted( "\1", 1 );
					klient->sendeEncrypted( &status, 1 );
				}
				break;
			case 9: // Server pausieren
				if( !adminId )
					errorZuKlient( "Du musst dich einloggen." );
				else
				{
					klient->sendeEncrypted( "\1", 1 );
					char pause = 0;
					klient->getNachrichtEncrypted( &pause, 1 );
					if( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSPausieren ) )
					{
						bool ok = 0;
						if( pause )
							ok = ns->serverPause();
						else
							ok = ns->serverFortsetzen();
						if( ok )
							klient->sendeEncrypted( "\1", 1 );
						else
						{
							Text *err = new Text();
							err->append( ns->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 Clients setzen
				if( !adminId )
					errorZuKlient( "Du musst dich einloggen." );
				else
				{
					klient->sendeEncrypted( "\1", 1 );
					int maxC = 0;
					klient->getNachrichtEncrypted( (char*)&maxC, 4 );
					if( ns->zDB()->adminHatRecht( adminId, Admin_Recht::NSMCChange ) )
					{
						if( ns->setMaxKlients( maxC ) )
							klient->sendeEncrypted( "\1", 1 );
						else
						{
							Text *err = new Text();
							err->append( ns->getLetzterFehler() );
							errorZuKlient( err->getText() );
							err->release();
						}
					}
					else
						errorZuKlient( "Du bist nicht berechtigt die maximale Anzahl der Clients zu verändern." );
				}
				break;
            case 0xC: // klient absturtz
                if( 1 )
                {
                    klient->sendeEncrypted( "\1", 1 );
                    int klientId = 0;
                    klient->getNachrichtEncrypted( (char*)&klientId, 4 );
                    if( klientId && ns->absturzKlient( klientId ) )
                        klient->sendeEncrypted( "\1", 1 );
                    else
                        klient->sendeEncrypted( "\0", 1 );
                }
                break;
			default:
				errorZuKlient( "Unbekannte Nachricht!" );
				break;
			}
			if( br )
				break;
			ns->addEmpfangen( klient->getDownloadBytes( 1 ) );
			ns->addGesendet( klient->getUploadBytes( 1 ) );
		}
	}
	ns->addEmpfangen( klient->getDownloadBytes( 1 ) );
	ns->addGesendet( klient->getUploadBytes( 1 ) );
	delete this;
}

void NSAKlient::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 );
}


// Inhalt der NSKlient aus NewsServer.h
// Konstruktor 
NSKlient::NSKlient( SKlient *klient, NewsServer *ns )
	: Thread()
{
	this->klient = klient;
	unsigned char key[ 20 ] = { 4, 150, 243, 145, 204, 71, 253, 88, 4, 51, 189, 207, 189, 248, 231, 40, 106, 7, 182, 5 };
	klient->setSendeKey( (char*)key, 20 );
	klient->setEmpfangKey( (char*)key, 20 );
	klientNummer = 0;
	this->ns = ns;
	ref = 1;
}

// Destruktor 
NSKlient::~NSKlient()
{
	klient->release();
	ns->release();
}

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

void NSKlient::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( !ns->zDB()->proveKlient( klientNummer, ns->getId() ) )
				{
					klientNummer = 0;
					errorZuKlient( "Du bist nicht für diesen Server eingetragen" );
				}
				else
				{
					Text *key = ns->zDB()->getKlientKey( klientNummer );
					if( !key )
						errorZuKlient( "Es konnte kein Key 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 && ns->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;
				}
				ns->zDB()->unregisterKlient( klientNummer, ns->getId() );
				klient->sendeEncrypted( "\1", 1 );
				break;
			case 5: // frage nach Seite
				if( klientNummer )
				{
					klient->sendeEncrypted( "\1", 1 );
					char len = 0;
					klient->getNachrichtEncrypted( &len, 1 );
					char *n = new char[ len + 1 ];
					n[ (int)len ] = 0;
					klient->getNachrichtEncrypted( n, len );
					Text *pfad = new Text( "../news/" );
					pfad->append( n );
					delete[] n;
					if( !DateiExistiert( pfad->getText() ) )
					{
						errorZuKlient( "Die Seite ist nicht vorhanden." );
						pfad->release();
						break;
					}
					klient->sendeEncrypted( "\1", 1 );
					Datei *dat = new Datei();
					dat->setDatei( pfad );
					RCArray< Text > *list = dat->getDateiListe();
					int dAnz = list->getEintragAnzahl();
					for( int i = 0; i < dAnz; i++ )
					{
						if( DateiIstVerzeichnis( list->get( i ) ) || list->z( i )->istGleich( "." ) || list->z( i )->istGleich( ".." ) )
						{
							list->remove( i );
							dAnz--;
							i--;
						}
					}
					klient->sendeEncrypted( (char*)&dAnz, 4 );
					for( int i = 0; i < dAnz; i++ )
					{
						Text *pf = new Text( dat->zPfad()->getText() );
						pf->append( "/" );
						pf->append( list->z( i )->getText() );
						Datei *d = new Datei();
						d->setDatei( pf );
						d->open( Datei::Style::lesen );
						char nl = (char)list->z( i )->getLength();
						klient->sendeEncrypted( &nl, 1 );
						klient->sendeEncrypted( list->z( i )->getText(), nl );
						__int64 gr = d->getSize();
						klient->sendeEncrypted( (char*)&gr, 8 );
						char *bytes = new char[ 2048 ];
						while( gr > 0 )
						{
							int len = gr > 2048 ? 2048 : (int)gr;
							d->lese( bytes, len );
							klient->sende( bytes, len );
							gr -= len;
						}
						delete[] bytes;
						d->close();
						d->release();
					}
					list->release();
					dat->release();
				}
				else
					errorZuKlient( "Du bist nicht Identifiziert." );
				break;
			case 0x6: // ping
				if( 1 )
				{
					if( !klientNummer )
					{
						errorZuKlient( "Du bist nicht Identifiziert." );
						break;
					}
					klient->sendeEncrypted( "\1", 1 );
				}
				break;
			default:
				errorZuKlient( "Unbekannte Nachricht!" );
				break;
			}
			if( br )
				break;
			ns->addEmpfangen( klient->getDownloadBytes( 1 ) );
			ns->addGesendet( klient->getUploadBytes( 1 ) );
		}
	}
	ns->addEmpfangen( klient->getDownloadBytes( 1 ) );
	ns->addGesendet( klient->getUploadBytes( 1 ) );
	ns->removeKlient( this ); // delete this
}

// constant
void NSKlient::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 NSKlient::getKlientNummer() const // gibt die KlientId zurück
{
	return klientNummer;
}

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

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