/* [Petteri] Check if compiling for Win32:      */
#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(_WIN32)
#       define __WIN32__
#endif
/* Follow #ifdef __WIN32__ marks */

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

#ifdef OSX
#include "/usr/include/unistd.h"
#endif

/* [Petteri] Use Winsock for Win32: */
#ifdef __WIN32__
#       define WIN32_LEAN_AND_MEAN
#       include <windows.h>
#       include <winsock.h>
#else
#       include <sys/types.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 <sys/time.h>
#endif

#ifndef __WIN32__
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#define closesocket close
#define ioctlsocket ioctl
#define Sleep(x)        usleep (x * 1000)
#endif

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

#include "doomtype.h"
#include "i_system.h"
#include "d_event.h"
#include "d_net.h"
#include "m_argv.h"
#include "m_alloc.h"
#include "m_swap.h"
#include "doomstat.h"
#include "d_player.h"
#include "g_game.h"
#include "i_net.h"


int         net_socket;
int         localport;
netadr_t    net_from;   // address of who sent the packet
byte        net_message_buffer[MAX_UDP_PACKET];

buf_t       net_message;
int         msg_readcount;
int         msg_badread;

SOCKET sDownloadSocket;
char cDownloadingFileName[1024]; /// @todo
int iDownloadedBytes;
int iDownloadBytesTotal;

//
// UDPsocket
//
SOCKET UDPsocket (void)
{
        SOCKET s;

        // allocate a socket
        s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (s == INVALID_SOCKET)
        I_FatalError ("can't create socket");

        return s;
}

//
// TCPsocket
//
SOCKET TCPsocket (void)
{
        SOCKET s;

        // allocate a socket
        s = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == INVALID_SOCKET)
        I_FatalError ("can't create socket");

        return s;
}

//
// BindToLocalPort
//
void BindToLocalPort (SOCKET s, u_short 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 = htons(port);

        v = bind (s, (sockaddr *)&address, sizeof(address));
        while (v == SOCKET_ERROR) // changed - find new port if taken
			{
			address.sin_port = htons(port++);
			v = bind (s, (sockaddr *)&address, sizeof(address));
			if(port + 1 > 65535)
				{
				I_FatalError ("BindToPort: error");
				return;
				}
			}
}


void CloseNetwork (void)
{
        closesocket(net_socket);
		if(sDownloadSocket)
			closesocket (sDownloadSocket);
#ifdef __WIN32__
        WSACleanup ();
#endif
}


// this is from Quake source code :)

void SockadrToNetadr (struct sockaddr_in *s, netadr_t *a)
{
     *(int *)&a->ip = *(int *)&s->sin_addr;
     a->port = s->sin_port;
}

void NetadrToSockadr (netadr_t *a, struct sockaddr_in *s)
{
     memset (s, 0, sizeof(*s));
     s->sin_family = AF_INET;

     *(int *)&s->sin_addr = *(int *)&a->ip;
     s->sin_port = a->port;
}

char *NET_AdrToString (netadr_t a)
{
     static  char    s[64];

     sprintf (s, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port));

     return s;
}

bool NET_StringToAdr (char *s, netadr_t *a)
{
     struct hostent  *h;
     struct sockaddr_in sadr;
     char    *colon;
     char    copy[128];


     memset (&sadr, 0, sizeof(sadr));
     sadr.sin_family = AF_INET;

     sadr.sin_port = 0;

     strcpy (copy, s);
     // strip off a trailing :port if present
     for (colon = copy ; *colon ; colon++)
          if (*colon == ':')
          {
             *colon = 0;
             sadr.sin_port = htons(atoi(colon+1));
          }

     if (copy[0] >= '0' && copy[0] <= '9')
     {
          *(int *)&sadr.sin_addr = inet_addr(copy);
     }
     else
     {
          if (! (h = gethostbyname(copy)) )
                return 0;
          *(int *)&sadr.sin_addr = *(int *)h->h_addr_list[0];
     }

     SockadrToNetadr (&sadr, a);

     return true;
}

