#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <enet/enet.h>
#include <enet/types.h>

#define MS_VERSION "0.1"
#define MS_MAXSRVS 32
#define MS_TIMEOUT 100

#define NET_CHANS 2
#define NET_CH_MAIN 0
#define NET_CH_UPD  1
#define NET_MAXCLIENTS 64

#define NET_BUFSIZE 65536

#define NET_MSG_ADD  200
#define NET_MSG_RM   201
#define NET_MSG_LIST 202

#define LC_MS_INIT "D2DF master server starting on port %d...\n" 
#define LC_MS_ADD  "\nAdded server in slot #%d:\n%s:%d\n%s\n%s (%d)\n%d/%d plrs\nproto: %d pw?: %d\n"
#define LC_MS_UPD  "\nUpdated server #%d (%s:%d):\n%s\n%s (%d)\n%d/%d plrs\nproto: %d pw?: %d\n"
#define LC_MS_RM   "\nRemoved server #%d (%s:%d) by request.\n"
#define LC_MS_TIME "\nServer #%d (%s:%d) timed out.\n"
#define LC_MS_LIST "\nSent server list to %x:%u.\n"
#define LC_MS_DIE  "\nD2DF master server shutting down..."
#define LC_MS_CONN "\nIncoming connection from %x:%u...\n"

struct ms_server_s {
  enet_uint8 used;
  char s_ip[17];
  char s_name[256];
  char s_map[256];
  enet_uint8  s_pw;
  enet_uint8  s_plrs;
  enet_uint8  s_maxplrs;
  enet_uint8  s_mode;
  enet_uint8  s_protocol;
  enet_uint16 s_port;
  enet_uint32 ttl;
};

typedef struct ms_server_s ms_server;

char ms_game_ver[] = "0.63";
int ms_port = 25660;
int ms_timeout = 100000;

size_t b_read = 0;
size_t b_write = 0;

enet_uint8 b_send[NET_BUFSIZE];

ENetHost  *ms_host = NULL;
ENetPeer  *ms_peers[NET_MAXCLIENTS];
ms_server  ms_srv[MS_MAXSRVS];
enet_uint8 ms_count = 0;

void i_usage() {
  printf("Usage: d2df_master -p port_number [-t timeout_seconds]");
  fflush(stdout);
}
void i_version() {
  printf("Doom 2D Forever master server v%s\n", MS_VERSION);
  fflush(stdout);
}

void d_error(const char *msg, int fatal) {
  if (fatal) {
    fprintf(stderr, "FATAL ERROR: %s\n", msg);
    exit(EXIT_FAILURE);
  } else 
    fprintf(stderr, "ERROR: %s\n", msg);
}
void d_getargs(int argc, char *argv[]) {
  if (argc < 2) {
    i_usage();
    exit(0);
    return;
  }
  
  for (int i = 1; i < argc; ++i) {
    if (!strcmp(argv[i], "-v")) {
      i_version();
      exit(0);
      return;
    } else if (!strcmp(argv[i], "-p")) {
      if (i + 1 >= argc) {
        d_error("Specify a port value!", 1);
        return;
      } else {
        ms_port = atoi(argv[++i]);
	  }
    } else if (!strcmp(argv[i], "-t") & (i + 1 < argc)) {
        ms_timeout = atoi(argv[++i]) * 1000;
    }
  }
      
}

enet_uint8  b_read_uint8(enet_uint8 buf[], size_t *pos) {  
  return buf[(*pos)++];
}
enet_uint16 b_read_uint16(enet_uint8 buf[], size_t *pos) {
  enet_uint16 ret = 0;
  
  ret = *(enet_uint16*)(buf + *pos);
  *pos += sizeof(enet_uint16);
  
  return ret;
}
char* b_read_dstring(enet_uint8 buf[], size_t *pos) {
  char *ret = NULL;

  size_t len = b_read_uint8(buf, pos);
  
  ret = malloc(len + 1);
  
  memmove(ret, (char*)(buf + *pos), len);
  ret[len] = '\0';
  *pos += len;
  
  return ret;
}
void b_write_uint8(enet_uint8 buf[], size_t *pos, enet_uint8 val) {
  buf[(*pos)++] = val;
}
void b_write_uint16(enet_uint8 buf[], size_t *pos, enet_uint16 val) {
  *(enet_uint16*)(buf + *pos) = val;
  *pos += sizeof(enet_uint16);
}
void b_write_dstring(enet_uint8 buf[], size_t *pos, const char* val) {
  enet_uint8 len = strlen(val);
  b_write_uint8(buf, pos, len);
  
  memmove((char*)(buf + *pos), val, len);
  *pos += len;
}
void b_write_server(enet_uint8 buf[], size_t *pos, ms_server s) {
  b_write_dstring(b_send, pos, s.s_ip);
  b_write_uint16 (b_send, pos, s.s_port);
  b_write_dstring(b_send, pos, s.s_name);
  b_write_dstring(b_send, pos, s.s_map);
  b_write_uint8  (b_send, pos, s.s_mode);
  b_write_uint8  (b_send, pos, s.s_plrs);
  b_write_uint8  (b_send, pos, s.s_maxplrs);
  b_write_uint8  (b_send, pos, s.s_protocol);
  b_write_uint8  (b_send, pos, s.s_pw);
}

