#include "http.h"
#include "gecko/gecko.hpp"

/**
 * Emptyblock is a statically defined variable for functions to return if they are unable
 * to complete a request
 */
const struct block emptyblock = {0, NULL};

//The maximum amount of bytes to send per net_write() call
//#define NET_BUFFER_SIZE 1024
#define NET_BUFFER_SIZE 3600

static u8 retryloop = 0;

// Write our message to the server
static s32 send_message(s32 server, char *msg)
{
	s32 bytes_transferred = 0;
	s32 remaining = strlen(msg);
	while (remaining)
	{
		if((bytes_transferred = net_write(server, msg, remaining > NET_BUFFER_SIZE ? NET_BUFFER_SIZE : remaining)) > 0)
		{
			remaining -= bytes_transferred;
			msg += bytes_transferred;
			usleep (20 * 1000);
		}
		else if(bytes_transferred < 0)
			return bytes_transferred;

		else
			return -ENODATA;
	}
	return 0;
}

/**
 * Connect to a remote server via TCP on a specified port
 *
 * @param u32 ip address of the server to connect to
 * @param u32 the port to connect to on the server
 * @return s32 The connection to the server (negative number if connection could not be established)
 */
static s32 server_connect(u32 ipaddress, u16 socket_port)
{
	//Initialize socket
	s32 connection = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if(connection < 0) return connection;

	//Set the connection parameters for the socket
 	struct sockaddr_in connect_addr;
	memset(&connect_addr, 0, sizeof(connect_addr));
	connect_addr.sin_family = AF_INET;
	connect_addr.sin_port = htons(socket_port);
	connect_addr.sin_addr.s_addr= ipaddress;

	//Attemt to open a connection on the socket
	if(net_connect(connection, (struct sockaddr*)&connect_addr, sizeof(connect_addr)) == -1)
	{
		net_close(connection);
		return -1;
	}

	return connection;
}

//The amount of memory in bytes reserved initially to store the HTTP response in
//Be careful in increasing this number, reading from a socket on the Wii 
//will fail if you request more than 20k or so
#define HTTP_BUFFER_SIZE 1024 * 5

//The amount of memory the buffer should expanded with if the buffer is full
#define HTTP_BUFFER_GROWTH 1024 * 5

/**
 * This function reads all the data from a connection into a buffer which it returns.
 * It will return an empty buffer if something doesn't go as planned
 *
 * @param s32 connection The connection identifier to suck the response out of
 * @return block A 'block' struct (see http.h) in which the buffer is located
 */
struct block read_message(s32 connection)
{
	//Create a block of memory to put in the response
	struct block buffer;
	buffer.data = malloc(HTTP_BUFFER_SIZE);
	buffer.size = HTTP_BUFFER_SIZE;

	if (buffer.data == NULL)
	{
		return emptyblock;
	}

	//The offset variable always points to the first byte of memory that is free in the buffer
	u32 offset = 0;

	while(true)
	{
 		//Fill the buffer with a new batch of bytes from the connection,
		//starting from where we left of in the buffer till the end of the buffer
		s32 bytes_read = net_read(connection, buffer.data + offset, buffer.size - offset);

		//Anything below 0 is an error in the connection
		if(bytes_read < 0)
		{
			//printf("Connection error from net_read()  Errorcode: %i\n", bytes_read);
			return emptyblock;
		}
		
		//No more bytes were read into the buffer,
		//we assume this means the HTTP response is done
		if(bytes_read == 0)
		{
			break;
		}

		offset += bytes_read;

		//Check if we have enough buffer left over,
		//if not expand it with an additional HTTP_BUFFER_GROWTH worth of bytes
		if (offset >= buffer.size)
		{
			buffer.size += HTTP_BUFFER_GROWTH;
			u8 * tmp = realloc(buffer.data, buffer.size);

			if (tmp == NULL)
			{
				free(buffer.data);
				return emptyblock;
			}
			else
				buffer.data = tmp;
		}
	}
	//At the end of above loop offset should be precisely the amount of bytes that were read from the connection
	buffer.size = offset;

