/****************************************************************************
*  XARXES DE COMPUTADORS II                                                 
*
*  Practica VoIP
*
*  Realizado por: 
*     Francisco Jose Aguilar Celdran
*     Sergio Blanco Cuaresma
*
*  phone.c: 
*     Fichero principal que inicializa la interficie gráfica.
*     Contiene funciones que se ejectuan en threads para tratar
*     eventos en segundo plano: 
*        - Peticiones de inicio de llamada
*        - Recepción y envio de audio: utilizando una funcion externa escrita en C++
****************************************************************************/
#include <glib.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include "cola.h"
#include "libclient.h"
#include "callbacks.h"
#include "interface.h"
#include "phone.h"
#include "librtp.h"

// Variables globales a nivel de programa
char *usuario_actual = NULL;
char *maquina_actual = NULL;
char *puerto_actual = NULL;
int estado;
char *miFrom;  // mi campo from
char *toServer;   // campo from del servidor
char callid[255];
char puerto_local[TAM_PORT];

int serverSock;
struct sockaddr_in dir_serverSock;
int llamadaSock = -1;
struct sockaddr_in dir_llamadaSock;
// FIN Variables globales a nivel de programa

GThread  *recepcionSonidoThread, *envioSonidoThread;

int sockfd; // Socket del demonio de recepcion de llamadas

