#include "SocketServer.h"
#include "SocketThreadMonitord.h"
#include "SocketThreadFMS32.h"
#include "SocketThreadCrusader.h"

#ifndef WIN32
	#include <sys/param.h>
	#include <arpa/inet.h> // fuer inet_ntoa
#else
	#define usleep Sleep
	#define socklen_t int
#endif

#include "memlock.h"
#include "MonitorModulesResults.h"
#include "base64.h"
#include "convert.h"
#include "Monitor.h"
#include "MonitorLogging.h"

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

using namespace std;

// Vielleicht sollte man das hier spaeter mal auslagern ... 
// TODO Auslagern in externe Dateien (.h, .cpp)-Paar
//

const int SERVER_PORT = 9333 ;

typedef struct {long tv_sec; long tv_usec;} timval;

 

SocketServer::SocketServer(MonitorConfiguration *config, int iLockStartwert)
{
	m_MonitorConfiguration=config ;
	m_ServerModus=SocketThread::monitord ;
	m_iLockStartwert=iLockStartwert ;
	m_bWantStop=false ;
}


SocketServer::~SocketServer()
{
	// Beim SocketManager abmelden
	GetSocketsManager().removeModule(this);	
}


bool SocketServer::createListeningSocket()
{
		bool result=false;
#ifdef _WIN32  
    /* Initialisiere TCP fuer Windows ("winsock") */
    short wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD (1, 1);
    if (WSAStartup (wVersionRequested, &wsaData) != 0) {
    	ThrowMonitorException("Failed to init windows sockets (WSAStartup)") ;
        //fprintf( stderr, "Failed to init windows sockets\n");
        //exit(1);
    }
#endif
    
    /* Erzeuge das Socket */
    m_sock = socket( PF_INET, SOCK_STREAM, 0);
    if (m_sock < 0) {
    	ThrowMonitorException("Failed to create listening socket") ;
        //perror( "failed to create socket");
        //exit(1);
    }

	
	switch (m_ServerModus)
	{
	case SocketThread::fms32pro:
		m_iPort=m_MonitorConfiguration->m_PortFMS32Pro ;
		break ;
	case SocketThread::crusader:
		m_iPort=m_MonitorConfiguration->m_PortCrusader ;
		break ;
	case SocketThread::monitord:
	default:
		m_iPort=m_MonitorConfiguration->m_Port ;
		break ;
	}
	
	if ( (m_iPort<=0) && (m_iPort >65535))
	{
		if (m_ServerModus==SocketThread::monitord)
		{
			m_iPort=SERVER_PORT ;
		} else {
			m_iPort=0 ;
		}
	}
    /* Erzeuge die Socketadresse des Servers 
     * Sie besteht aus Typ und Portnummer */
    memset( &m_server, 0, sizeof (m_server));
    m_server.sin_family = AF_INET;
    
    if (m_MonitorConfiguration->m_BindIP=="")
    {
    	m_server.sin_addr.s_addr = htonl( INADDR_ANY);
    } else {
    	m_server.sin_addr.s_addr = (inet_addr(m_MonitorConfiguration->m_BindIP.c_str()));
    }
    m_server.sin_port = htons( m_iPort);

	int on = 1 ;
#ifndef WIN32   
    setsockopt(m_sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) ;
#else
	setsockopt(m_sock,SOL_SOCKET,SO_REUSEADDR,(char*) &on,sizeof(on)) ;
#endif

    /* Erzeuge die Bindung an die Serveradresse 
     * (d.h. an einen bestimmten Port) */
    if (bind( m_sock, (struct sockaddr*)&m_server, sizeof( m_server)) < 0) {
		// Verbindungsfehler ignorieren, damit die Nachrichten auf jeden Fall
		// aus dem ModuleResultSet "gepumpt" werden !
		//
        //perror( "can't bind socket");
        //exit(1);
	} else {
		/* Teile dem Socket mit, dass Verbindungswuensche
		 * von Clients entgegengenommen werden */
		listen( m_sock, 10);
		result=true ;
		
		/**
		 * In globaler ManagerListe eintragen/anmelden
		 */
		GetSocketsManager().addModule(this) ;
	}
	
	
	return result ;
}