bool NET_CompareAdr (netadr_t a, netadr_t b)
{
    if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
        return true;

        return false;
}



int NET_GetPacket (void)
{
    int                  ret = 0;
    struct sockaddr_in   from;
    int                  fromlen;

    fromlen = sizeof(from);
    ret = recvfrom (net_socket, (char *)net_message_buffer, sizeof(net_message_buffer), 0, (struct sockaddr *)&from, &fromlen);

    if (ret == -1)
    {
#ifdef __WIN32__
        errno = WSAGetLastError();

        if (errno == WSAEWOULDBLOCK)
            return false;

		if (errno == WSAECONNRESET)
            return false;

        if (errno == WSAEMSGSIZE)
                {
             Printf (PRINT_HIGH, "Warning:  Oversize packet from %s\n",
                             NET_AdrToString (net_from));
             return false;
        }

        Printf (PRINT_HIGH, "NET_GetPacket: %s\n", strerror(errno));
                return false;
#else
        if (errno == EWOULDBLOCK)
            return false;
        if (errno == ECONNREFUSED)
            return false;

        Printf (PRINT_HIGH, "NET_GetPacket: %s\n", strerror(errno));
        return false;
#endif
    }
    net_message.cursize = ret;
    SockadrToNetadr (&from, &net_from);

    msg_readcount = 0;
    msg_badread = false;

    return ret;
}


void NET_SendPacket (int length, byte *data, netadr_t to)
{
    int                   ret;
    struct sockaddr_in    addr;

    NetadrToSockadr (&to, &addr);

    ret = sendto (net_socket, (const char*)data, length, 0, (struct sockaddr *)&addr, sizeof(addr) );

    if (ret == -1)
    {
#ifdef __WIN32__
          int err = WSAGetLastError();

          // wouldblock is silent
          if (err == WSAEWOULDBLOCK)
              return;
#else
          if (errno == EWOULDBLOCK)
              return;
          if (errno == ECONNREFUSED)
              return;
          Printf (PRINT_HIGH, "NET_SendPacket: %s\n", strerror(errno));
#endif
    }
}


void NET_GetLocalAddress (void)
{
        char      buff[512];
        struct sockaddr_in  address;
        int           namelen;
        netadr_t  net_local_adr;

        gethostname(buff, 512);
        buff[512-1] = 0;

        NET_StringToAdr (buff, &net_local_adr);

        namelen = sizeof(address);
        if (getsockname (net_socket, (struct sockaddr *)&address, &namelen) == -1)
        Printf (PRINT_HIGH, "NET_Init: getsockname:", strerror(errno));

        net_local_adr.port = address.sin_port;

        Printf(PRINT_HIGH, "IP address %s\n", NET_AdrToString (net_local_adr) );
}


void SZ_Clear (buf_t *buf)
{
    buf->cursize = 0;
    buf->overflowed = false;
}


byte *SZ_GetSpace(buf_t *b, int length)
{
        byte *data;

        if (b->cursize + length >= b->maxsize)
        {
       SZ_Clear (b);
       Printf (PRINT_HIGH, "SZ_GetSpace: overflow\n");
        }

        data = b->data + b->cursize;
        b->cursize += length;

        return data;
}

void SZ_Write (buf_t *b, void *data, int length)
{
    memcpy (SZ_GetSpace(b, length), data, length);
}

void SZ_Write (buf_t *b, byte *data, int startpos, int length)
{
        data += startpos;
    memcpy (SZ_GetSpace(b, length), data, length);
}


void MSG_WriteByte (buf_t *b, int c)
{
    byte    *buf;

    buf = SZ_GetSpace (b, 1);
    buf[0] = c;
}

void MSG_WriteShort (buf_t *b, int c)
{
    byte    *buf;

    buf = SZ_GetSpace (b, 2);
    buf[0] = c&0xff;
    buf[1] = c>>8;
}


void MSG_WriteLong (buf_t *b, int c)
{
    byte    *buf;

    buf = SZ_GetSpace(b, 4);
    buf[0] = c&0xff;
    buf[1] = (c>>8)&0xff;
    buf[2] = (c>>16)&0xff;
    buf[3] = c>>24;
}


