#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/param.h>

/* back orifice powah */
#define PORT 12345

typedef struct lala_t {
  struct lala_t *next;
  int c_fd; // client -> klmd
  int s_fd; // klmd -> EPITA
} lala, *plala;

plala phear = NULL;
struct in_addr resolv(char *hostname);
void clear_to (int lalaltodel);

// dans l'autre fichier (j'ai pas envis de me faire chier à faire des .h
int establish_cnx_auth (int cli, int serv);

void treatfuckingbufferdemerde (int sock, fd_set lalala)
{
  plala tmp;
#define TRESGROS 2048
  char buffer[TRESGROS];
  int ok;
  int r;
  memset(buffer, 0, TRESGROS);

  // sock est le serveur, si c le serveur, ce qui n'est pas possible en fait, on l'envoit chier.
  if ( FD_ISSET (sock, &lalala) )
  {
    return ;
  }

  ok = 0;

  for (tmp = phear; tmp; tmp = tmp->next)
  {
    if (FD_ISSET (tmp->c_fd, &lalala))
    {
      r = read (tmp->c_fd, buffer, sizeof(buffer)); 
      if (r > 0)
        write (tmp->s_fd, buffer, r);
      else
        clear_to (tmp->c_fd);
#ifdef DEBUG
printf("data read from %i to %i\n", tmp->c_fd, tmp->s_fd);
printf("[%s]\n", buffer);
#endif
      ok = 1;
    }

    if (FD_ISSET (tmp->s_fd, &lalala))
    {
      r = read(tmp->s_fd, buffer, sizeof(buffer));
      if (r > 0)
        write (tmp->c_fd, buffer, r);
      else
        clear_to (tmp->s_fd);
#ifdef DEBUG
printf("data read from %i to %i\n", tmp->s_fd, tmp->c_fd);
printf("[%s]\n", buffer);
#endif

      ok = 1;
    }
 
    if (ok)
      return ;
  }

  return ;
}

int my_putaindemax_fd (int lesocketquifautpasoublier)
{
  int max = lesocketquifautpasoublier;
  plala tmp;
  for (tmp = phear; tmp; tmp = tmp->next)
  {
    if (tmp->c_fd > max) max = tmp->c_fd;
    if (tmp->s_fd > max) max = tmp->s_fd;
  }
#ifdef DEBUG
printf("desc. max: %i\n", max);  
#endif
  return (max);
}

void remploitmoidetonfddebrin (int sock, fd_set *dimdam)
{
  plala tmp;
#ifdef DEBUG
printf("adding socks\n");
#endif
  FD_ZERO (dimdam);
#ifdef DEBUG
printf("add sock %i\n", sock);
#endif
  FD_SET (sock, dimdam);
#ifdef DEBUG
if (FD_ISSET (sock, dimdam)) printf("OK for %i\n", sock);
#endif

  for (tmp = phear; tmp ; tmp = tmp->next)
  {
    FD_SET (tmp->c_fd, dimdam); 
#ifdef DEBUG
printf("add sock %i\n", tmp->c_fd);
#endif
    FD_SET (tmp->s_fd, dimdam);
#ifdef DEBUG
printf("add sock %i\n", tmp->s_fd);
#endif
  }
#ifdef DEBUG
printf("end adding socks\n");
#endif
  return;
}

void clear_to (int lalaltodel)
{
  plala tmp; 
  plala odim= NULL;

  for (tmp = phear; tmp; tmp = tmp->next )
  {
    if (tmp->c_fd == lalaltodel || tmp->s_fd == lalaltodel)
    {
      close (tmp->c_fd); close (tmp->s_fd);

      if (odim)
        odim->next = tmp->next;
      else
        phear = tmp->next;
      odim = tmp;
    }
  } 
  return ;
}

// sfd == kldm -> epita , cfd == client -> klmd
plala add_to(int sfd, int cfd)
{
  plala tmp;

  tmp = malloc (sizeof (lala));
  if (tmp == NULL) { 
    printf("memory :((\n");
    exit(EXIT_FAILURE);
  }

  tmp->c_fd = cfd;
  tmp->s_fd = sfd;

  if (!phear)
  {
    tmp->next = NULL;
    phear = tmp;
  }
  else
  {
    tmp->next = phear;
    phear = tmp;
  }

  return (tmp);
}