void *SocketServer::Thread()
{
	this->ThreadStarted() ; // Erstmal Bescheid geben, dass wir laufen
	
	if (createListeningSocket())
	{
		initSocketThreads() ;
	}
	
	
	if (memLockOpen( 12345, & m_queueLock) < 0) {
		ThrowMonitorException("memLockOpen failed") ;
    }
	
	while (!m_bWantStop)
	{		
	    /* Bearbeite die Verbindungswünsche von Clients 
	     * in einer Endlosschleife
	     * Der Aufruf von accept() blockiert solange, 
	     * bis ein Client Verbindung aufnimmt, oder der timeout abgelaufen ist */
	       
	   	fd_set fdset ;
	    timeval tv ;
	    int result=0 ;
	    int maxHandle ;
	    m_bWantStop=false ;
	    while ((result==0) && (!m_bWantStop))
	    {
	    	tv.tv_sec=0; // Wartezeit auf Socketereignis, danach Kontrolle, ob ein "Sendeauftrag" vorliegt
	    	tv.tv_usec=100000 ; 
	    	FD_ZERO(&fdset) ;   // Den Socket m_sock ueberwachen ...
	   		FD_SET(m_sock,&fdset);
	   		
	   		// Diese Zeile sollte man NICHT mit "1" uebersetzen !
	   		// Das geht voll daneben. Es muss m_sock+1 sein ! Auch bei einem (!) Socket !
	   		maxHandle = m_sock + 1; 
	   		
	    	result= select(maxHandle, &fdset, &fdset, &fdset, &tv); // wartet Zeitraum wie in tv definiert
			
			if (FD_ISSET(m_sock,&fdset)>0) // Socket ereignis ?
			{
				result=1 ;
			}
					
			// Neue Verbindungsanfrage ?
			// Dann annehmen und als eigenen Thread laufen lassen
			//
			if (result>0)
			{
				int useSocket=-1 ;
				
				// Jetzt freien SocketThread suchen
				for (int i=0;i<MAX_CLIENTS;++i)
				{
					if (!socketThread[i]->IsRunning())
					{
						useSocket=i ;
						i=MAX_CLIENTS ;
					}
				}
			
				if ((useSocket>=MAX_CLIENTS) || (useSocket<0))
				{
					// TODO zu viele Clients !
					ThrowMonitorException("Too many client connects") ;
					perror("Zu viele Clients !\r\n") ;
					exit (1) ;
				}
					
				socklen_t sin_size = sizeof (sockaddr_in);
				int fd = accept(m_sock,(sockaddr*) &socketThread[useSocket]->m_client,&sin_size);
				FILE_LOG(logINFO) << "new connection from " << inet_ntoa(socketThread[useSocket]->m_client.sin_addr) ;
				
				// Thread mit dem oben angenommenen Socket starten
				socketThread[useSocket]->setFD(fd) ;
				socketThread[useSocket]->Start() ;
			}
		}
	}
	
	// Listening Socket beenden
	closesocket( m_sock);
	
	// Alle Clients beenden
	FILE_LOG(logINFO) << "Beende alle Clients"  ;
	for (int i=0;i<MAX_CLIENTS;++i)
	{
		FILE_LOG(logDEBUG) << "beende client " << i  ;
		if (socketThread[i]->IsRunning())
		{		
			socketThread[i]->closeSocket() ;
			FILE_LOG(logINFO) << i << ": closesocket done" ;	
			socketThread[i]->Kill() ;
			FILE_LOG(logINFO) << i << ": kill done" ;		
		}	
	}
	
	// uns selbst beenden
	usleep(1000) ;
	//JThread::Kill() ;
	return NULL ;
}


