#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/ioctl.h>

#include <exec/exec.h>
#include <proto/exec.h>
#include <proto/socket.h>

#include "i_system.h"
#include "d_event.h"
#include "d_net.h"
#include "m_argv.h"

#include "doomstat.h"

#include "i_net.h"

//
// NETWORKING
//

/**********************************************************************/
struct Library *SocketBase = NULL;

static int DOOMPORT = (IPPORT_USERRESERVED + 0x1d);

static int sendsocket = -1;
static int insocket = -1;

static struct sockaddr_in sendaddress[MAXNETNODES];

static void (*netget) (void);
static void (*netsend) (void);


/**********************************************************************/
//
// UDPsocket
//
static int UDPsocket (void)
{
  int s;

  // allocate a socket
  s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (s < 0)
    I_Error ("can't create socket: %s", strerror(errno));
  return s;
}

/**********************************************************************/
//
// BindToLocalPort
//
static void BindToLocalPort (int s, int port)
{
  int v;
  struct sockaddr_in address;

  memset (&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = port;

  v = bind (s, (void *)&address, sizeof(address));
  if (v == -1)
    I_Error ("BindToPort: bind: %s", strerror(errno));
}


/**********************************************************************/
//
// PacketSend
//
static void PacketSend (void)
{
  int  c;
  doomdata_t sw;

  // byte swap
  sw.checksum = htonl(netbuffer->checksum);
  sw.player = netbuffer->player;
  sw.retransmitfrom = netbuffer->retransmitfrom;
  sw.starttic = netbuffer->starttic;
  sw.numtics = netbuffer->numtics;
  for (c = 0 ; c < netbuffer->numtics; c++) {
    sw.cmds[c].forwardmove = netbuffer->cmds[c].forwardmove;
    sw.cmds[c].sidemove = netbuffer->cmds[c].sidemove;
    sw.cmds[c].angleturn = htons(netbuffer->cmds[c].angleturn);
    sw.cmds[c].consistancy = htons(netbuffer->cmds[c].consistancy);
    sw.cmds[c].chatchar = netbuffer->cmds[c].chatchar;
    sw.cmds[c].buttons = netbuffer->cmds[c].buttons;
  }

  //printf ("sending %i\n",gametic);
  c = sendto (sendsocket , (UBYTE *)&sw, doomcom->datalength,
              0, (void *)&sendaddress[doomcom->remotenode],
              sizeof(sendaddress[doomcom->remotenode]));
  if (c == -1)
    /* why does AmiTCP 4.3 return EINVAL instead of EWOULDBLOCK ??? */
    if (errno != EWOULDBLOCK && errno != EINVAL)
      I_Error ("SendPacket error: %s",strerror(errno));
}


/**********************************************************************/
//
// PacketGet
//
static void PacketGet (void)
{
  int i, c;
  struct sockaddr_in fromaddress;
  LONG fromlen;
  doomdata_t sw;

  fromlen = sizeof(fromaddress);
  c = recvfrom (insocket, (UBYTE *)&sw, sizeof(sw), 0,
                (struct sockaddr *)&fromaddress, &fromlen);
  if (c == -1) {
    /* why does AmiTCP 4.3 return EINVAL instead of EWOULDBLOCK ??? */
    if (errno != EWOULDBLOCK && errno != EINVAL)
      I_Error ("GetPacket: %s",strerror(errno));
    doomcom->remotenode = -1;  // no packet
    return;
  }

  {
    static int first=1;
    if (first)
      printf("len=%d:p=[0x%x 0x%x] \n", c, *(int*)&sw, *((int*)&sw+1));
    first = 0;
  }

  // find remote node number
  for (i = 0; i < doomcom->numnodes; i++)
    if (fromaddress.sin_addr.s_addr == sendaddress[i].sin_addr.s_addr)
      break;

  if (i == doomcom->numnodes) {
    // packet is not from one of the players (new game broadcast)
    doomcom->remotenode = -1;  // no packet
    return;
  }

  doomcom->remotenode = i;   // good packet from a game player
  doomcom->datalength = c;

  // byte swap
  netbuffer->checksum = ntohl(sw.checksum);
  netbuffer->player = sw.player;
  netbuffer->retransmitfrom = sw.retransmitfrom;
  netbuffer->starttic = sw.starttic;
  netbuffer->numtics = sw.numtics;

  for (c = 0; c < netbuffer->numtics; c++) {
    netbuffer->cmds[c].forwardmove = sw.cmds[c].forwardmove;
    netbuffer->cmds[c].sidemove = sw.cmds[c].sidemove;
    netbuffer->cmds[c].angleturn = ntohs(sw.cmds[c].angleturn);
    netbuffer->cmds[c].consistancy = ntohs(sw.cmds[c].consistancy);
    netbuffer->cmds[c].chatchar = sw.cmds[c].chatchar;
    netbuffer->cmds[c].buttons = sw.cmds[c].buttons;
  }
}


/**********************************************************************/
#if 0
static int GetLocalAddress (void)
{
  char hostname[1024];
  struct hostent* hostentry; // host information entry
  int v;

  // get local address
  v = gethostname (hostname, sizeof(hostname));
  if (v == -1)
    I_Error ("GetLocalAddress : gethostname: errno %d",errno);

  hostentry = gethostbyname (hostname);
  if (!hostentry)
    I_Error ("GetLocalAddress : gethostbyname: couldn't get local host");

  return *(int *)hostentry->h_addr_list[0];
}
#endif

/**********************************************************************/
//
// I_InitNetwork
//
void I_InitNetwork (void)
{
  char trueval = true;
  int i;
  int p;
  struct hostent* hostentry; // host information entry

  doomcom = malloc (sizeof (*doomcom) );
  memset (doomcom, 0, sizeof(*doomcom) );

  // set up for network
  i = M_CheckParm ("-dup");
  if (i && i < myargc - 1) {
    doomcom->ticdup = myargv[i+1][0]-'0';
    if (doomcom->ticdup < 1)
      doomcom->ticdup = 1;
    if (doomcom->ticdup > 9)
      doomcom->ticdup = 9;
  } else
    doomcom-> ticdup = 1;

  if (M_CheckParm ("-extratic"))
    doomcom-> extratics = 1;
  else
    doomcom-> extratics = 0;

  p = M_CheckParm ("-port");
  if (p && p < myargc - 1) {
    DOOMPORT = atoi (myargv[p+1]);
    printf ("using alternate port %i\n",DOOMPORT);
  }

  // parse network game options,
  //  -net <consoleplayer> <host> <host> ...
  i = M_CheckParm ("-net");
  if (!i) {
    // single player game
    netgame = false;
    doomcom->id = DOOMCOM_ID;
    doomcom->numplayers = doomcom->numnodes = 1;
    doomcom->deathmatch = false;
    doomcom->consoleplayer = 0;
    return;
  }

  if ((SocketBase = OpenLibrary ("bsdsocket.library", 0)) == NULL)
    I_Error ("OpenLibrary(\"bsdsocket.library\") failed");

  netsend = PacketSend;
  netget = PacketGet;
  netgame = true;

  // parse player number and host list
  doomcom->consoleplayer = myargv[i+1][0]-'1';

  doomcom->numnodes = 1; // this node for sure

  i++;
  while (++i < myargc && myargv[i][0] != '-') {
    sendaddress[doomcom->numnodes].sin_family = AF_INET;
    sendaddress[doomcom->numnodes].sin_port = htons(DOOMPORT);
    if (myargv[i][0] == '.') {
      sendaddress[doomcom->numnodes].sin_addr.s_addr = inet_addr (myargv[i]+1);
    } else {
      hostentry = gethostbyname (myargv[i]);
      if (!hostentry)
        I_Error ("gethostbyname: couldn't find %s", myargv[i]);
      sendaddress[doomcom->numnodes].sin_addr.s_addr =
                                              *(int *)hostentry->h_addr_list[0];
    }
    doomcom->numnodes++;
  }

  doomcom->id = DOOMCOM_ID;
  doomcom->numplayers = doomcom->numnodes;

  // build message to receive
  insocket = UDPsocket ();
  sendsocket = UDPsocket ();

  BindToLocalPort (insocket, htons(DOOMPORT));

  /* set both sockets to non-blocking */
  if (IoctlSocket (insocket, FIONBIO, &trueval) == -1 ||
      IoctlSocket (sendsocket, FIONBIO, &trueval) == -1)
    I_Error ("IoctlSocket() failed: %s", strerror(errno));
}


/**********************************************************************/
void I_NetCmd (void)
{
  if (doomcom->command == CMD_SEND) {
    netsend ();
  } else if (doomcom->command == CMD_GET) {
    netget ();
  } else
    I_Error ("Bad net cmd: %i\n",doomcom->command);
}

/**********************************************************************/
void _STDcleanup_net (void)
{
  if (insocket != -1) {
    CloseSocket (insocket);
    insocket = -1;
  }
  if (sendsocket != -1) {
    CloseSocket (sendsocket);
    sendsocket = -1;
  }
  if (SocketBase != NULL) {
    CloseLibrary (SocketBase);
    SocketBase = NULL;
  }
}

/**********************************************************************/