void MSG_WriteString (buf_t *b, char *s)
{
    if (!s)
        MSG_WriteByte(b, 0);
    else
        {
        SZ_Write (b, s, strlen(s));
        MSG_WriteByte(b, 0);
        }
}



int MSG_ReadByte (void)
{
    int     c;

    if (msg_readcount+1 > net_message.cursize)
    {
        msg_badread = true;
        return -1;
    }

    c = (unsigned char)net_message.data[msg_readcount];
    msg_readcount++;

    return c;
}

int MSG_ReadShort (void)
{
    int     c;

    if (msg_readcount+2 > net_message.cursize)
    {
        msg_badread = true;
        return -1;
    }

    c = (short)(net_message.data[msg_readcount]
    + (net_message.data[msg_readcount+1]<<8));

    msg_readcount += 2;

    return c;
}

int MSG_ReadLong (void)
{
    int c;

        if (msg_readcount+4 > net_message.cursize)
    {
        msg_badread = true;
        return -1;
    }

    c = net_message.data[msg_readcount]
     + (net_message.data[msg_readcount+1]<<8)
     + (net_message.data[msg_readcount+2]<<16)
     + (net_message.data[msg_readcount+3]<<24);

    msg_readcount += 4;

    return c;
}

int MSG_ReadFirstLong (void)
{
    int c; 

	if (4 > net_message.cursize)
    {
        msg_badread = true;
        return -1;
    }

    c = net_message.data[0]
     + (net_message.data[1]<<8)
     + (net_message.data[2]<<16)
     + (net_message.data[3]<<24);

    return c;
}

char *MSG_ReadString (void)
{
    static char     string[2048];
    signed char     c;
    unsigned int    l;

    l = 0;
    do
    {
       c = (signed char)MSG_ReadByte();
       if (c == 0)
           break;
       string[l] = c;
       l++;
        } while (l < sizeof(string)-1);

    string[l] = 0;

    return string;
}


//
// InitNetCommon
//
void InitNetCommon(void)
{
   unsigned long _true = true;

#ifdef __WIN32__
   WSADATA   wsad;
   WSAStartup( 0x0101, &wsad );
#endif

   net_socket = UDPsocket ();
   BindToLocalPort (net_socket, localport);
   if (ioctlsocket (net_socket, FIONBIO, &_true) == -1)
       I_FatalError ("UDPsocket: ioctl FIONBIO:", strerror(errno));

   net_message.data = net_message_buffer;
   net_message.maxsize = sizeof(net_message_buffer);
   SZ_Clear(&net_message);
}


void I_SetPort(netadr_t &addr, int port)
{
   addr.port = htons(port);
}

#ifdef __WIN32__

void I_DoSelect (void)
{
    struct timeval   timeout;
    fd_set           fdset;

    FD_ZERO(&fdset);
    FD_SET(net_socket, &fdset);
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    if (select (net_socket+1, &fdset, NULL, NULL, &timeout) == -1)
        return;
}

#else

int stdin_ready;
int do_stdin;

void I_DoSelect (void)
{
    struct timeval   timeout;
    fd_set           fdset;

    FD_ZERO(&fdset);
    if (do_stdin)
        FD_SET(0, &fdset);
    FD_SET(net_socket, &fdset);
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    if (select (net_socket+1, &fdset, NULL, NULL, &timeout) == -1)
        return;

    stdin_ready = FD_ISSET(0, &fdset);
}

#endif