void SocketServer::initSocketThreads()
{
	int i ;
	for (i=0;i<MAX_CLIENTS;i++)
	{
		switch (m_ServerModus)
		{
			case SocketThread::monitord:
				socketThread[i]=new SocketThreadMonitord(m_iLockStartwert+i,i+1) ;
				socketThread[i]->m_MonitorConfiguration = m_MonitorConfiguration ;
				break ;
			case SocketThread::fms32pro:
				socketThread[i]=new SocketThreadFMS32(m_iLockStartwert+i,i+1) ;
				socketThread[i]->m_MonitorConfiguration = m_MonitorConfiguration ;
				break ;
			default:
				socketThread[i]=new SocketThreadCrusader(m_iLockStartwert+i,i+1) ;
				socketThread[i]->m_MonitorConfiguration = m_MonitorConfiguration ;
				break ;
		}
	}
}


void SocketServer::addResult(ModuleResultBase* pRes)
{		
	// Nun f�r die nachgeordneten Threads die Ausgabe nach den Uebergabewerte zusammenbauen
	//
	
	
	// Wenn ja, Ereignis an alle Threads weitergeben
	for (int i=0;i<MAX_CLIENTS;++i)
	{
		if (socketThread[i]->IsRunning())
		{		
			socketThread[i]->addResult(pRes) ;	
		}
		
	}
}


/*
 * -----------------------------------------------------------
 * ThreadBase
 * -----------------------------------------------------------
 */

ThreadBase::~ThreadBase()
{
}

ThreadBase::ThreadBase(int locknum)
{
	m_iLockNum= locknum ;
	m_exitThread=false ;
}

bool ThreadBase::createLock()
{
	if ( memLockCreate( m_iLockNum, &m_Lock) < 0) {
		
		ThrowMonitorException("memLockCreate failed for " + convertIntToString(m_iLockNum)) ;
        //cerr << ( "memLockCreate failed\n");
        //exit(1);
    }
    return true;
}

void ThreadBase::releaseLock()
{
	 memLockDestroy(m_Lock) ;
 
}


/*
 * -----------------------------------------------------------
 * SocketThread
 * -----------------------------------------------------------
 */

SocketThread::~SocketThread()
{
}

SocketThread::SocketThread(int locknum, int PortNum, SocketMode ServerMode)
: ThreadBase(locknum)
{
	m_iPortNum = PortNum ;
	m_ServerMode = ServerMode ;
	ResetThreadVars() ;
}

void SocketThread::setFD(int fd)
{
	m_fd=fd ;
}

void SocketThread::ResetThreadVars()
{
	// Zuruecksetzen:
	// Kein Socket zugewiesen
	// Client ist nicht angemeldet
	// Thread soll sich nicht beenden
	//
	
	m_fd=0 ;
	m_authenticated=false ;
	m_exitThread=false ;

	// Commandbuffer loeschen
	memset(m_CommandBuffer,0,MAX_COMMANDLINE) ;
	
}

void *SocketThread::Thread()
{
	if (m_fd==0)
	{
		ThrowMonitorException("SocketThread started wird fd=NULL") ;
		return NULL ;
	}
	
	this->ThreadStarted() ; // Erstmal Bescheid geben, dass wir laufen
	createLock() ;
	createSocket() ;

	FILE_LOG(logINFO) << "SocketThreads exits" ;
	
	releaseLock() ;
	return NULL;
}


std::string SocketThread::createZVEIOutputString(ModuleResultBase Result)
{
	std::string socketText="" ;
	return socketText ;	
}


std::string SocketThread::createPOCSAGOutputString(ModuleResultBase Result)
{
	std::string socketText="" ;
	return socketText ;	
}

std::string SocketThread::createFMSOutputString(ModuleResultBase Result)
{
	std::string socketText="" ;

	return socketText ;	
}

