/****************************************************************************
 * Network Operations
 * for USB Loader GX
 *
 * HTTP operations
 * Written by dhewg/bushing modified by dimok
 ****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ogcsys.h>
#include <ogc/machine/processor.h>
#include <fcntl.h>

#include "prompts/PromptWindows.h"
#include "language/gettext.h"
#include "settings/CSettings.h"
#include "utils/timer.h"
#include "networkops.h"
#include "main.h"
#include "http.h"
#include "svnrev.h"
#include "gecko.h"
#include "update.h"

#define PORT			4299

/*** Incomming filesize ***/
u32 infilesize = 0;
u32 uncfilesize = 0;

bool updateavailable = false;
s32 connection;
static s32 socket;
static bool updatechecked = false;
static bool networkinitialized = false;
static bool checkincomming = false;
static bool waitforanswer = false;
static char IP[16];
char incommingIP[50];
char wiiloadVersion[2];

static lwp_t networkthread = LWP_THREAD_NULL;
static bool networkHalt = true;

/****************************************************************************
 * Initialize_Network
 ***************************************************************************/
void Initialize_Network(void)
{

	if (networkinitialized) return;

	s32 result;

	result = if_config(IP, NULL, NULL, true);

	if (result < 0)
	{
		networkinitialized = false;
		return;
	}
	else
	{
		networkinitialized = true;
		return;
	}
}

/****************************************************************************
 * DeinitNetwork
 ***************************************************************************/
void DeinitNetwork(void)
{
	net_wc24cleanup();
	net_deinit();
	networkinitialized = false;
}

/****************************************************************************
 * Check if network was initialised
 ***************************************************************************/
bool IsNetworkInit(void)
{
	return networkinitialized;
}

/****************************************************************************
 * Get network IP
 ***************************************************************************/
char * GetNetworkIP(void)
{
	return IP;
}

/****************************************************************************
 * Get incomming IP
 ***************************************************************************/
char * GetIncommingIP(void)
{
	return incommingIP;
}

s32 network_request(s32 connect, const char * request, char * filename)
{
	if(connect == NET_DEFAULT_SOCK)
		connect = connection;

	char buf[1024];
	char *ptr = NULL;

	u32 cnt, size;
	s32 ret;

	/* Send request */
	ret = net_send(connect, request, strlen(request), 0);
	if (ret < 0) return ret;

	/* Clear buffer */
	memset(buf, 0, sizeof(buf));

	/* Read HTTP header */
	for (cnt = 0; !strstr(buf, "\r\n\r\n"); cnt++)
		if (net_recv(connect, buf + cnt, 1, 0) <= 0) return -1;

	/* HTTP request OK? */
	if (!strstr(buf, "HTTP/1.1 200 OK")) return -1;

	if (filename)
	{
		/* Get filename */
		ptr = strstr(buf, "filename=\"");

		if (ptr)
		{
			ptr += sizeof("filename=\"") - 1;

			for (cnt = 0; ptr[cnt] != '\r' && ptr[cnt] != '\n' && ptr[cnt] != '"'; cnt++)
			{
				filename[cnt] = ptr[cnt];
				filename[cnt + 1] = '\0';
			}
		}
	}

	/* Retrieve content size */
	ptr = strstr(buf, "Content-Length:");
	if (!ptr) return NET_SIZE_UNKNOWN;

	sscanf(ptr, "Content-Length: %u", &size);
	return size;
}

s32 network_read(s32 connect, u8 *buf, u32 len)
{
	if(connect == NET_DEFAULT_SOCK)
		connect = connection;

	u32 read = 0;
	s32 ret = -1;

	/* Data to be read */
	while (read < len)
	{
		/* Read network data */
		ret = net_read(connect, buf + read, len - read);
		if (ret < 0) return ret;

		/* Read finished */
		if (!ret) break;

		/* Increment read variable */
		read += ret;
	}

	return read;
}

/****************************************************************************
 * Download request
 ***************************************************************************/
s32 download_request(const char * url, char * filename)
{
	//Check if the url starts with "http://", if not it is not considered a valid url
	if (strncmp(url, "http://", strlen("http://")) != 0)
	{
		return -1;
	}

	//Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');

	//At the very least the url has to end with '/', ending with just a domain is invalid
	if (path == NULL)
	{
		return -1;
	}

	//Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");

	if (domainlength == 0)
	{
		return -1;
	}

	char domain[domainlength + 1];
	strlcpy(domain, url + strlen("http://"), domainlength + 1);

	connection = GetConnection(domain);
	if (connection < 0)
	{
		return -1;
	}

	//Form a nice request header to send to the webserver
	char header[strlen(path) + strlen(domain) + strlen(url) + 100];
	sprintf(header, "GET %s HTTP/1.1\r\nHost: %s\r\nReferer: %s\r\nUser-Agent: USBLoaderGX\r\nConnection: close\r\n\r\n", path, domain, url);

	s32 filesize = network_request(connection, header, filename);

	return filesize;
}