int main(int argc, char *argv[]) {
  d_getargs(argc, argv);

  if (enet_initialize()) {
    d_error("Could not init ENet!", 1);
    return EXIT_FAILURE;
  }
  
  printf(LC_MS_INIT, ms_port);
  
  for (int i = 0; i < NET_MAXCLIENTS; ++i) 
    ms_peers[i] = NULL;
  for (int i = 0; i < MS_MAXSRVS; ++i) {
    ms_srv[i].used = 0;
    ms_srv[i].s_ip[0] = '\0';
    ms_srv[i].s_name[0] = '\0';
    ms_srv[i].s_map[0] = '\0';
    ms_srv[i].ttl = 0;
  }
    
  ENetAddress addr;
  addr.host = ENET_HOST_ANY;
  addr.port = ms_port;
  
  ms_host = enet_host_create(&addr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
  if (!ms_host) {
    d_error("Could not create host on specified port!", 1);
    return EXIT_FAILURE;
  }
  
  atexit(enet_deinitialize);
  
  ENetEvent event;
  int shutdown = 0;
  enet_uint8 msg = 255;
  
  char ip[17];
  enet_uint16 port = 0;
              
  char *name = NULL;
  char *map = NULL;
  enet_uint8 gm = 0;
  enet_uint16 pl = 0;
  enet_uint16 mpl = 0;
              
  enet_uint8 proto = 0;
  enet_uint8 pw = 0;
  while (!shutdown) {
    while (enet_host_service(ms_host, &event, 1) > 0) {
      switch (event.type) {
        case ENET_EVENT_TYPE_CONNECT:
          printf(LC_MS_CONN, 
                 event.peer->address.host, event.peer->address.port);
          break;
        case ENET_EVENT_TYPE_RECEIVE:
          if (!event.peer) continue;
          b_read = 0;
          msg = b_read_uint8(event.packet->data, &b_read);
          
          switch (msg) {
            case NET_MSG_ADD:
              if (!event.peer) continue;
              
              enet_address_get_host_ip(&(event.peer->address), ip, 17);
              port = b_read_uint16(event.packet->data, &b_read);
              
              name = b_read_dstring(event.packet->data, &b_read);
              map = b_read_dstring(event.packet->data, &b_read);
              gm  = b_read_uint8(event.packet->data, &b_read);
              
              pl = b_read_uint8(event.packet->data, &b_read);
              mpl = b_read_uint8(event.packet->data, &b_read);
              
              proto = b_read_uint8(event.packet->data, &b_read);
              pw = b_read_uint8(event.packet->data, &b_read);
            
              for (int i = 0; i < MS_MAXSRVS; ++i) {
                if (ms_srv[i].used) {
                  if ((strncmp(ip, ms_srv[i].s_ip, 16) == 0) && (ms_srv[i].s_port == port)) {
                    strncpy(ms_srv[i].s_map, map, 255);
                    strncpy(ms_srv[i].s_name, name, 255);
                    ms_srv[i].s_plrs = pl;
                    ms_srv[i].s_maxplrs = mpl;
                    ms_srv[i].s_pw = pw;
                    ms_srv[i].s_mode = gm;
                    
                    ms_srv[i].ttl = ms_timeout;
                    
                    printf(LC_MS_UPD, 
                           i, ip, port, name, map, gm, pl, mpl, proto, pw); 
					break;
                  }
                } else {
                    strncpy(ms_srv[i].s_ip, ip, 16);
                    strncpy(ms_srv[i].s_map, map, 255);
                    strncpy(ms_srv[i].s_name, name, 255);
                    ms_srv[i].s_port = port;
                    ms_srv[i].s_plrs = pl;
                    ms_srv[i].s_maxplrs = mpl;
                    ms_srv[i].s_pw = pw;
                    ms_srv[i].s_mode = gm;
                    ms_srv[i].s_protocol = proto;
                    ms_srv[i].ttl = ms_timeout;
                    
                    ms_srv[i].used = 1;
                    
                    printf(LC_MS_ADD,
                             i, ip, port, name, map, gm, pl, mpl, proto, pw); 
                         
                    ++ms_count;
					break;
                }
              }
              free(name);
              free(map);
              break;
            case NET_MSG_RM:
              enet_address_get_host_ip(&(event.peer->address), ip, 17);
              port = b_read_uint16(event.packet->data, &b_read);
              for (int i = 0; i < MS_MAXSRVS; ++i) {
                if (ms_srv[i].used) {
                  if ((strncmp(ip, ms_srv[i].s_ip, 16) == 0) && (ms_srv[i].s_port == port)) {
                    ms_srv[i].used = 0;
                    printf(LC_MS_RM, i, ip, port);
                    --ms_count;
                  }
                }
              }
              break;
            case NET_MSG_LIST:
              if (!event.peer) continue;
              b_write = 0;
			  b_write_uint8(b_send, &b_write, NET_MSG_LIST);
              b_write_uint8(b_send, &b_write, ms_count);
              for (int i = 0; i < MS_MAXSRVS; ++i) {
                if (ms_srv[i].used)
                  b_write_server(b_send, &b_write, ms_srv[i]);
              }
              
              ENetPacket *p = enet_packet_create(b_send, b_write, ENET_PACKET_FLAG_RELIABLE);
              enet_peer_send(event.peer, NET_CH_MAIN, p);
              enet_host_flush(ms_host);
              
              printf(LC_MS_LIST, 
                     event.peer->address.host, event.peer->address.port);
              break;
          }
          
          enet_packet_destroy(event.packet);
          break;
          
        default:
          break;
      }
    }
    
    for (int i = 0; i < MS_MAXSRVS; ++i) {
      if (ms_srv[i].used) {
        if (--(ms_srv[i].ttl) == 0) {
          ms_srv[i].used = 0;
          printf(LC_MS_TIME, 
                 i, ms_srv[i].s_ip, ms_srv[i].s_port);
          --ms_count;
        }
      }
    }
  }
  
  printf(LC_MS_DIE);
  
  return EXIT_SUCCESS;
}