std::string SocketThread::createOutputString(ModuleResultBase Result)
{
	std::string socketText="" ;
	
	if (Result["typ"]=="fms")
	{
		socketText=createFMSOutputString(Result) ;
	}
	else if (Result["typ"]== "pocsag") 
	{
		socketText=createPOCSAGOutputString(Result) ;
	} 
	else if (Result["typ"]=="zvei")
	{
		socketText=createZVEIOutputString(Result) ;
	}

	return socketText ;	
}
void SocketThread::addResult(ModuleResultBase* pRes)
{
	std::string outText ;
		
	outText=createOutputString(*pRes);
	addOutputText(outText) ;
}

void SocketThread::addOutputText(std::string outText)
{
	memLock(m_Lock) ;
	if (!(outText==""))
	{
		m_outputStrings.insert(m_outputStrings.begin(),outText) ;
	}
	memUnlock(m_Lock) ;
}

void SocketThread::say(const std::string& something)
{
	unsigned int len=send( m_fd, something.c_str(), something.length(), 0);
	if (len!=something.length())
	{
		FILE_LOG(logERROR) << "error sending date to client. thread exiting"  ;
		doLogout() ;
	}
}

void SocketThread::say(const char *something)
{
	unsigned int len = send( m_fd, something, strlen(something), 0);
	if (len!=strlen(something))
	{
		FILE_LOG(logERROR) << "error sending date to client. thread exiting"  ;
		doLogout() ;
	}
}

void SocketThread::processInput()
{

}


void SocketThread::doLogout()
{
	m_exitThread=true ;
}


void SocketThread::sayWelcome()
{
}

void SocketThread::sayGoodbye()
{
}

void SocketThread::createSocket()
{
	timeval tv ;
	
    if (m_fd < 0) {
    	ThrowMonitorException("accept failed") ;
    	//perror( "accept failed");
        //exit(1);
    }
	
	// Hier ist die Sitzung noch nicht "authenticated"
	fd_set fdset ;
	
    FD_ZERO(&fdset) ;
    FD_SET(m_fd,&fdset);
        
    m_sClientIP= inet_ntoa(m_client.sin_addr) ;

	m_authenticated=false ;
	// Gültige IP Adresse, die sich nicht anmelden muss ?
	if (m_MonitorConfiguration->IsValidLogin("","",this->m_sClientIP))
	{
		FILE_LOG(logINFO) << "login authentication (ip allowed): " << m_sClientIP ;
		this->m_authenticated=true ;
	}

	// FMS32Pro ist dafuer zu doof ;-)
	//
	if ((m_ServerMode==SocketThread::fms32pro))
	{
		// TODO: IP Liste auch bei FMS32 anwenden ? dann nachfolgendes auskommentiert lassen
		// this->m_authenticated=true ;
	}
	// Wir sagen natuerlich brav "Guten Tag" ...
	sayWelcome() ;
	
	fd_set fdset_write ;
	fd_set fdset_exceptions ;
	
	
    for (;;) {
    	if (m_exitThread)
    	{
    		break ;
    	}
    	
        // Fuer eine Sekunde auf Meldung am Port warten
        tv.tv_sec=0 ;
        tv.tv_usec=10000 ;
        FD_ZERO(&fdset) ;
        FD_SET(m_fd,&fdset);
        
        // Neu: write + exceptions
        FD_ZERO(&fdset_write) ;
    	FD_ZERO(&fdset_exceptions) ;
		//FD_SET(m_fd,&fdset_write);
    	FD_SET(m_fd,&fdset_exceptions);
    	

        int result = select(m_fd+1, &fdset, NULL,&fdset_exceptions, &tv);
        
		if (FD_ISSET(m_fd,&fdset)>0) // Socket ereignis ?
		{
			FILE_LOG(logDEBUG) << "Socket reports read event" ;
			result=1 ;
		}
		
		if (FD_ISSET(m_fd,&fdset_write)>0) // Problem ?
		{
			FILE_LOG(logDEBUG) << "Socket reports write event" ;
			result=0 ;
			//m_exitThread=true ;
		}
		
		if (FD_ISSET(m_fd,&fdset_exceptions)>0) // Problem ?
		{
			FILE_LOG(logDEBUG) << "Socket reports exception event" ;
			result=0 ;
			m_exitThread=true ;
		}

        if (result>0)
        {
        	char buffer[RECV_BUFFER] ;
			char * posPtr ;
			int pos ;
			int gelesen ;
			gelesen=recv (m_fd, buffer, RECV_BUFFER-1,0) ;
        	if (gelesen<=0) // Nix am Port, aber doch Port Event ?
			{
				FILE_LOG(logINFO) << "recv()<=0 => socketthread exiting"  ;
				m_exitThread=true ;
			}
        	strncat(m_CommandBuffer,buffer,MAX_COMMANDLINE) ;
        	memset(buffer,0,RECV_BUFFER) ;
			
			// Bis zum CR/LF einkuerzen
			if ((posPtr=strstr(m_CommandBuffer,"\r\n"))>0)
			{
				// pos soll an das ENDE von CR/LF zeigen
				pos=posPtr-m_CommandBuffer+2 ;
				char mybuffer[MAX_COMMANDLINE] ;
				memset(mybuffer,0,MAX_COMMANDLINE) ;
				strncpy(mybuffer,m_CommandBuffer,pos) ;
				memset(m_CommandBuffer,0,MAX_COMMANDLINE) ;
				strncpy(m_CommandBuffer,mybuffer,pos) ;

				// Erst wenn eine Eingabezeile empfangen wurde, wird diese ausgewertet
				processInput() ; 
			}
			 	
        } else
        {
     		// Nix tun   	
        }
		
        // Gibt es etwas zu senden ?
        //
		memLock(m_Lock) ;
		while (!m_outputStrings.empty())
		{			
			std::string retString=m_outputStrings.back() ;	
			
			if (m_authenticated)
			{
				retString.push_back('\r') ;
				retString.push_back('\n') ;
				say(retString) 	;
			}
			m_outputStrings.pop_back() ;
		}
		memUnlock(m_Lock) ;
    }
    
    closesocket(m_fd);
    ResetThreadVars() ;
}

