/*
 * serverProtocol.h
 *
 * Gouverneur Th. - Cuisinier Gi.
 *
 * Define the server-side protocol RMP
 * and his implementation.
 *
 */

#include "serverProtocol.h"
#include "mysqlQuery.h"
#include "libs/mysql.h"

using namespace std;

LLU<Reservation *> ServerProtocol::listBook;
LLU<User *>        ServerProtocol::listUser;
LLU<Room *>        ServerProtocol::listRoom;
bool		   ServerProtocol::suspended = false;
int		   ServerProtocol::shutdown = -1;
						

void ServerProtocol::cmdLogin    (Message *m)
{
	Message answer;
	answer.numRequest = m->numRequest;
	answer.ident = "Siege central";
	answer.token = "LOGIN";
	if (m->args.Size() == 2)
	{
		cout << "size:" << listUser.Size()<< endl;
		for (int i=0; i<listUser.Size(); i++)
		{
			if (listUser[i]->getLogin() == m->args[0])
			{
				if (listUser[i]->getAuth() == true) // USER ALREADY LOGGED IN !!
				{
					answer.args.Push(string("NOK"));
					break;
				}
				if (listUser[i]->getPass() == m->args[1])
				{
					answer.args.Push(string("OK"));  // ok!
					listUser[i]->setAuth(true);
					if (m->sC)
						listUser[i]->setConn(m->sC);
				}
				else
					answer.args.Push(string("NOK")); // !ok
				break;
			}
		}
		if (answer.args.Size() == 0) answer.args.Push(string("NOK"));
	} else answer.args.Push(string("NOK"));

	if (m->sC)
		*(m->sC) << answer.serialize();
	else
		throw Exception("Cannot reach other side, pointer NULL...");
	return;
}


void ServerProtocol::cmdBRoom    (Message *m)
{
	Message answer;
	
	answer.numRequest = m->numRequest;
	answer.ident = "Siege Central";
	answer.token = "BROOM";

	if (getUserFromSock(*(m->sC)) == NULL)
	{
		// not authenticated
		answer.args.Push(string("NOK"));
		answer.args.Push(string("Not Logged in.."));

		if (m->sC)
			*(m->sC) << answer.serialize();
		else
			throw Exception("Cannot reach other side, pointer NULL...");
		return;
	}
	// refreshing reservations list...
	try
	{
		refreshBooks();
	}
	catch (Exception e)
	{
		throw e;
	}
	
		if (m->args.Size() == 4)
	{
		date d(atoi(m->args[1].c_str()));
		if (!d.valid()) // date incorrecte
		{
			answer.args.Push(string("NOK"));
			answer.args.Push(string("Date incorrecte..."));
		}	
		else if (m->args[3].length() == 0)
		{
			answer.args.Push(string("NOK"));
			answer.args.Push(string("Nom invalide..."));
		}
		else
		{
			for (int i=0; i<listRoom.Size();i++)
			{
				if (listRoom[i]->getType() == m->args[0])
				{
					cout << "Already " << listRoom[i]->getReservations()->Size() << "reservations" << endl;
					if (listRoom[i]->getReservations()->Size() == 0)
					{
						// chambre du type correct, et libre
						listRoom[i]->addReservation(new Reservation(m->args[3], m->args[1], m->args[2]));
						(*(listRoom[i]->getReservations()))[0]->setRoom(listRoom[i]);
						listBook.Push((*(listRoom[i]->getReservations()))[0]);
						answer.args.Push(string("OK"));
						answer.args.Push(listRoom[i]->getNum());

						/* adding in database: */
						try
						{
							insertBook((*(listRoom[i]->getReservations()))[0]);
						}
						catch (Exception e)
						{
							throw e;
						}
						
						break;
					}
					else
					{ // check corruption with all reservation for this room
						int bad = 0;
						for (int j=0; j<listRoom[i]->getReservations()->Size(); j++)
						{
							Reservation *tmpr = (*(listRoom[i]->getReservations()))[j];
							cout << "date: " << tmpr->getDate().toString() << endl;
							date datedebut = tmpr->getDate();
							date datefin = datedebut + atoi(tmpr->getNbr().c_str());
							date datedebutnu = d;
							date datefinnu = d + atoi(m->args[2].c_str());
							
							if (datedebutnu >= datedebut && datedebutnu <= datefin) // corrupted, baaaak
								{ bad = 1; break; }	
							if (datefinnu >= datedebut && datefinnu <= datefin) // corrupted, baak
								{ bad = 1; break; }	
							// goed
							bad = 0;
						}
						if (bad) 
							continue; 
						else 
						{
							Reservation *r = new Reservation(m->args[3], m->args[1], m->args[2]);
							listRoom[i]->addReservation(r);
							r->setRoom(listRoom[i]);
							listBook.Push(r);
							answer.args.Push(string("OK"));
							answer.args.Push(listRoom[i]->getNum());
							
							/* adding into database: */
							try
							{
								insertBook(r);
							}
							catch (Exception e)
							{
								throw e;
							}
							break;
						}
					}
				}
			}
		}
		if (answer.args.Size() != 2)
		{
			answer.args.Push(string("NOK"));
			answer.args.Push(string("Plus de chambre libre du type demande."));
		}
	}
	else
	{
		answer.args.Push(string("NOK"));
		answer.args.Push(string("Nombre de parametres incorrect..."));
	}

	if (m->sC)
		*(m->sC) << answer.serialize();
	else
		throw Exception("Cannot reach other side, pointer NULL...");
	return;

}