/****************************************************************************
 * HTML HEAD request
 ***************************************************************************/
char * HEAD_Request(const char * url)
{
	if(strncmp(url, "http://", strlen("http://")) != 0)
	{
		gprintf("Not a valid URL");
		return NULL;
	}
	char *path = strchr(url + strlen("http://"), '/');

	if(!path)
	{
		gprintf("Not a valid URL path");
		return NULL;
	}

	int domainlength = path - url - strlen("http://");

	if(domainlength == 0)
	{
		gprintf("Not a valid domain");
		return NULL;
	}

	char domain[domainlength + 1];
	strncpy(domain, url + strlen("http://"), domainlength);
	domain[domainlength] = '\0';

	connection = GetConnection(domain);
	if(connection < 0)
	{
		gprintf("Could not connect to the server.");
		return NULL;
	}

	char header[strlen(path)+strlen(domain)*2+150];
	sprintf(header, "HEAD %s HTTP/1.1\r\nHost: %s\r\nReferer: %s\r\nUser-Agent: USB Loader GX\r\nConnection: close\r\n\r\n", path, domain, domain);

	/* Send request */
	s32 ret = net_send(connection, header, strlen(header), 0);
	if (ret < 0)
	{
		CloseConnection();
		return NULL;
	}

	u32 cnt = 0;
	char * buf = (char *) malloc(1024);
	memset(buf, 0, 1024);

	for (cnt = 0; !strstr(buf, "\r\n\r\n") && cnt < 1024; cnt++)
	{
		if(net_recv(connection, buf + cnt, 1, 0) <= 0)
		{
			CloseConnection();
			free(buf);
			return NULL;
		}
	}

	CloseConnection();

	return buf;
}

void CloseConnection()
{

	net_close(connection);

	if (waitforanswer)
	{
		net_close(socket);
		waitforanswer = false;
	}
}

/****************************************************************************
 * Test if connection to the address is available (PING)
 ***************************************************************************/
bool CheckConnection(const char *url, float timeout)
{
	//Check if the url starts with "http://", if not it is not considered a valid url
	if (strncmp(url, "http://", strlen("http://")) != 0)
		return false;

	//Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');

	//At the very least the url has to end with '/', ending with just a domain is invalid
	if (path == NULL)
		return false;

	//Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");
	if (domainlength == 0)
		return false;

	char domain[domainlength + 1];
	strlcpy(domain, url + strlen("http://"), domainlength + 1);

	//Parsing of the URL is done, start making an actual connection
	u32 ipaddress = getipbynamecached(domain);
	if (ipaddress == 0)
		return false;

	//Initialize socket
	s32 connection = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (connection < 0) return connection;

	s32 flags = net_fcntl(connection, F_GETFL, 0);
	if (flags >= 0) flags = net_fcntl(connection, F_SETFL, flags | 4);

	struct sockaddr_in connect_addr;
	memset(&connect_addr, 0, sizeof(connect_addr));
	connect_addr.sin_family = AF_INET;
	connect_addr.sin_port = 80;
	connect_addr.sin_addr.s_addr = getipbynamecached(domain);

	Timer netTime;

	int res = -1;
	while(res < 0 && res != -127 && netTime.elapsed() < timeout)
	{
		res = net_connect(connection, (struct sockaddr*) &connect_addr, sizeof(connect_addr));
		usleep(1000);
	}

	net_close(connection);

	return !(res < 0 && res != -127);
}


/****************************************************************************
 * Download request
 ***************************************************************************/