std::string SocketThread::getClientIP()
{
	return (m_sClientIP) ;
}

void SocketThread::closeSocket()
{
	closesocket( m_fd);
} 


bool SocketThread::paramIsHex(int param) 
{
	// TODO
	return true ;
}

bool SocketThread::paramIsBase64(int param)
{
	// TODO
	return true ;
}


bool SocketThread::HexToString(int param, std::string &result)
{
	return convertHexToString(m_cmdParam[param],result);
}

// ----------------------------------------
//MonitorSocketsManager

MonitorSocketsManager GlobalMonitorSocketsManager ;
MonitorSocketsManager& GetSocketsManager() { return (GlobalMonitorSocketsManager);}
	

MonitorSocketsManager::MonitorSocketsManager()
{
	FILE_LOG(logINFO) << "SocketManager erstellt"  ;
	
	if ( memLockCreate( 12347, & m_MemLock) < 0) {
   		ThrowMonitorException("SocketsManager: memLockCreate failed") ;
    }	
   
    m_bStop=false ;
}


MonitorSocketsManager::~MonitorSocketsManager()
{
}


bool MonitorSocketsManager::addModule(SocketServer* pServer)
{
	m_Modules.push_back(pServer) ;
	
	return true ;
}


bool MonitorSocketsManager::dispatchResult(ModuleResultBase *pRes) 
{	
	tMonitorSocketServerVector::iterator i ;
	
	for (i= m_Modules.begin(); i< m_Modules.end(); i++)
	{
			(*i)->addResult(pRes) ;
	}
	return true ;
}

			
bool MonitorSocketsManager::removeModule(SocketServer* pServer)
{
	return true ;
}