void ServerProtocol::cmdCRoom    (Message *m)
{
	Message answer;
	answer.numRequest = m->numRequest;
	answer.ident = "Siege Central";
	answer.token = "CROOM";

	if (getUserFromSock(*(m->sC)) == NULL)
	{
		// not authenticated
		answer.args.Push(string("NOK"));
		answer.args.Push(string("Not Logged in.."));

		if (m->sC)
			*(m->sC) << answer.serialize();
		else
			throw Exception("Cannot reach other side, pointer NULL...");
		return;
	}

	// refreshing reservations list...
	try
	{
		refreshBooks();
	}
	catch (Exception e)
	{
		throw e;
	}

	
	if (m->args.Size() == 2)
	{
		for (int i=0; i<listBook.Size(); i++)
		{
			if (listBook[i]->getNom() == m->args[1])
			{
				if (listBook[i]->getDate() > date::now())
				{
					Reservation *r = listBook[i];

					/* deleting from database: */
					try
					{
						deleteBook(r);
					}
					catch (Exception e)
					{
						throw e;
					}

					listBook.Delete(r);
					r->getRoom()->getReservations()->Delete(r);
					delete r;
					answer.args.Push(string("OK"));
#ifdef DEBUG
					cout << "OK CROOM" << endl;
#endif
				}
				else
				{
					answer.args.Push(string("NOK"));
					answer.args.Push(string("Date depassee"));
#ifdef DEBUG
					cout << "NOK CROOM" << endl;
#endif
					break;
				}
			}
		}
		if (answer.args.Size() == 0)
		{
			answer.args.Push(string("NOK"));
			answer.args.Push(string("Inexistant"));
		}
	}
	else
	{
		answer.args.Push(string("NOK"));
		answer.args.Push(string("Commande eronnee.."));
	}
	if (m->sC)
		*(m->sC) << answer.serialize();
	else
		throw Exception("Cannot reach other side, pointer NULL...");
	return;
}


