/****************************************************************************
*  XARXES DE COMPUTADORS II                                                 
*
*  Practica VoIP
*
*  Realizado por: 
*     Francisco Jose Aguilar Celdran
*     Sergio Blanco Cuaresma
*
*  libclient.c: 
*     Implementación de funciones que permiten interactuar con el servidor
*     o otro usuario (iniciar llamada) segun el protocolo establecido en
*     la practica.
*     La funcion crear_socket se utilizara para generar los sockets necesarios
*     que seran solicitado en el resto de funciones.
****************************************************************************/
#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 <sys/wait.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include <glib.h>

#include "cola.h"
#include "phone.h"
#include "libclient.h"

            
/**
   Realiza el tratamiento de una petición INVITE segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_invite(char *peticion, char *from, char *to, char *callid) {
   int i = 14; // 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 Call-ID
   j = 0;
   while (peticion[i] != '\r') {
      callid[j] = peticion[i];
      i++; j++;
   }
   callid[j-1] = '\0';   // Final d'string
}


/**
   Realiza el tratamiento de una petición BYE segun el protocolo utilizado, 
   devolviendo los campos importantes.
**/
void tratar_bye(char *peticion, char *from, char *to, char *callid, char *subject) {
   int i = 11; // 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 Call-ID
   j = 0;
   while (peticion[i] != '\r') {
      callid[j] = peticion[i];
      i++; j++;
   }
   callid[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
}

   
/**
   Nos da de alta en el servidor
   Devuelve 1 en caso de error y devuelve el mensaje del mismo por la variable
   error (debe existir antes de la llamada)
**/
int Register(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to, char *port, char *error){
   struct sockaddr_in dir_socket_recibido;  /*info de quien recibimos*/
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];
   int i=0, j=0;

   snprintf(peticion, BUFSIZ, "REGISTER\n\rFrom: %s\n\rTo: %s\n\rSubject: %s\n\r", from, to, port);

   /*Enviamos*/
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   /*Recibimos*/
   recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
   
   if (strncmp(respuesta, "REGISTER-ACK", 12) == 0) {
      return 0;
   } else {
      i = 0;
      for (j=0; j<3; j++) {
         while (respuesta[i]!='\r') {
            i++;
         }
         i++;
      }
      
      i += 9; j = 0;
      while (respuesta[i]!='\r') {
         error[j] = respuesta[i];
         i++; j++;
      }
      error[j-1] = '\0';
      return 1;
   }
}


/**
   Nos da de baja en el servidor
   Devuelve 1 en caso de error
**/
int Unregister(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to){
   struct sockaddr_in dir_socket_recibido;  /*info de quien recibimos*/
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];

   snprintf(peticion, BUFSIZ, "UNREGISTER\n\rFrom: %s\n\rTo: %s\n\r", from, to);

   /*Enviamos*/
   //sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   /*Recibimos*/
   recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
   
   if (strncmp(respuesta, "UNREGISTER-ACK", 14) == 0) {
      return 0;
   } else {
      return 1;
   }
}

/**
   Busca un usuario en el servidor
   Devuelve 1 en caso de no encontrarlo
   Si lo encuentra lo devuelve en una estructura UserPort por location (debe
   existir previamente)
**/
int Whereis(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to, char *userToFind, UserPort *location){
   struct sockaddr_in dir_socket_recibido;  /*info de quien recibimos*/
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];
   int i=0, j=0;

   snprintf(peticion, BUFSIZ, "WHEREIS\n\rFrom: %s\n\rTo: %s\n\rSubject: whereis %s\n\r", from, to, userToFind);

   /*Enviamos*/
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   /*Recibimos*/
   recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
   
   if (strncmp(respuesta, "LOCATION", 8) == 0) {
      i = 0;
      for (j=0; j<3; j++) {
         while (respuesta[i]!='\r') {
            i++;
         }
         i++;
      }
      
      i += 9; j = 0;
      while (respuesta[i]!=':') {
         location->user[j] = respuesta[i];
         i++; j++;
      }
      location->user[j] = '\0';
      i++; j=0;
      while (respuesta[i]!='\r') {
         location->port[j] = respuesta[i];
         i++; j++;
      }
      location->port[j-1] = '\0';
      return 0;
   } else {
      // NEGATIVE: user is not registered
      return 1;
   }
}