	//Shrink the size of the buffer so the data fits exactly in it
	buffer.data = realloc(buffer.data, buffer.size);

	return buffer;
}

/* Downloads the contents of a URL to memory
 * This method is not threadsafe (because networking is not threadsafe on the Wii) 
 */
struct block downloadfile(const char *url)
{
	//Check if the url starts with "http://", if not it is not considered a valid url
	if(strncmp(url, "http://", strlen("http://")) != 0)
		return emptyblock;
	
	//Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');
	if(path == NULL)
		return emptyblock;
	
	//Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");
	if(domainlength == 0)
		return emptyblock;
	
	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 emptyblock;

	s32 connection = server_connect(ipaddress, 80);
	if(connection < 0)
		return emptyblock;
	
	//Form a nice request header to send to the webserver
	char* headerformat = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: WiiFlow 2.1\r\n\r\n";
	char header[strlen(headerformat) + strlen(domain) + strlen(path)];
	sprintf(header, headerformat, path, domain);

	//Do the request and get the response
	send_message(connection, header);
	struct block response = read_message(connection);
	net_close(connection);

	//Search for the 4-character sequence \r\n\r\n in the response which signals the start of the http payload (file)
	unsigned char *filestart = NULL;
	u32 filesize = 0;
	u32 i;
	char newURL[512];
	bool redirect = false;
	
	for(i = 3; i < response.size; i++)
	{
		if(response.data[i] == '\n' && response.data[i-1] == '\r' && response.data[i-2] == '\n' && response.data[i-3] == '\r')
		{
			filestart = response.data + i + 1;
			filesize = response.size - i - 1;
			
			// Check the HTTP response code
			if (response.size > 10 && strncmp((char*)response.data, "HTTP/", 5)==0) 
			{
				char htstat[i];
				strncpy(htstat, (char*)response.data, i);
				htstat[i] = 0;
				char *codep;
				codep = strchr(htstat, ' ');
				if (codep) 
				{
					int code;
					if (sscanf(codep+1, "%d", &code) == 1) 
					{
						//gprintf("HTTP response code: %d\n", code);
						if (code == 302) // 302 FOUND (redirected link)
						{
							char *ptr = strcasestr((char*)response.data, "Location: ");
							if(ptr)
							{
								ptr += strlen("Location: ");
								strncpy(newURL, ptr, sizeof(newURL));
								*(strchr(newURL, '\r'))=0;
								
								redirect = true;
								//gprintf("New URL to download = %s \n", newURL);
							}
							else
							{
								//gprintf("HTTP ERROR: %s\n", htstat);
								free(response.data);
								return emptyblock;
							}
						}
						if (code >= 400) // Not found
						{
							//gprintf("HTTP ERROR: %s\n", htstat);
							free(response.data);
							return emptyblock;
						}
					}
				}
			}
			break;
		}
	}

	if(redirect)
	{
		// Prevent endless loop
		retryloop++;
		if(retryloop > 3)
		{
			retryloop = 0;
			free(response.data);
			return emptyblock;
		}
		
		struct block redirected = downloadfile(newURL);

		// copy the newURL data into the original data
		u8 * tmp = realloc(response.data, redirected.size);
		if (tmp == NULL)
		{
			//gprintf("Could not allocate enough memory for new URL. Download canceled.\n");
			free(response.data);
			free(redirected.data);
			return emptyblock;
		}
		response.data = tmp;
		memcpy(response.data, redirected.data, redirected.size);

		// Set filestart's new size based on redirected file
		filestart = response.data;
		filesize = redirected.size;
		free(redirected.data);
		
	}
	retryloop = 0;
	
	if(filestart == NULL)
	{
		free(response.data);
		return emptyblock;
	}
	
	//Copy the file part of the response into a new memoryblock to return
	struct block file;
	file.data = malloc(filesize);
	file.size = filesize;
	
	if (file.data == NULL)
	{
		//printf("No more memory to copy file from HTTP response\n");
		free(response.data);
		return emptyblock;
	}

	memcpy(file.data, filestart, filesize);

	//Dispose of the original response
	free(response.data);

	return file;
}