void ServerProtocol::cmdLRooms   (Message *m)
{
	Message answer;
	answer.numRequest = m->numRequest;
	answer.ident = "Siege central";
	answer.token = "LROOMS";
	answer.args.Push(string("OK"));

	if (getUserFromSock(*(m->sC)) == NULL)
	{
		// not authenticated
		answer.args.Push(string("NOK"));
		answer.args.Push(string("Not Logged in.."));

		if (m->sC)
			*(m->sC) << answer.serialize();
		else
			throw Exception("Cannot reach other side, pointer NULL...");
		return;
	}

	// refreshing reservations list...
	try
	{
		refreshBooks();
	}
	catch (Exception e)
	{
		throw e;
	}
	
	answer.args.Push(Message::numToString(listBook.Size()));

	if (m->sC)
		*(m->sC) << answer.serialize();
	else
		throw Exception("Cannot reach other side, pointer NULL...");
	
	for (int i=0; i< listBook.Size();i++)
	{
		Message ans;
		ans.numRequest = m->numRequest;
		ans.ident = "Siege central";
		ans.token = "LROOMS";
		ans.args.Push(listBook[i]->getRoom()->getNum());
		ans.args.Push(listBook[i]->getNom());
		
		if (m->sC)
			*(m->sC) << ans.serialize();
		else
			throw Exception("Cannot reach other side, pointer NULL...");
	}
	return;
}
						

void ServerProtocol::cleanClient(Sock_Base::sConn *sSock)
{
	for (int i = 0; i < listUser.Size(); i++)
	{
		if (listUser[i]->getConn() == sSock) // user leaved.
		{
			listUser[i]->setConn(NULL);
			listUser[i]->setAuth(false);
			break;
		}
	}
	return;
}


void ServerProtocol::cleanUpAll(void)
{
	for (int i=0; i<listUser.Size();i++)
		delete listUser[i];
	for (int i=0; i<listBook.Size();i++)
		delete listBook[i];
	for (int i=0; i<listRoom.Size();i++)
		delete listRoom[i];
}

void ServerProtocol::cleanUpBook(void)
{
	for (int i=0; i<listBook.Size();i++)
	{
		listBook[i]->getRoom()->getReservations()->Flush();
		delete listBook[i];
	}
	listBook.Flush();
}