s32 DownloadWithResponse(const char * url, u8 **outbuffer, u32 *outsize)
{
	//Check if the url starts with "http://", if not it is not considered a valid url
	if (strncmp(url, "http://", strlen("http://")) != 0)
		return -1;

	//Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');

	//At the very least the url has to end with '/', ending with just a domain is invalid
	if (path == NULL)
		return -1;

	//Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");

	if (domainlength == 0)
		return -1;

	char domain[domainlength + 1];
	strlcpy(domain, url + strlen("http://"), domainlength + 1);

	int connect = GetConnection(domain);
	if (connect < 0)
		return -1;

	//Form a nice request header to send to the webserver
	char header[strlen(path) + strlen(domain) + strlen(url) + 100];
	sprintf(header, "GET %s HTTP/1.1\r\nHost: %s\r\nReferer: %s\r\nUser-Agent: USBLoaderGX\r\nConnection: close\r\n\r\n", path, domain, url);

	int ret = net_send(connect, header, strlen(header), 0);
	if(ret < 0)
	{
		net_close(connect);
		return ret;
	}

	int blocksize = 4096;

	u8 *buffer = (u8 *) malloc(blocksize);
	if(!buffer)
	{
		net_close(connect);
		return -1;
	}

	int done = 0;

	while(1)
	{
		int ret = network_read(connect, buffer+done, blocksize);

		if(ret < 0)
		{
			free(buffer);
			net_close(connect);
			return -1;
		}
		else if(ret == 0)
			break;

		done += ret;
		u8 *tmp = (u8 *) realloc(buffer, done+blocksize);
		if(!tmp)
		{
			free(buffer);
			net_close(connect);
			return -1;
		}

		buffer = tmp;
	}

	net_close(connect);

	u8 *tmp = (u8 *) realloc(buffer, done+1);
	if(!tmp)
	{
		free(buffer);
		return -1;
	}

	buffer = tmp;
	buffer[done] = 0;

	*outbuffer = buffer;
	*outsize = done;

	return done;
}


/****************************************************************************
 * NetworkWait
 ***************************************************************************/
int NetworkWait()
{
	if (!checkincomming) return -3;

	struct sockaddr_in sin;
	struct sockaddr_in client_address;
	socklen_t addrlen = sizeof(client_address);

	//Open socket
	socket = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

	if (socket == INVALID_SOCKET)
	{
		return socket;
	}

	sin.sin_family = AF_INET;
	sin.sin_port = htons( PORT );
	sin.sin_addr.s_addr = htonl( INADDR_ANY );

	if (net_bind(socket, (struct sockaddr*) &sin, sizeof(sin)) < 0)
	{
		net_close(socket);
		return -1;
	}

	if (net_listen(socket, 3) < 0)
	{
		net_close(socket);
		return -1;
	}

	connection = net_accept(socket, (struct sockaddr*) &client_address, &addrlen);

	sprintf(incommingIP, "%s", inet_ntoa(client_address.sin_addr));

	if (connection < 0)
	{
		net_close(connection);
		net_close(socket);
		return -4;

	}
	else
	{

		unsigned char haxx[9];
		//skip haxx
		net_read(connection, &haxx, 8);
		wiiloadVersion[0] = haxx[4];
		wiiloadVersion[1] = haxx[5];

		net_read(connection, &infilesize, 4);

		if (haxx[4] > 0 || haxx[5] > 4)
		{
			net_read(connection, &uncfilesize, 4); // Compressed protocol, read another 4 bytes
		}
		waitforanswer = true;
		checkincomming = false;
		networkHalt = true;
	}

	return 1;
}

/****************************************************************************
 * HaltNetwork
 ***************************************************************************/
void HaltNetworkThread()
{
	networkHalt = true;
	checkincomming = false;

	if (waitforanswer) CloseConnection();

	// wait for thread to finish
	while (!LWP_ThreadIsSuspended(networkthread))
		usleep(100);
}

/****************************************************************************
 * ResumeNetworkThread
 ***************************************************************************/
void ResumeNetworkThread()
{
	networkHalt = false;
	LWP_ResumeThread(networkthread);
}

/****************************************************************************
 * Resume NetworkWait
 ***************************************************************************/
void ResumeNetworkWait()
{
	networkHalt = true;
	checkincomming = true;
	waitforanswer = true;
	infilesize = 0;
	connection = -1;
	LWP_ResumeThread(networkthread);
}

/*********************************************************************************
 * Networkthread for background network initialize and update check with idle prio
 *********************************************************************************/
static void * networkinitcallback(void *arg)
{
	while (1)
	{
		if (!checkincomming && networkHalt)
			LWP_SuspendThread(networkthread);

		Initialize_Network();

		if (networkinitialized == true && updatechecked == false)
		{

			if (CheckUpdate() > 0) updateavailable = true;

			//suspend thread
			updatechecked = true;
			networkHalt = true;
		}

		if (checkincomming) NetworkWait();

		usleep(100000);
	}
	return NULL;
}

/****************************************************************************
 * InitNetworkThread with priority 0 (idle)
 ***************************************************************************/
void InitNetworkThread()
{
	LWP_CreateThread(&networkthread, networkinitcallback, NULL, NULL, 16384, 0);
}

/****************************************************************************
 * ShutdownThread
 ***************************************************************************/
void ShutdownNetworkThread()
{
	LWP_JoinThread(networkthread, NULL);
	networkthread = LWP_THREAD_NULL;
}
