/*    
    This file is part of NullSrv.

    NullSrv is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    NullSrv is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with NullSrv.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */
#include <netdb.h> /* gethostbyname */
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdarg.h>

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close(s)

typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR;

typedef struct structClient
{
	SOCKET sock;
	pthread_t thread;
	struct structClient* next;
} Client;

int bStop;
Client* ClientList;
pthread_mutex_t mutexlist;
pthread_mutex_t mutexstop;


// Trame HTTP à renvoyer
#define NULLRES "HTTP/1.1 200 OK\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\nCache-Control: no-cache\r\nCache-Control: must-revalidate\r\nCache-Control: max-age=0\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 26\r\n\r\n<HTML><BODY></BODY></HTML>"

// Affichage seulement si DEBUG est défini
void DebugPrint(char* format, ...)
{
#ifdef DEBUG
	va_list args;
	va_start(args, format);
	vprintf(format, args);
	va_end(args);
#endif
}

// Alloue une nouvelle structure Client
Client* AllocClient(SOCKET sock)
{
	Client* tmp = (Client*) malloc(sizeof(Client));
	if(tmp != NULL)
	{
		memset(tmp, 0, sizeof(Client));

		tmp->sock = sock;
	}

	return tmp;
}

// Ajoute un nouveau client à la liste ClientList
Client* AddClient(SOCKET sock)
{
	Client* tmp = ClientList;

	DebugPrint("lock mutexlist ... ");
	pthread_mutex_lock (&mutexlist);
	DebugPrint("OK\n");

	// Si la liste est nulle (1er client)
	if(ClientList == NULL)
	{
		ClientList = AllocClient(sock);

		DebugPrint("unlock mutexlist ... ");
		pthread_mutex_unlock (&mutexlist);
		DebugPrint("OK\n");

		return ClientList;
	}
	else
	{
		// Recherche du dernier élément de la liste
		while(tmp->next != NULL)
			tmp = tmp->next;

		// Allocation nouveau client
		tmp->next = AllocClient(sock);

		DebugPrint("unlock mutexlist ... ");
		pthread_mutex_unlock (&mutexlist);
		DebugPrint("OK\n");

		return tmp->next;
	}
}

// Supprime un Client de la liste ClientList
void RemoveClient(Client* Cli)
{
	Client* tmp = ClientList;
	Client* prev = NULL;

	DebugPrint("lock mutexlist ... ");
	pthread_mutex_lock (&mutexlist);
	DebugPrint("OK\n");

	// Parcours de la liste jusqu'a temps de trouver le client
	while((tmp != NULL) && (tmp != Cli))
	{
		prev = tmp;
		tmp = tmp->next;
	}

	// Si client trouvé
	if(tmp != NULL)
	{
		// Si le client précédent est null, c'est que le client à supprimer est le 1er élément de a liste
		if(prev == NULL)
		{
			// Si le client n'a pas d'élément suivant, c'est qu'il est le seul dans la liste
			if(tmp->next == NULL)
			{
				// Mise à zero de la liste car il n'y a plus aucun élément dedans
				ClientList = NULL;
			}
			else
			{
				// La tete de la liste est egale à l'élément suivant
				ClientList = tmp->next;
			}
		}
		else
		{
			// Si le client n'a pas d'élément suivant, c'est le dernier de la liste
			if(tmp->next == NULL)
			{
				// Mise à NULL de l'élément suivant du client précédent
				prev->next = NULL;
			}
			else
			{
				// L'élément suivant du client précédent = élément suivant
				prev->next = tmp->next;
			}
		}
		// Liberation du client
		free(tmp);
	}

	DebugPrint("unlock mutexlist ... ");
	pthread_mutex_unlock (&mutexlist);
	DebugPrint("OK\n");
}

// Thread de réponse lors de la connexion d'un nouveau client
void* responseThread (void * arg)
{
	char buffer[4096];
	Client* Cli = (Client*) arg;
	int bThreadStop = 0;

	DebugPrint("recv %d ... ", (int) Cli->sock);
	
	// Lecture de la requete du client
	if(recv(Cli->sock, buffer, sizeof(buffer), 0) < 0)
	{
		DebugPrint("recv error %s", strerror(errno));
		bThreadStop = 1;
	}
	else
	{
		DebugPrint("OK\nRequest : %s\n", buffer);
		DebugPrint("Sending response %d ... ", (int) Cli->sock);

		// Envoi de la trame HTTP bidon
		strcpy(buffer, NULLRES);
		send(Cli->sock, buffer, strlen(NULLRES), 0);

		DebugPrint("OK\n");
	}

	// Fermeture de la connexion
	close(Cli->sock);

	DebugPrint("Remove cli %d\n", Cli->sock);

	// Suppression du client de la liste
	RemoveClient(Cli);

	pthread_exit((void*) 0);
}