/**
   Lista los usuarios dados de alta en el servidor, en la cola se añaden todos 
   los resultados.
   Devuelve 1 en caso de no encontrar nada
**/
// La cola ya tiene que estar creada
int GetList(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to, Cola *cola){
   struct sockaddr_in dir_socket_recibido;  /*info de quien recibimos*/
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];
   int i=0, j=0, l=0;
   char total[16];
   int itotal;
   UserPort *usuario;
   
   snprintf(peticion, BUFSIZ, "GET-LIST\n\rFrom: %s\n\rTo: %s\n\r", from, to);

   /*Enviamos*/
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   /*Recibimos*/
   recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
   
   if (strncmp(respuesta, "ONLINE-LIST", 11) == 0) {
      i = 0;
      for (j=0; j<3; j++) {
         while (respuesta[i]!='\r') {
            i++;
         }
         i++;
      }
      
      i += 9; j = 0;
      while (respuesta[i]!=' ') {
         total[j] = respuesta[i];
         i++; j++;
      }
      itotal = atoi(total);
      
      i += 13;
      for (l = 0; l < itotal; l++) {
         usuario = (UserPort *)malloc(sizeof(UserPort));
         
         // Leer usuario
         j=0;
         while (respuesta[i]!=' ') {
            usuario->user[j] = respuesta[i];
            i++; j++;
         }
         usuario->user[j] = '\0';
         
         // Leer puerto
         i++; j = 0;
         while (respuesta[i]!='\r') {
            usuario->port[j] = respuesta[i];
            i++; j++;
         }
         usuario->port[j-1] = '\0';
         i++;
         
         encolar(cola, (void *)usuario);
      }
      return 0;
   } else {
      // NEGATIVE: user is not registered
      return 1;
   }
}

/**
   A partir de un nombre de usuario y maquina construye una dirección.
   e.g. usuario, google.es -> usuario@google.es
**/
char *construir_direccion(char *usuario, char *maquina) {
   int size = (strlen(usuario) + strlen(maquina)) * sizeof(char) +2;
   char *direccion = (char *)malloc(size);
   
   snprintf(direccion, size, "%s@%s", usuario, maquina);
   return direccion;
}

/**
   A partir de una direccion obtiene el nombre de usuario y maquina por separado
   e.g. usuario@google.es -> usuario, google.es
**/
void tratar_direccion(char *direccion, char *usuario, char *maquina) {
   int i = 0;
   int j = 0;
   while (direccion[i] != '@') {
      usuario[j] = direccion[i];
      i++; j++;
   }
   usuario[j] = '\0';   // Final d'string
   
   i++; j = 0;
   while (direccion[i] != '\0') {
      maquina[j] = direccion[i];
      i++; j++;
   }
   maquina[j] = '\0';   // Final d'string
}


/**
   Solicita a otro usuario la realización de una llamada
   Devuelve 1 en caso de aceptar
   Devuelve 1 en caso de estar ocupado
   Devuelve 2 en caso de ser rechazado
**/
int Invite(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to, char *callid){
   struct sockaddr_in dir_socket_recibido;  /*info de quien recibimos*/
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];
   char usuarioFrom[TAM_USER];
   char maquinaFrom[255];
   
   tratar_direccion(from, usuarioFrom, maquinaFrom);
   
   GRand *semilla = g_rand_new(); // Time or /dev/urandom by default
   int icallid = g_rand_int(semilla);   // random 32 bits
   
   snprintf(callid, 255,"%i@%s", icallid, maquinaFrom);
   snprintf(peticion, BUFSIZ, "INVITE\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\r", from, to, callid);
   
   /*Enviamos*/
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   /*Recibimos*/
   recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
   
   
   if (strncmp(respuesta, "RESPONSE 600 Busy", 17) == 0) {
      return 1;
   }
   
   if (strncmp(respuesta, "RESPONSE 180 Ringing", 20) == 0) {
      /*Recibimos*/
      recvfrom(sock,respuesta,BUFSIZ,0,(struct sockaddr *)&dir_socket_recibido,(socklen_t *)sizeof(dir_socket_recibido));
      
      if (strncmp(respuesta, "RESPONSE 200 OK", 15) == 0) {
         return 0;
      } else if (strncmp(respuesta, "RESPONSE 400 Refused", 20) == 0) {
         return 2;
      }
   }
   
   return 1;
}