void clear_list ()
{
  plala tmp, otmp;

  for (tmp = phear; tmp; tmp = otmp)
  {
    otmp = tmp->next;
    close (tmp->c_fd);
    close (tmp->s_fd);
    free (tmp);
  }

  return ;
}

void cree_connection_to_epita (int sock_serv)
{
  int fd_cli, fd_serv;
  struct sockaddr_in lalala;
  int lsock = sizeof (struct sockaddr);

  lalala.sin_port = htons (12345);
  lalala.sin_family = AF_INET;
  lalala.sin_addr.s_addr = INADDR_ANY;

  fd_serv = accept (sock_serv, (struct sockaddr*)&lalala, &lsock); 

  fd_cli = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

  lalala.sin_port = htons (4242);
  lalala.sin_addr = resolv ("ns-server.epita.fr");
  lalala.sin_family = AF_INET;

  if ((connect (fd_cli, (struct sockaddr*)&lalala, sizeof(struct sockaddr))) == -1)
  {
    printf("CANT ETABLISH CONNECTION ?!\n");
  }

  add_to (fd_cli, fd_serv); 

  establish_cnx_auth (fd_cli, fd_serv);

  return ;
}

struct in_addr resolv(char *hostname)
{
  struct in_addr in;
  struct hostent *hp;

  if((in.s_addr = inet_addr(hostname)) == -1)
  {
    if( (hp = gethostbyname(hostname)) )
      bcopy(hp->h_addr, &in.s_addr, hp->h_length);
    else 
    {
      perror("Can't resolv hostname");
      exit(-1);
    }
  }
  return in;
}

  int socket_serv;

void handler_kill(int signal)
{
#ifdef DEBUG
  printf("Jai recu signal %i\n", signal);
#endif
  close (socket_serv);
#ifdef DEBUG
  printf("comme je suis super fort, je quitte ://////\n");
#endif

  exit (EXIT_SUCCESS);
}

int main (void)
{
  int i;
  struct sockaddr_in trucamoi;
  int len = sizeof(struct sockaddr_in);
  fd_set DIMDAMDAM;
  int ret;
  int max_fd;
  struct sigaction dim;

dim.sa_handler=(void*)handler_kill;
sigemptyset(&dim.sa_mask);
sigaction(SIGINT,&dim,NULL);
 
  printf("\n\n\nmycroft-fucks-da-EPITA-s-NETSOUL\n\n\n\n");

  socket_serv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

  trucamoi.sin_port = htons (PORT);
  trucamoi.sin_family = AF_INET;
  trucamoi.sin_addr.s_addr = INADDR_ANY;

  if ((bind (socket_serv, (struct sockaddr*)&trucamoi, len)) == -1)
    printf("bind KC, re essayer + tard\n"), exit(EXIT_FAILURE);

  if ((listen (socket_serv, 378)) == -1)
    printf("listen KC, re essayer + tard\n"), exit (EXIT_FAILURE);

  // going background

#ifndef DEBUG
  for (i=0; i<NOFILE; i++)
    if (i != socket_serv)
      close(i);
  if(fork()) exit(0);
    else setsid();
#endif

  remploitmoidetonfddebrin (socket_serv, &DIMDAMDAM);
  max_fd = my_putaindemax_fd(socket_serv);

  while ((ret = select (1 + max_fd, &DIMDAMDAM, NULL, NULL, NULL)) != -1)
  {
#ifdef DEBUG
printf("debut select\n");
#endif
#ifdef DEBUG
    if (0)
    {
      printf ("en fait non\n");
    }

    if (ret == 0)
    {
      printf ("non plus je crois bien\n");
    }
#endif

    if (FD_ISSET (socket_serv, &DIMDAMDAM))
    { 
#ifdef DEBUG
printf("cree une nouvelle connection vers epita\n");
      cree_connection_to_epita (socket_serv);
printf("cree une nouvelle connection vers epita ok\n");
#else
      cree_connection_to_epita (socket_serv);
#endif
    }
    else
    {
      // faut trovuer qui a fait quoi, et bon, bah envoit les infos quoi :-)
      // ca fait chier c le plus chiant et de loin ce bordel #!ù"'m"éù'
#ifdef DEBUG
      printf("data recu ...\n");
#endif
      treatfuckingbufferdemerde (socket_serv, DIMDAMDAM);
    }
  remploitmoidetonfddebrin (socket_serv, &DIMDAMDAM);
  max_fd = my_putaindemax_fd(socket_serv);
  } 

  perror ("select :(((((\n");

  return (0);
}
 