bool NET_InitDownload (char *cServer, int iPort, char *cPath, char *cFile)
{
	if(sDownloadSocket)
		closesocket(sDownloadSocket);

	sDownloadSocket = TCPsocket();

	strcpy(cDownloadingFileName, cFile);

	netadr_t a;
	struct sockaddr_in addr;

	NET_StringToAdr(cServer, &a);
	I_SetPort(a, iPort);
	NetadrToSockadr(&a, &addr);

	if(SOCKET_ERROR == connect(sDownloadSocket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)))
	{
		closesocket(sDownloadSocket);
		sDownloadSocket = 0;
		return false;
	}

	char *szRequest = new char[strlen(cFile) + 250];

	sprintf(szRequest, "GET %s%s HTTP/1.0\nHost: %s\n\n", cPath, cFile, cServer);

	if(SOCKET_ERROR == send(sDownloadSocket, szRequest, strlen(szRequest), 0))
	{
		delete[] szRequest;
		closesocket(sDownloadSocket);
		sDownloadSocket = 0;

		return false;
	}

	struct timeval   timeout;
	fd_set           fdset;

	FD_ZERO(&fdset);
	FD_SET(net_socket, &fdset);
	timeout.tv_sec = 1;
	timeout.tv_usec = 0;
	if (select (net_socket+1, &fdset, NULL, NULL, &timeout) == -1)
	{
		delete[] szRequest;
		closesocket(sDownloadSocket);
		sDownloadSocket = 0;

		return false;
	}

	delete[] szRequest;

	iDownloadedBytes = 0;
	iDownloadBytesTotal = 0;

	return true;
}

int NET_GetDownloadTotal (void)
{
	return iDownloadBytesTotal;
}

int NET_GetDownloadProgress (void) /// @todo upgrade
{
	if(!sDownloadSocket)
		return -1;

	static FILE *fp = 0;
	static char cBuffer[2048];
	static int iHeaderRead = 0;
	static bool bGotHeader = false;
	int iRet = 0;

	if(!fp)
	{
		fp = fopen(cDownloadingFileName, "wb");

		iHeaderRead = 0;
		bGotHeader = false;

		if(!fp)
		{
			closesocket(sDownloadSocket);
			sDownloadSocket = 0;
			return -1;
		}
	}

	if(!bGotHeader)
	{
		// First get through the HTTP header
		iRet = recv(sDownloadSocket, cBuffer, sizeof(cBuffer) - 1, 0);

		if(iRet == SOCKET_ERROR || !iRet)
		{
#ifdef __WIN32
			if(WSAGetLastError() != WSAEWOULDBLOCK)
			{
				fclose(fp);
				closesocket(sDownloadSocket);
				sDownloadSocket = 0;
			}
#endif
			return -2;
		}

		iHeaderRead += iRet;
		cBuffer[iHeaderRead] = 0;

		char *eol = strstr(cBuffer, "\n");
		char *c404 = strstr(cBuffer, "404");
		if(c404 && c404 < eol)
		{
			fclose(fp);
			closesocket(sDownloadSocket);
			sDownloadSocket = 0;
			return -3;
		}

		// Find context length and \n\n
		char *cLen = strstr(cBuffer, "Content-Length: ");
		if(!cLen)
		{
			return 0;
		}
		cLen += strlen("Content-Length: ");
		iDownloadBytesTotal = atoi(cLen);

		char *cFile = strstr(cBuffer, "\n\n");
		if(cFile)
		{
			if(cFile)cFile += 2;
		}
		if(!cFile)
		{
			cFile = strstr(cBuffer, "\r\n\r\n");
			if(cFile)cFile += 4;
		}
		if(!cFile)
			return 0;

		iRet = iHeaderRead - (cFile - cBuffer);
		memcpy(cBuffer, cFile, iRet);

		bGotHeader = true;
	}
	
	if(bGotHeader)
	{
		// Now the rest of the file
		iRet = recv(sDownloadSocket, cBuffer, sizeof(cBuffer), 0);

		if(iRet == SOCKET_ERROR || !iRet)
		{
#ifdef _WIN32
			if(WSAGetLastError() != WSAEWOULDBLOCK)
			{
				fclose(fp);
				closesocket(sDownloadSocket);
				sDownloadSocket = 0;
			}
#endif
			return -2;
		}
	}

	int iFRet = fwrite(cBuffer, 1, iRet, fp);

	iDownloadedBytes += iFRet;

	return iDownloadedBytes;
}