void	ServerProtocol::getRoomFromSql(void)
{
        MySQL myConn(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
	if (myConn.Connect() == -1) 
		throw Exception("Cannot connect to mysql database");
	try
	{
		if (myConn.Query(mysqlQuery::roomGetAll()) == -1)
			throw Exception("Error, sql query doesn't achieved correctly...");

		int nb = myConn.NumTup();
		listRoom.Flush();

		if (!nb) // no rows...
		{
			myConn.Disconnect();
			return;
		}
		while (nb--)
		{
			myConn.GetTupl();
			string room_id, room_type;
			if (myConn.GetChamp(string("numero"), &room_id) == -1)
				throw Exception("Cannot retrieve information about room...");
			if (myConn.GetChamp(string("type"), &room_type) == -1)
				throw Exception("Cannot retrieve information about room...");
			listRoom.Push(new Room(room_id, room_type));
#ifdef DEBUG
			cout << "Room added: " << room_id << ":" << room_type << endl;
#endif
		}
		myConn.Disconnect();
		return;
	}
	catch (Exception e)
	{
		myConn.Disconnect();
		throw e; // propage exception
	}
}


void	 ServerProtocol::getUserFromSql(void)
{
        MySQL myConn(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
	if (myConn.Connect() == -1) 
		throw Exception("Cannot connect to mysql database");
	try
	{
		if (myConn.Query(mysqlQuery::getUsers()) == -1)
			throw Exception("Error, sql query doesn't achieved correctly...");

		int nb = myConn.NumTup();
		listUser.Flush();

		if (!nb) // no rows...
		{
			myConn.Disconnect();
			return;
		}
		while (nb--)
		{
			myConn.GetTupl();
			string user_login, user_pass;
			if (myConn.GetChamp(string("login"), &user_login) == -1)
				throw Exception("Cannot retrieve information about user...");
			if (myConn.GetChamp(string("pass"), &user_pass) == -1)
				throw Exception("Cannot retrieve information about user...");
			listUser.Push(new User(user_login, user_pass));
#ifdef DEBUG
			cout << "User readed: " << user_login << ":" << user_pass << endl;
#endif
		}
		myConn.Disconnect();
		return;
	}
	catch (Exception e)
	{
		myConn.Disconnect();
		throw e; // propage exception
	}
}


void      ServerProtocol::refreshBooks(void)
{
        MySQL myConn(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
	if (myConn.Connect() == -1) 
		throw Exception("Cannot connect to mysql database");
	try
	{
		if (myConn.Query(mysqlQuery::bookGetAll()) == -1)
			throw Exception("Error, sql query doesn't achieved correctly...");

		int nb = myConn.NumTup();
		cout << "avant clean" << endl;	
		cleanUpBook();
		cout << "apres clean" << endl;

		if (!nb) // no rows...
		{
			myConn.Disconnect();
			return;
		}
		while (nb--)
		{
			myConn.GetTupl();
			string book_nom, book_date, book_nbr, book_num;
			if (myConn.GetChamp(string("numroom"), &book_num) == -1)
				throw Exception("Cannot retrieve information about reservation...");
			if (myConn.GetChamp(string("nom"), &book_nom) == -1)
				throw Exception("Cannot retrieve information about reservation...");
			if (myConn.GetChamp(string("nbr_nuit"), &book_nbr) == -1)
				throw Exception("Cannot retrieve information about reservation...");
			if (myConn.GetChamp(string("date_arrive"), &book_date) == -1)
				throw Exception("Cannot retrieve information about reservation...");
			
			Reservation *r = new Reservation(book_nom, book_date, book_nbr);
			for (int i=0; i<listRoom.Size(); i++) 
			{
				if (listRoom[i]->getNum() == book_num) 
				{
					r->setRoom(listRoom[i]);
					listRoom[i]->addReservation(r);
				}
			}
			listBook.Push(r);
#ifdef DEBUG
			cout << "Reservation readed: " << book_nom << ":" << book_num << endl;
#endif
		}
		myConn.Disconnect();
		return;
	}
	catch (Exception e)
	{
		myConn.Disconnect();
		throw e; // propage exception
	}
}

void             ServerProtocol::insertBook(const Reservation *r)
{
#ifdef DEBUG
	cout << "begin insert" << endl;
#endif
	MySQL myConn(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
	if (myConn.Connect() == -1) 
		throw Exception("Cannot connect to mysql database");
	try
	{
		if (myConn.Insert(mysqlQuery::insertBook(r->getRoom()->getNum(), r->getNom(), r->getNbr(), r->getDate().toString())) == -1)
			throw Exception("Error, sql query doesn't achieved correctly...");

		myConn.Disconnect();
		return;
	}
	catch (Exception e)
	{
		myConn.Disconnect();
		throw e; // propage exception
	}
}


void             ServerProtocol::deleteBook(const Reservation *r)
{
	MySQL myConn(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
	if (myConn.Connect() == -1) 
		throw Exception("Cannot connect to mysql database");
	try
	{
		if (myConn.Insert(mysqlQuery::deleteBook(r->getRoom()->getNum(), r->getNom(), r->getNbr(), r->getDate().toString())) == -1)
			throw Exception("Error, sql query doesn't achieved correctly...");

		myConn.Disconnect();
		return;
	}
	catch (Exception e)
	{
		myConn.Disconnect();
		throw e; // propage exception
	}
}
		

User *ServerProtocol::getUserFromSock(const Sock_Base::sConn &s)
{
	for (int i=0; i < listUser.Size(); i++)
	{
		if (listUser[i]->getConn() != NULL)
			if (listUser[i]->getConn()->sock == s.sock)
				return listUser[i];
	}
	return NULL;
}	


void ServerProtocol::sendSusp	(Sock_Base::sConn *s)
{
	Message answer;
	answer.numRequest = 0;
	answer.ident = "Siege Central";
	answer.token = "SUSP";
	answer.args.Push(string("NOK"));

	if (s)
		*(s) << answer.serialize();
	else
		throw Exception("Cannot reach other side, pointer NULL...");
	return;


}