/**
   Indica a otro usuario que la llamada ha acabado.
**/
int Bye(int sock, struct sockaddr_in dir_socket_servidor, char *from, char *to, char *callid, char *reason){
   char peticion[BUFSIZ];
   
   snprintf(peticion, BUFSIZ, "BYE\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\rSubject: %s\n\r", from, to, callid, reason);

   /*Enviamos*/
   sendto(sock, peticion, strlen(peticion),0,(struct sockaddr *)&dir_socket_servidor,sizeof(dir_socket_servidor));
   
   return 0;
}


/**
   Crea un socket y un sockaddr_in para poder realizar las conexiones al servidor
   indicado. Protocolo UDP.
   - Sera utilizado tanto para conectar con el servidor como con otros usuarios
**/
void crear_socket(char *nombre_servidor, unsigned int puerto, int *sock, struct sockaddr_in *dir_socket_servidor) {
   struct sockaddr_in dir_socket_cliente;    /*estructura:puerto,@IP,protocolo*/
   
   struct hostent *info_host_S;      /*estructura con info del host Serv*/ 
   struct in_addr ** addr_host_S;    /*direccion IP del host servidor*/ 
   
   info_host_S= (struct hostent *) malloc (sizeof(struct hostent)); 
   addr_host_S= (struct in_addr **) malloc (sizeof(struct in_addr)*10);
   
   // Creacion del socket

   *sock = socket(AF_INET,SOCK_DGRAM,0);
   if (*sock == -1) {
      fprintf(stderr,"Error al crear socket en el servidor! \n");
      exit(-1);
   }
      
   // NOTA: Que permita recibir datos por cualquier interfaz/ip ya que el
   dir_socket_cliente.sin_addr.s_addr = INADDR_ANY;
   dir_socket_cliente.sin_port= 0;                /*puerto 0: se reseva uno libre*/  
   dir_socket_cliente.sin_family= AF_INET;        /*protocolo que usamos*/     
   
   /*Dejamos reusar la direccion de socket.Sin esta instruccion hemos*/ 
   /*de esperar un tiempo para poder usar de nuevo el puerto*/ 
   int tmp=1; 
   setsockopt(*sock,SOL_SOCKET,SO_REUSEADDR,&tmp,sizeof(tmp)); 
   
 
   /*enlazamos socket con puerto*/ 
   if ((bind(*sock,(struct sockaddr *) &dir_socket_cliente, sizeof(dir_socket_cliente)))==-1) { 
      fprintf(stderr, "Error Cliente: Conectar a puerto"); 
      perror((char *)bind); 
      exit(1);
    } 
   
   /*obtenemos direccion del host servidor*/ 
   if ((info_host_S=gethostbyname((char *)nombre_servidor))==NULL) { 
      fprintf(stderr, "Error cliente:Obtener direccion servidor"); 
      perror((char *) gethostbyname); 
      exit(1); 
    } 
  
   addr_host_S= (struct in_addr **)info_host_S->h_addr_list; 
   
   dir_socket_servidor->sin_addr= ** addr_host_S;  /*Direccion del server*/    
   dir_socket_servidor->sin_port= htons(puerto);  /*Puerto del server*/   
   dir_socket_servidor->sin_family= AF_INET;
}


