/****************************************************************************
*  XARXES DE COMPUTADORS II                                                 
*
*  Practica VoIP
*
*  Realizado por: 
*     Francisco Jose Aguilar Celdran
*     Sergio Blanco Cuaresma
*
*  server.c:
*     Servidor que implementa el protocolo establecido en la practica.
*
*     Utiliza mysql.h para guardar la información de los usuarios registrados,
*     en las constantes MYSQL_SERVER, MYSQL_USER, MYSQL_PASS, MYSQL_DB de mysql.h
*     se debe indicar la información correspondiente para poder usar MySQL.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>

#include "cola.h"
#include "mysql.h"


int sock_S = 0;
MYSQL mysql;

/**
   Funcion que permite finalizar correctamente la ejecucion al pulsar crtl+c
**/
void controlador (int numero){
   if (sock_S != 0) {
      close(sock_S);
   }
 
   // Cerramos conexión BD.  
   mysql_close(&mysql);
   
	// Se pone controlador por defecto para ctrl-c
	signal (SIGINT, SIG_DFL);
   // Los procesos hijos que vuelvan a ser zombies
   signal (SIGCHLD, SIG_DFL);
   exit(1);
}


/**
   Muestra los datos de conexion del servidor al iniciar
**/
void mensaje_inicial_servidor (char *nombre_servidor, struct sockaddr_in *info)
{
   printf("\n*** Bienvenido al Servidor VoIP pseudoSIP *** \n\n");
   printf("Nombre del Host Servidor: %s \n",nombre_servidor);
   printf("Informacion: \n");
   printf("\t - Puerto: %d \n",ntohs(info -> sin_port));
   printf("\t - Direccion del host: %s \n",(char *)inet_ntoa(info -> sin_addr));
   printf("\t - Familia: %d \n\n",info -> sin_family);
   printf("Servidor a la espera de peticiones de los clientes... \n\n");
}


/**
   Realiza el tratamiento de una petición REGISTER segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_register(char *peticion, char *from, char *to, char *subject) {
   int i = 16; // Posició a on comença el From
   int j =0;
   while (peticion[i] != '\r') {
      from[j] = peticion[i];
      i++; j++;
   }
   from[j-1] = '\0';   // Final d'string
   
   i += 5;  // Posició a on comença el To
   j = 0;
   while (peticion[i] != '\r') {
      to[j] = peticion[i];
      i++; j++;
   }
   to[j-1] = '\0';   // Final d'string
   
   i += 10;  // Posició a on comença el Subject
   j = 0;
   while (peticion[i] != '\r') {
      subject[j] = peticion[i];
      i++; j++;
   }
   subject[j-1] = '\0';   // Final d'string
   
}

/**
   Realiza el tratamiento de una petición WHEREIS segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_whereis(char *peticion, char *from, char *to, char *subject) {
   int i = 15; // Posició a on comença el From
   int j = 0;
   while (peticion[i] != '\r') {
      from[j] = peticion[i];
      i++; j++;
   }
   from[j-1] = '\0';   // Final d'string
   
   i += 5;  // Posició a on comença el To
   j = 0;
   while (peticion[i] != '\r') {
      to[j] = peticion[i];
      i++; j++;
   }
   to[j-1] = '\0';   // Final d'string
   
   i += 18;  // Posició a on comença el Subject
   j = 0;
   while (peticion[i] != '\r') {
      subject[j] = peticion[i];
      i++; j++;
   }
   subject[j-1] = '\0';   // Final d'string
}

/**
   Realiza el tratamiento de una petición GET-LIST segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_getlist(char *peticion, char *from, char *to) {
   int i = 16; // Posició a on comença el From
   int j = 0;
   while (peticion[i] != '\r') {
      from[j] = peticion[i];
      i++; j++;
   }
   from[j-1] = '\0';   // Final d'string
   
   i += 5;  // Posició a on comença el To
   j = 0;
   while (peticion[i] != '\r') {
      to[j] = peticion[i];
      i++; j++;
   }
   to[j-1] = '\0';   // Final d'string
}

/**
   Realiza el tratamiento de una petición UNREGISTER segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_unregister(char *peticion, char *from, char *to) {
   int i = 18; // Posició a on comença el From
   int j = 0;
   while (peticion[i] != '\r') {
      from[j] = peticion[i];
      i++; j++;
   }
   from[j-1] = '\0';   // Final d'string
   
   i += 5;  // Posició a on comença el To
   j = 0;
   while (peticion[i] != '\r') {
      to[j] = peticion[i];
      i++; j++;
   }
   to[j-1] = '\0';   // Final d'string
}

/* ------------------- */
/* PROGRAMA PRINCIPAL  */
/* ------------------- */

