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

struct thrarg {
  int sock;
  int index;
  int port;
  char *ip;
};

int rvSock;

int threads;
pthread_t **thr;
pthread_mutex_t thread_mtx = PTHREAD_MUTEX_INITIALIZER;

int best[3];
int best_sum;
char best_ip[32];
int best_port;
pthread_mutex_t best_mtx = PTHREAD_MUTEX_INITIALIZER;

void handler(int sig) {
  if (close (rvSock) < 0) {
    perror("\nCould not close socket\n");
    return;
  }
}

int findFreeThread() {
  int i;

  pthread_mutex_lock(&thread_mtx);
  for(i=0; i<threads; i++) {
    if(thr[i] == NULL) {
      pthread_mutex_unlock(&thread_mtx);
      return i;
    }
  }
  pthread_mutex_unlock(&thread_mtx);

  return -1;
}

void serve(struct thrarg* arg) {
  int k;
  int toReceive[3];

  k = recv(arg->sock, &toReceive, sizeof(toReceive), 0);
  if(k > 0) {
    printf("!!! SRVER: from: %d :: %s\n", arg->port, arg->ip);
    printf("!!! SERVER: size: %d\n", k);
    printf("!!! SERVER: Numbers: (%d %d %d)\n", toReceive[0], toReceive[1], toReceive[2]);
  }

  pthread_mutex_lock(&best_mtx);

  if(best_sum < toReceive[0] + toReceive[1] + toReceive[2]) {
    best_sum = toReceive[0] + toReceive[1] + toReceive[2];
    best[0] = toReceive[0];
    best[1] = toReceive[1];
    best[2] = toReceive[2];
    strcpy(best_ip, arg->ip);
    best_port = arg->port;
  }

  send(arg->sock, &best, sizeof(best), 0);
  send(arg->sock, &best_port, sizeof(best_port), 0);
  int i = 0;
  for(i = 0; i < sizeof(best_ip); ) {
    i += send(arg->sock, best_ip+i, sizeof(best_ip+i), 0);
    //printf("%d :: %d %d\n", i, sizeof(best_ip+i), sizeof(best_ip));
  }
  pthread_mutex_unlock(&best_mtx);


  pthread_mutex_lock(&thread_mtx);
  free(thr[arg->index]);
  thr[arg->index] = NULL;
  close(arg->sock);
  free(arg);
  pthread_mutex_unlock(&thread_mtx);
}

int main(int argc, char *argv[]) {
  int sock;
  int port;
  unsigned int len;
  int i;
  int k;
  struct sockaddr_in addr;

  if (argc < 3) {
    printf ("\nUsage: ./server threads port\n");
    return 0;
  }
  else {
    sscanf(argv[1], "%d", &threads);
    sscanf(argv[2], "%d", &port);
  }

  printf("SERVER START\n");
  printf("Threads: %d\n", threads);
  printf("Port: %d\n", port);

  thr = (pthread_t **)malloc(threads * sizeof(pthread_t *));
  for(i=0; i<threads; i++)
    thr[i] = NULL;


  rvSock = socket(AF_INET, SOCK_STREAM, 0);
  if (rvSock < 0) {
    perror("\nCould not make a socket\n");
    return 1;
  }

  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons (port);
  addr.sin_family = AF_INET;

  if (bind (rvSock, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
    perror("\nCould not bind socket\n");
    return 1;
  }

  if (listen(rvSock, 5) < 0) {
    perror ("\nCould not listen\n");
    return 1;
  }

  signal(SIGINT, handler);

  while(1) {
    sock = accept (rvSock, (struct sockaddr *)&addr,  &len);

    if(sock < 0) {
      break;
    }

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

    k = findFreeThread();
    if(k < 0) {
      close(sock);
      continue;
    }

    struct thrarg* ta = (struct thrarg*)malloc(sizeof(struct thrarg));
    ta->sock = sock;
    ta->index = k;
    ta->port = ntohs(addr.sin_port);
    ta->ip = inet_ntoa(addr.sin_addr);
    thr[i] = (pthread_t*)malloc(sizeof(pthread_t));

    pthread_create(thr[i], NULL, (void *(*)(void*))serve, ta);
  }

  for(i=0; i<threads; i++) {
    pthread_t* t = thr[i];
    if(t != NULL) {
      pthread_join(*t, NULL);
    }
  }

  free(thr);

  return 0;
}