/**
   Crea socket global sockfd para que sea utilizado por call_daemon, elige como
   puerto origen uno aleatorio y lo devuelve.
   Devuelve 1 en caso de error, de lo contrario devuelve el puerto seleccionado.
**/
unsigned int call_socket() {
   // int sockfd -> es global
   struct sockaddr_in my_addr;    // my address information   
   struct sockaddr_in dir_socket_servidor;    /*estructura:puerto,@IP,protocolo*/
   int puerto;
   
   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

   sockfd = socket(AF_INET,SOCK_DGRAM,0);
   if (sockfd == -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_servidor.sin_addr.s_addr = INADDR_ANY;
   dir_socket_servidor.sin_port= 0;                /*puerto 0: se reseva uno libre*/  
   dir_socket_servidor.sin_family= AF_INET;        /*protocolo que usamos*/     
   bzero(&(dir_socket_servidor.sin_zero),8);
   
   /*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(sockfd,SOL_SOCKET,SO_REUSEADDR,&tmp,sizeof(tmp)); 
   
   /*enlazamos socket con puerto*/
   if ((bind(sockfd,(struct sockaddr *) &dir_socket_servidor,sizeof(dir_socket_servidor))==-1)) { 
      fprintf(stderr, "Error al enlazar el socket con el puerto! \n"); 
      perror((char *)bind); 
      exit(1);
    } 
   
   // Obtenemos el puerto que bind nos ha asignado
   socklen_t len = sizeof(struct sockaddr);
   getsockname(sockfd, (struct sockaddr *)&my_addr, &len);
   //puerto = (unsigned int *)malloc(sizeof(unsigned int));
   puerto = (unsigned int)ntohs(my_addr.sin_port);
   
#ifdef DEBUG
   fprintf(stderr, "Puerto: %i\n", ntohs(my_addr.sin_port));
   fflush(stderr);
#endif
   
   return puerto;
}

/**
   Atiende peticiones de llamada segun el protocolo de la practica.
   No crea hijos por cada peticion ya que complicaria el diseño general
   del programa y el beneficio es mínimo (no es un servidor).
   Sera ejecutado por un thread, de ahi que se deba utilizar gdk_threads_enter y
   gdk_threads_leave cada vez que queramos modificar algo de la interficie.
   ** Es encesario ejecutar file_transfer_socket antes que esta para crear
   ** el socket global 'sockfd'.
**/
void call_daemon() {
   // int sockfd -> es global
   int sin_size;
   struct sockaddr_in dir_socket_cliente; // connector's address information
   int valor;
   char peticion[BUFSIZ];
   char respuesta[BUFSIZ];
   char cfrom[TAM_USER];   // campo from
   char cto[TAM_USER];
   char ccallid[TAM_USER];
   
   
   while(1) {
     sin_size = sizeof(struct sockaddr_in);
     valor = recvfrom(sockfd,peticion,BUFSIZ,0,(struct sockaddr *)&dir_socket_cliente,
                       &sin_size);
                       
#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
     
      // Invitacion a hablar
      if (strncmp(peticion, "INVITE", 6) == 0) {
         tratar_invite(peticion, cfrom, cto, ccallid);
         char pregunta[100];
         
         // Si ya estamos hablando o intentamos llamar => estamos ocupados, no
         // aceptamos
         if ((estado == HABLANDO) || (estado == LLAMANDO)) {
            snprintf(respuesta, BUFSIZ, "RESPONSE 600 Busy\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\r", cto, cfrom, ccallid);
            sendto(sockfd,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,sin_size);
#ifdef DEBUG
            fprintf(stderr, "--- RESPUESTA ---\n");
            fprintf(stderr, "%s", respuesta);
            fprintf(stderr, "-----------------\n");
#endif
         } else {
            // No estamos hablando o intentando llamar asi que se pregunta al
            // usuario si acepta la llamada.
            snprintf(respuesta, BUFSIZ, "RESPONSE 180 Ringing\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\r", cto, cfrom, ccallid);
            sendto(sockfd,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,sin_size);
#ifdef DEBUG
            fprintf(stderr, "--- RESPUESTA ---\n");
            fprintf(stderr, "%s", respuesta);
            fprintf(stderr, "-----------------\n");
#endif

            snprintf(pregunta, 100,"El usuario %s quiere hablar con usted ¿Realizar la connexión?", cfrom);
            gdk_threads_enter();
            int accion = preguntar(_(pregunta));
            gdk_threads_leave();
            // Si ha aceptado la llamada
            if (accion == 0) {
               snprintf(respuesta, BUFSIZ, "RESPONSE 200 OK\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\r", cto, cfrom, ccallid);
               sendto(sockfd,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,sin_size);
               
               // Liberamos los datos actuales del usuario al que queremos/estamos
               // llamando (solo en caso de que esten inicializados)
               if (usuario_actual != NULL) {
                  free (usuario_actual);
                  free (maquina_actual);
                  free (puerto_actual);
               }
               usuario_actual = (char *)malloc(TAM_USER * sizeof(char));
               maquina_actual = (char *)malloc(255 * sizeof(char));
               puerto_actual = (char *)malloc(TAM_PORT * sizeof(char));
               
               // Establecemos usuario_actual y maquina_actual la información
               // del usuario con el que vamos a hablar
               tratar_direccion(cfrom, usuario_actual, maquina_actual);
               
               // Creamos socket por si hay que enviar Bye!
               // Necesitamos saber su puerto
               UserPort location;
               Whereis(serverSock, dir_serverSock, miFrom, toServer, cfrom, &location);
               snprintf(puerto_actual, TAM_PORT, "%s", location.port);
               crear_socket(maquina_actual, atoi(puerto_actual), &llamadaSock, &dir_llamadaSock);
               
               // Llamamos, esto creará dos thread
               llamar(maquina_actual);
               
               // Actualizamos estado
               estado = HABLANDO;
               gdk_threads_enter();
               visualizar_estado(estado);
               gdk_threads_leave();
#ifdef DEBUG
               fprintf(stderr, "--- RESPUESTA ---\n");
               fprintf(stderr, "%s", respuesta);
               fprintf(stderr, "-----------------\n");
#endif
            } else {
               // Ha rechazado la llamada
               snprintf(respuesta, BUFSIZ, "RESPONSE 400 Refused\n\rFrom: %s\n\rTo: %s\n\rCall-ID: %s\n\r", cto, cfrom, ccallid);
               sendto(sockfd,respuesta,strlen(respuesta),0,(struct sockaddr *)&dir_socket_cliente,sin_size);
#ifdef DEBUG
               fprintf(stderr, "--- RESPUESTA ---\n");
               fprintf(stderr, "%s", respuesta);
               fprintf(stderr, "-----------------\n");
#endif
            }
         }
            
         
      // Peticion BYE
      } else if (strncmp(peticion, "BYE", 3) == 0) {
         if (estado == HABLANDO) {
            colgar(RECIBIMOS);
            estado = REGISTRADO;
            gdk_threads_enter();
            visualizar_estado(estado);
            gdk_threads_leave();
#ifdef DEBUG
            fprintf(stderr, "--- RESPUESTA ---\n");
            //fprintf(stderr, "%s", respuesta);
            fprintf(stderr, "----(colgamos)---\n");
#endif
         }
      }
   }
}


/*
   Cuelga. 
   - Envia una peticion Bye segun el protocolo establecido en caso de que nosotros
   seamos quienes originamos el cuelgue (si lo origina el otro extremo habremos
   recibido un BYE y no es necesario devolverlo).
   - Pone la variable global conversacionActiva a 0 lo que hara terminar los
   threads de envio y recepcion de sonido.
   
   El sentido puede ser ORIGINAMOS o RECIBIMOS.
*/
void colgar(int sentido) {
   
   if (sentido == ORIGINAMOS) {
      char *toCliente = construir_direccion(usuario_actual, maquina_actual);
      
      Bye(llamadaSock, dir_llamadaSock, miFrom, toCliente, callid, "Colgar");
   }
   
   conversacionActiva = 0; // global
   g_thread_join(envioSonidoThread);
   g_thread_join(recepcionSonidoThread);
}

/*
   Llamada a funcion escrita en C++ que permite enviar sonido a una maquina 
   determinada. Se ejecutara como thread.
*/
gpointer enviarSonidoThread(gpointer maquina) {
   enviarSonido((char *)maquina);
   g_thread_exit(NULL);
   return NULL;
}

/*
   Llamada a funcion escrita en C++ que permite recibir sonido.
   Se ejecutara como thread.
*/
gpointer recibirSonidoThread(gpointer data) {
   recibirSonido();
   g_thread_exit(NULL);
   return NULL;
}

/**
   Lanza los 2 threads de envio y recepcion de sonido. Estos no acabaran mientras
   conversacionActiva sea igual a 1.
**/
void llamar(char *maquina) {
   conversacionActiva = 1; // global
   envioSonidoThread = g_thread_create (enviarSonidoThread, (gpointer)maquina, TRUE, NULL);
   recepcionSonidoThread = g_thread_create (recibirSonidoThread, NULL, TRUE, NULL);
}

 
/*
   Funcion que se ejecutara como thread y llamara al demonio que controla
   las peticiones de llamadas segun el protocolo.
*/
gpointer escuchar(gpointer data){
   int puerto = call_socket();
   snprintf(puerto_local, TAM_PORT, "%i", puerto);
   call_daemon();
   g_thread_exit(NULL);
   return NULL;
}


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

int main(int argc, char *argv[]) {
    char usuario_servidor[TAM_USER];
    char maquina_servidor[255];
    char puerto_servidor[TAM_PORT];

    if (argc < 4) {
       printf("Uso: phone usuario@localhost usuario@servidor puerto_servidor\n");
       exit(1);
    }
    
    
    /*conversacionActiva =1;
    enviarSonido("localhost");*/
    // Guardamos en variables globales los datos introducidos por parametro
    miFrom = argv[1];
    toServer = argv[2];
    
    // Obtenemos información necesaria para crear el socket que comunique con el servidor
    tratar_direccion(argv[2], usuario_servidor, maquina_servidor);
    snprintf(puerto_servidor, 7, "%s", argv[3]);
    
    // Socket que contecta con el servidor
    crear_socket(maquina_servidor, atoi(puerto_servidor), &serverSock, &dir_serverSock);
    
    // Estado inicial
    estado = DEREGISTRADO;
    
    // Inicializamos el sistema de threads
    g_thread_init(NULL);
    gdk_threads_init();
                                                                                                                     
    gtk_set_locale ();
    gtk_init (&argc, &argv);
    
    // Creamos un thread que atendera las peticiones de llamadas
    g_thread_create (escuchar, NULL, FALSE, NULL);

    

    /* load the interface */
    xml = glade_xml_new("phone.glade", "mainWin", NULL);
    xmlDialog = glade_xml_new("phone.glade", "buscarDialog", NULL);

    /* connect the signals in the interface */
    glade_xml_signal_autoconnect(xml);
    glade_xml_signal_autoconnect(xmlDialog);
    
    
    asignar_elementos();
    preparar_listado();
    visualizar_estado(estado);
    

    /* start the event loop */
    gdk_threads_enter();
    gtk_main();
    gdk_threads_leave();
    
    close(serverSock);
    return 0;
}