int main (int argc, char **argv)
{
   int valor, temp, id;
   int len_info_sock;
   unsigned int puerto;
   char *nombre_servidor = NULL;
   struct sockaddr_in dir_socket_servidor;
   struct sockaddr_in dir_socket_cliente;
   char peticion[BUFSIZ];

   Cola *cola = NULL;
   UserPort usuario;
   UserPort *usuario_pt;
   char from[TAM_USER];
   char to[TAM_USER];
   char subject[TAM_USER];
   char respuesta[BUFSIZ];
   char respuesta_tmp[BUFSIZ];
   int i;
   
   // Obtencion del parametro 'puerto'
   if (argc != 2) {
      fprintf(stderr,"Sintaxis: server puerto \n");
      fprintf(stderr,"\t (puerto: valor entre 0 y 65635) \n");
      exit(-1);
   }
   else {
      puerto = atoi((char *)argv[1]);
   }

   // Cambio de controlador para ctrl-c y asi finalizar correctamente el programa
   // al abortar el bucle de recepcion de paquetes con ctrl-c

   signal (SIGINT, controlador);
   
   // Que los procesos hijos no se queden zombies al terminar (la unica forma de
   // salir de zombie es haciendo un wait el padre, cosa que no nos interesa)
   signal(SIGCHLD,SIG_IGN);
   
   // Creacion del socket
   sock_S = socket(AF_INET,SOCK_DGRAM,0);
   if (sock_S == -1) {
      fprintf(stderr,"Error al crear socket en el servidor! \n");
      exit(-1);
   }
   
   // Obtencion del nombre y direccion del host local
   // y contruccion de la estructura sockaddr_in
   
   // NOTA: Escuchar en todas las Interfaces/IPs de la máquina (0.0.0.0)
   dir_socket_servidor.sin_port = htons(puerto);            // Puerto a utilizar
   dir_socket_servidor.sin_family = AF_INET;                // Protocolo Internet
   dir_socket_servidor.sin_addr.s_addr = INADDR_ANY;
   bzero(&(dir_socket_servidor.sin_zero),8);
   
   // Enlace del socket con el puerto a utilizar
   // Previamente se reusa la direccion del socket, de forma que no
   // sea necesario esperar un tiempo para usar de nuevo el pueto
   temp = 1;
   setsockopt(sock_S,SOL_SOCKET,SO_REUSEADDR,&temp,sizeof(temp));
   
   valor = bind(sock_S,(struct sockaddr *) &dir_socket_servidor,sizeof(dir_socket_servidor));
   if (valor == -1) {
      perror("Error al enlazar el socket con el puerto! \n");
      exit(-1);
   }
   
   
   mensaje_inicial_servidor(nombre_servidor,&dir_socket_servidor);

   // Conectamos con la BD e inicializamos las tablas
   mysql_conectar_db(&mysql, MYSQL_SERVER, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
   mysql_borrar_tablas(&mysql);
   mysql_crear_tablas(&mysql);
   
   // Ejecucion indefinida del servidor, hasta abortar su ejecucion con ctrl+c,
   // procesando concurrentemente todas las peticiones recibidas de los clientes
   len_info_sock = sizeof(dir_socket_cliente);
   while (1) {
      valor = recvfrom(sock_S,peticion,BUFSIZ,0,(struct sockaddr *)&dir_socket_cliente,
                       &len_info_sock);
      peticion[valor] = '\0';  // Ponemos final de string a lo que hemos recibido
                               // se evitan problemas.
      if (valor == -1) {
         perror("Error al recibir datos");
         exit(-1);
      }

#ifdef DEBUG
      fprintf(stderr, "Conexión cliente: %s - Peticion:\n", inet_ntoa(dir_socket_cliente.sin_addr));
      fprintf(stderr, "--- PETICION ---\n");
      fprintf(stderr, peticion);
      fprintf(stderr, "----------------\n");
      fflush(stderr);
#endif
      /////
      
      id = fork();         // Creacion de un proceso hijo para atender la peticion cliente
      switch (id) {
         case 0:                // Proceso hijo
            // Peticion REGISTER
            if (strncmp(peticion, "REGISTER", 8) == 0) {
               tratar_register(peticion, from, to, subject);
               if (mysql_cliente_dado_de_alta(&mysql, from)) {
                  // Ya Existe!
                  snprintf(respuesta, BUFSIZ, "NEGATIVE\n\rTo: %s\n\rFrom: %s\n\rSubject: reason: user already registered\n\r", from, to);
                  sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
               } else {
                  if (mysql_alta_cliente(&mysql, from, subject)==0) {
                     snprintf(respuesta, BUFSIZ, "REGISTER-ACK\n\rTo: %s\n\rFrom: %s\n\r", from, to);
                     sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
                  } else {
                     // ERROR
                  }
               }
               
            // Peticion WHEREIS
            } else if (strncmp(peticion, "WHEREIS", 7) == 0) {
               tratar_whereis(peticion, from, to, subject);
               if (mysql_info_cliente(&mysql, subject, &usuario) != 0) {
                  // No existe
                  snprintf(respuesta, BUFSIZ, "NEGATIVE\n\rTo: %s\n\rFrom: %s\n\rSubject: reason: user is not registered\n\r", from, to);
                  sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
               } else {
                  // Existe
                  snprintf(respuesta, BUFSIZ, "LOCATION\n\rTo: %s\n\rFrom: %s\n\rSubject: %s:%s\n\r", from, to, usuario.user, usuario.port);
                  sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
               }
               
            // Peticion GET-LIST
            } else if (strncmp(peticion, "GET-LIST", 8) == 0) {
               tratar_getlist(peticion, from, to);
               cola = (Cola *)crear_cola();
               mysql_listar_cliente (&mysql, cola);
               // Enviar listado
               snprintf(respuesta, BUFSIZ, "ONLINE-LIST\n\rTo: %s\n\rFrom: %s\n\rSubject: %i are online\n\r", from, to, cola->total);
               reiniciar_PDI(cola);
               for (i=0; i<cola->total; i++) {
                  usuario_pt = (UserPort *)obtener_elemento_PDI(cola);
                  snprintf(respuesta_tmp, BUFSIZ, "%s %s\n\r", usuario_pt->user, usuario_pt->port);
                  strcat(respuesta, respuesta_tmp);
                  avanzar_PDI(cola);
               }
               vaciar_cola(cola);
               sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
            
            // Peticion UNREGISTER
            } else if (strncmp(peticion, "UNREGISTER", 10) == 0) {
               tratar_unregister(peticion, from, to);
               if (mysql_cliente_dado_de_alta(&mysql, from)) {
                  if (mysql_baja_cliente(&mysql, from)==0) {
                     snprintf(respuesta, BUFSIZ, "UNREGISTER-ACK\n\rTo: %s\n\rFrom: %s\n\r", from, to);
                     sendto(sock_S,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,len_info_sock);
                  } else {
                     // Error
                  }
               } else {
                  // No existe
               }
            }
            
#ifdef DEBUG
            fprintf(stderr, "--- RESPUESTA ---\n");
            fprintf(stderr, "%s", respuesta);
            fprintf(stderr, "-----------------\n");
#endif
            
            close(sock_S); // Petición atendida
            exit(0);   // Finaliza hijo
            break;
            
         default:              // Proceso padre
            break;
      }
   }
   
   return (0);   
}