// Ouvre un socket en écoute sur le port spécifié
static void init_connection(int port)
{
   SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
   SOCKADDR_IN sin = { 0 };
   int x;
   SOCKET cli;

   ClientList = NULL;
   Client* TmpClient;

   // Création socket
   sock = socket(AF_INET, SOCK_STREAM, 0);
   bStop = 0;

   if(sock == INVALID_SOCKET)
   {
      perror("socket()");
      exit(errno);
   }

   // Renseignement des informations pour l'écoute
   sin.sin_addr.s_addr = htonl(INADDR_ANY);
   sin.sin_port = htons(port);
   sin.sin_family = AF_INET;

   // On positionne le socket en non bloquant (pour le accept)
   x=fcntl(sock,F_GETFL,0);
   fcntl(sock,F_SETFL,x | O_NONBLOCK);

   // Bind sur le port spécifié
   if(bind(sock,(SOCKADDR *) &sin, sizeof sin) == SOCKET_ERROR)
   {
      perror("bind()");
      exit(errno);
   }

   if(listen(sock, 10000) == SOCKET_ERROR)
   {
      perror("listen()");
      exit(errno);
   }

   DebugPrint("lock mutexstop ... ");
   pthread_mutex_lock (&mutexstop);
   DebugPrint("OK\n");

   // On boucle tant que bStop = 0
   while(!bStop)
   {
		DebugPrint("unlock mutexstop ... ");
		pthread_mutex_unlock (&mutexstop);
		DebugPrint("OK\n");

		DebugPrint("accept\n");

		// Accept des nouvelles connexions
		cli = accept(sock, NULL, NULL);
		if(cli == -1)
		{
			// On verifie le retour pour savoir si une erreur s'est produite
			// ou que tout simplement, il n'y a pas de nouvelle connexion
			if(errno != EWOULDBLOCK)
			{
				// Erreur
			    perror("accept()");
				exit(errno);
			}
			else
			{
				// Pas de connexion, on attend 1 sec
				DebugPrint("No connection ... \n");
				sleep(1);
			}
		}
		else
		{
			// Nouvelle connexion
			DebugPrint("Starting new thread %d\n", (int) cli);

			// Création d'un nouveau client et ajout à la liste
			TmpClient = AddClient(cli);

			// Création et démarrage du thread réponse pour le client
			pthread_create(&(TmpClient->thread), NULL, responseThread, TmpClient);
		}
	
		DebugPrint("lock mutexstop ... ");
		pthread_mutex_lock (&mutexstop);
		DebugPrint("OK\n");
   }

   DebugPrint("unlock mutexstop ... ");
   pthread_mutex_unlock (&mutexstop);
   DebugPrint("OK\n");

   //Fermeture du socket
   close(sock);
   DebugPrint("Terminated\n");
}

// Gestion du CTRL-C
void ex_program(int sig) 
{
	printf("Ctrl-C caught, exiting ... ");
	
	DebugPrint("lock mutexstop ... ");
	pthread_mutex_lock (&mutexstop);
	DebugPrint("OK\n");

	// On positionne bStop à 1 pour provoquer l'arret de init_connection
	bStop = 1;

	DebugPrint("unlock mutexstop ... ");
	pthread_mutex_unlock (&mutexstop);
	DebugPrint("OK\n");

	DebugPrint("lock mutexlist ... ");
	pthread_mutex_lock (&mutexlist);
	DebugPrint("OK\n");

	// On attend qu'il n'y ait plus de client connecté
	while(ClientList != NULL)
	{
		DebugPrint("unlock mutexlist ... ");
		pthread_mutex_unlock (&mutexlist);
		DebugPrint("OK\n");

		sleep(1);

		DebugPrint("lock mutexlist ... ");
		pthread_mutex_lock (&mutexlist);
		DebugPrint("OK\n");
	}
	DebugPrint("unlock mutexlist ... ");
	pthread_mutex_unlock (&mutexlist);
	DebugPrint("OK\n");

	pthread_mutex_destroy(&mutexstop);
	pthread_mutex_destroy(&mutexlist);

	printf("OK\n");

	signal(SIGINT, SIG_DFL);
}

int main()
{
	signal(SIGINT, ex_program);

	// Ecoute sur le port 80
	init_connection(80);


	return 0;
}
