#ifndef MS_WIN
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#else
#include <winsock.h>
#endif
#include <stdio.h>
#include <stdlib.h>

#include "defs.h"
#include "server.h"
#include "graph.h"
#include "MsgQ.h"
#include "net.h"

struct PlayerInfo
{
  char *name;
  int fd;
  int route;
  int id;
};

static PlayerInfo *playerTable;
static int connSock;
static int leftToConnect;

const int ConnectPort = 7123;

// clients use this socket to communicate with the server
int sock;

// q is NULL
static void NewConnection(MsgQ *q, int fd, int info)
{
  static int currPlayer = 1;
  struct sockaddr_in sock_in;
  int tmp, new_sock;

  tmp = sizeof(sock_in);
  new_sock = accept(fd, (struct sockaddr *)&sock_in, &tmp);
  if (new_sock < 0) return;
  if (currPlayer >= numPlayers) {
#ifdef MS_WIN
    closesocket(new_sock);
#else
    close(new_sock);
#endif
    return;
  }

  int set = 1;
  setsockopt(new_sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&set, sizeof(set));

  playerTable[currPlayer].fd = new_sock;
  playerTable[currPlayer].route = -1;
  playerTable[currPlayer].id = currPlayer;
  screen->AddReadNotify(new_sock, ServerGotData, currPlayer);
  ++currPlayer;
}

void InitServer()
{
  playerTable = new PlayerInfo[numPlayers];
  playerTable[0].name = myName;
  playerTable[0].fd = -1;
  playerTable[0].route = -1;
  playerTable[0].id = 0;
  leftToConnect = numPlayers-1;

  struct sockaddr_in sock_in;
  int val;

  sock_in.sin_addr.s_addr = htonl(INADDR_ANY);
  sock_in.sin_port = htons(ConnectPort);
  sock_in.sin_family = AF_INET;

  if ((connSock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    fprintf(stderr, "socket failed\n");
    exit(1);
  }
  val = 1;
  if (setsockopt(connSock, SOL_SOCKET, SO_REUSEADDR, (char *)&val,
		 sizeof(val)) < 0) {
    fprintf(stderr, "setsockopt failed\n");
    exit(1);
  }
  if (bind(connSock, (struct sockaddr *)&sock_in, sizeof(sock_in)) < 0) {
    fprintf(stderr, "bind failed\n");
    exit(1);
  }
  if (listen(connSock, 5) < 0) {
    fprintf(stderr, "listen failed\n");
    exit(1);
  }

  screen->AddReadNotify(connSock, NewConnection, 0, 0);
}

void ServerGotData(MsgQ *q, int fd, int player)
{
  static int waitingOnSynch = 0;
  long cmd, arg;
  int i;
  if (!q->IsCmd(cmd, arg)) {
    if (playerTable[player].route == -1) { // send to everyone
      MsgQ *copy = NULL;
      int sendsLeft = numPlayers-1;
      if (player != 0) {
	--sendsLeft;
	copy = q->Copy();
      }
      if (sendsLeft == 0)
	delete q;
      for (i = 1; i < numPlayers; ++i)
	if (i != player) {
	  --sendsLeft;
	  if (sendsLeft > 1)
	    NonDestructiveSendQ(playerTable[i].fd, q);
	  else
	    SendQ(playerTable[i].fd, q);
	}
      if (player != 0)
	HandleMessage(copy, -1, 0);
      return;
    }
    // send to specific player
    if (playerTable[player].route == 0)
      HandleMessage(q, -1, 0);
    else {
      for (int i = 0;;++i)
	if (playerTable[i].id == playerTable[player].route) {
	  SendQ(playerTable[i].fd, q);
	  break;
	}
    }
    return;
  }
  // the message is a command
  MsgQ *send;
  switch (cmd) {
  case GET_PLAYER_INFO:
    delete q;
    send = new MsgQ;
    *send << "player_info";
    *send << numPlayers;
    for (i = 0; i < numPlayers; ++i)
      *send << playerTable[i].name;
    if (player == 0)
      HandleMessage(send, -1, 0);
    else
      SendQ(playerTable[player].fd, send);
    return;
  case CHANGE_ROUTE:
    delete q;
    playerTable[player].route = arg;
    return;
  case SYNCH:
    delete q;
    if (waitingOnSynch == 0) // initiated synchronise
      waitingOnSynch = numPlayers-1;
    else {
      --waitingOnSynch;
      if (waitingOnSynch == 0) {
	send = new MsgQ;
	*send << "synch";
	for (i = 1; i < numPlayers; ++i)
	  if (i == numPlayers-1)
	    SendQ(playerTable[i].fd, send);
	  else
	    NonDestructiveSendQ(playerTable[i].fd, send);
	send = new MsgQ;
	*send << "synch";
	HandleMessage(send, -1, 0);
      }
    }
    return;
  case CONNECT:
    *q >> playerTable[player].name;
    delete q;
    if (--leftToConnect <= 0) {
      send = new MsgQ;
      *send << "send_map";
      HandleMessage(send, -1, 0);
    }
    return;
  case RECONNECT:
    *q >> playerTable[player].name;
    delete q;
    playerTable[player].id = arg;
    --leftToConnect;
    return;
  }
  delete q;
  return;
}

// client calls this to connect to us
void ConnectServer(char *server)
{
  struct sockaddr_in addr;
  struct hostent FAR *host;

  addr.sin_port = htons(ConnectPort);
  host = gethostbyname(server);
  if (host == NULL) {
    exit(1);
  }
  addr.sin_addr = *(struct in_addr *)host->h_addr;

  sock = socket(PF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
    exit(1);
  }
  addr.sin_family = AF_INET;
  if (connect(sock, (sockaddr *)&addr, sizeof(addr)) < 0) {
    exit(1);
  }
}
