/*
NAME:       TCPServer [Port number]
SYNOPSIS:    TCPServer 
DESCRIPTION:  The program creates a TCP socket in the inet, 
              listens for connections from TCPClients, accepts
              clients into private sockets, and forks
              an echo process to ``serve'' the cleint.
*/

#include <stdio.h>
#include <errno.h>
#include <sys/time.h>

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>  /*gethostname*/
#include <pthread.h>


#define FIFONAME    "myfifo"
#define MAXHOSTNAME 80

void reusePort(int sock);   
void EchoServe(int psd, struct sockaddr_in from);
void * udp_server_loop(void * args);
void * tcp_server_loop(void * args);

struct udp_thread_args{
  int sd_udp;
  double buf_udp;
};

struct tcp_thread_args{
  int psd;
  double *value;
  struct sockaddr_in from;
};

int main(int argc,  char *argv[]) {
  int   sd_udp;
  struct   sockaddr_in server_udp;
  struct  hostent *hp_udp, *gethostbyname();
  struct  servent *sp_udp;
  struct sockaddr_in from_udp;
  int fromlen;
  int length;
  char ThisHost[MAXHOSTNAME];
  double buf_udp;

  /********************************************************/
   /*CONFIGURACIÓN SERVIDOR UDP*/
   /********************************************************/

  gethostname(ThisHost, MAXHOSTNAME);

  printf("----OPC/Server running at host NAME: %s\n", ThisHost);
  if  ( (hp_udp = gethostbyname(ThisHost)) == NULL ) {
    fprintf(stderr, "Can't find host %s\n", ThisHost);
    exit(-1);
  }
  bcopy ( hp_udp->h_addr, &(server_udp.sin_addr), hp_udp->h_length);
  printf("    (OPC/Server INET ADDRESS is: %s )\n", inet_ntoa(server_udp.sin_addr));


  /* Construct name of socket to send to. */
  server_udp.sin_family = hp_udp->h_addrtype;
  /*OR server.sin_family = AF_INET; */


  server_udp.sin_addr.s_addr = htonl(INADDR_ANY);

  /*OR    server.sin_port = sp->s_port; */
  if (argc == 2)
      server_udp.sin_port = htons(atoi(argv[1])); 
  else
      server_udp.sin_port = htons(0);  

  /*   Create socket on which to send  and receive */

  /* sd = socket (PF_INET,SOCK_DGRAM,0); */
  sd_udp = socket (hp_udp->h_addrtype,SOCK_DGRAM,0);

  if (sd_udp<0) {
    perror("opening stream socket");
    exit(-1);
  }

  reusePort(sd_udp);

  if ( bind( sd_udp, (struct sockaddr *) &server_udp, sizeof(server_udp) ) ) {
    close(sd_udp);
    perror("binding name to stream socket");
    exit(-1);
  }

  length = sizeof(server_udp);
  if ( getsockname (sd_udp, (struct sockaddr *) &server_udp,&length) ) {
    perror("getting socket name");
    exit(0);
  }

  printf("Server Port is: %d\n", ntohs(server_udp.sin_port));
  listen(sd_udp,5);
  fromlen = sizeof(from_udp);

  struct timeval tv;
  tv.tv_sec = 30;
  tv.tv_usec = 0;
  setsockopt(sd_udp, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

  /********************************************************/
   /*CONFIGURACIÓN SERVIDOR TCP*/
   /********************************************************/

  int   sd_tcp, psd;
  struct   sockaddr_in server_tcp;
  struct  hostent *hp_tcp, *gethostbyname();
  struct  servent *sp_tcp;
  struct sockaddr_in from_tcp;

  gethostname(ThisHost, MAXHOSTNAME);

  printf("----TCP/Server running at host NAME: %s\n", ThisHost);
  if  ( (hp_tcp = gethostbyname(ThisHost)) == NULL ) {
    fprintf(stderr, "Can't find host %s\n", ThisHost);
    exit(-1);
  }
  bcopy ( hp_tcp->h_addr, &(server_tcp.sin_addr), hp_tcp->h_length);
  printf("    (TCP/Server INET ADDRESS is: %s )\n", inet_ntoa(server_tcp.sin_addr));


  /* Construct name of socket to send to. */
  server_tcp.sin_family = hp_tcp->h_addrtype;
  /*OR server.sin_family = AF_INET; */


  server_tcp.sin_addr.s_addr = htonl(INADDR_ANY);

  /*OR    server.sin_port = sp->s_port; */
  if (argc == 2)
      server_tcp.sin_port = htons(atoi(argv[1])); 
  else
      server_tcp.sin_port = htons(0);  

  /*   Create socket on which to send  and receive */

  /* sd = socket (PF_INET,SOCK_DGRAM,0); */
  sd_tcp = socket (hp_tcp->h_addrtype,SOCK_STREAM,0);

  if (sd_tcp<0) {
    perror("opening stream socket");
    exit(-1);
  }

  reusePort(sd_tcp);

  if ( bind( sd_tcp, (struct sockaddr *) &server_tcp, sizeof(server_tcp) ) ) {
    close(sd_tcp);
    perror("binding name to stream socket");
    exit(-1);
  }

  length = sizeof(server_tcp);
  if ( getsockname (sd_tcp, (struct sockaddr *) &server_tcp,&length) ) {
    perror("getting socket name");
    exit(0);
  }

  printf("Server Port is: %d\n", ntohs(server_tcp.sin_port));
  listen(sd_tcp,5);
  fromlen = sizeof(from_tcp);



  /********************************************************/
   /*LEVANTAR SERVIDORES*/
   /********************************************************/

  // Variables para creación hilo que captura datos
  int err;
  pthread_t tid_udp, tid_tcp;
  int* pStatus;

  // Se preparan argumentos para hilo
  struct udp_thread_args *udp_args = malloc(sizeof(struct udp_thread_args));
  udp_args->sd_udp = sd_udp;
  udp_args->buf_udp = buf_udp;

  err = pthread_create(&tid_udp, NULL, udp_server_loop, udp_args);
  if (err != 0){
    printf("can't create thread \n");
    exit(0);
  }
  

  //Bucle que espera por conexiones tcp y crea sus respectivos hilos
  for(;;){
    psd  = accept(sd_tcp, (struct sockaddr *) &from_tcp, &fromlen);

    struct tcp_thread_args *tcp_args = malloc(sizeof(struct tcp_thread_args));
    tcp_args->psd = psd;
    tcp_args->from = from_tcp;
    tcp_args->value = &udp_args->buf_udp;
    err = pthread_create(&tid_tcp, NULL, tcp_server_loop, tcp_args);
    if (err != 0){
    printf("can't create thread \n");
    exit(0);
    }
    //close (sd_tcp);
  }
  
  while(1){
    printf("valor actual buffer = %lf\n",udp_args->buf_udp);
    sleep(1);
  }
  
}

void SendMeasure(int psd, struct sockaddr_in from, double *value) {
  char buf[20];
  int rc;
  struct  hostent *hp, *gethostbyname();

  printf("Serving %s:%d\n", inet_ntoa(from.sin_addr),ntohs(from.sin_port));

  socklen_t socklen = sizeof(from.sin_addr.s_addr);
  const void * addr = (char*)&from.sin_addr.s_addr;
  if ((hp = gethostbyaddr(addr,socklen,AF_INET)) == NULL)
    fprintf(stderr, "Can't find host %s\n", inet_ntoa(from.sin_addr));
  else
    printf("(Name is : %s)\n", hp->h_name);


  /*      get data from  clients and send it back */
  for(;;){
    printf("\n...server is waiting...\n");
    if( (rc=read(psd, buf, sizeof(buf))) < 0)
      perror("receiving stream  message");
    if (rc > 0){
      //printf("rc = %d\n",rc);
      buf[rc]='\0';
      printf("Received: \"%s\"\n", buf);
      printf("From TCP/Client: %s:%d\n", inet_ntoa(from.sin_addr),
          ntohs(from.sin_port));
      printf("(Name is : %s)\n", hp->h_name);
      if(buf[0] = 'n'){
        //printf("entró al if con el valor %lf\n",*value);
        int len = sprintf(buf,"%f\n",*value);
        printf("Contenido del buffer: \"%s\"\n",buf);
        if (send(psd, buf, len, 0) <0 ){
          perror("sending stream message");
        }
        //printf("final del if\n");
      }
      
    }
    else {
      printf("TCP/Client: %s:%d\n", inet_ntoa(from.sin_addr),
          ntohs(from.sin_port));
      printf("(Name is : %s)\n", hp->h_name);
      printf("Disconnected..\n");
      close (psd);
      break;
    }
  }
  close(psd);
}
void reusePort(int s)
{
  int one=1;

  if ( setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *) &one,sizeof(one)) == -1 )
  {
    printf("error in setsockopt,SO_REUSEPORT \n");
    exit(-1);
  }
}

void * udp_server_loop(void * args){
  for(;;){
    //Se desempaquetan argumentos del hilo
    int sd_udp = ((struct udp_thread_args*)args)->sd_udp;
    double buf_udp = ((struct udp_thread_args*)args)->buf_udp;

    //Se recibe el valor medido por udp
    int n;
    if(n = recv(sd_udp, &buf_udp, sizeof(buf_udp), 0) >0){
      printf("number: %lf\n",buf_udp);
    }

    if(n==-1){
      break;
    }

    //Se guarda valor recibido en estructura
    ((struct udp_thread_args*)args)->buf_udp = buf_udp;
  }
  free(((struct udp_thread_args*)args));
  printf("memoria liberada en loop udp\n");
}

void * tcp_server_loop(void * args){

  //Se desempaquetan argumentos del hilo
  int psd = ((struct tcp_thread_args*)args)->psd;
  double *value = ((struct tcp_thread_args*)args)->value;
  struct sockaddr_in from = ((struct tcp_thread_args*)args)->from;

  SendMeasure(psd, from, value);
  free(((struct tcp_thread_args*)args));
  printf("memoria liberada en loop tcp\n");
}
