/*-*-linux-c-*-*/

#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>

#include <gtk/gtk.h>

#include "newtella.h"
#include "connection.h"
#include "gui.h"
#include "gnutella.h"
#include "hosts.h"
#include "downloads.h"
#include "http.h"
#include "file.h"

/* General functions for Download connections */

int download_write(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct newtella_connection *dcon = (struct newtella_connection *)gdata;
	struct gpacket *packet;
	gint r;

	if (cond == GDK_INPUT_EXCEPTION) {
		download_stop(dcon, "Failed at sending HTTP headers");
		return -1;
	}

	if (g_list_length(dcon->queue)) {
		packet = (struct gpacket *) g_list_last(dcon->queue)->data;

		r = send(dcon->s, packet->data, packet->len, 0);

		if (r != packet->len){
			download_stop(dcon, "Failed at sending HTTP headers");
		}

		dcon->queue = g_list_remove(dcon->queue, packet);

		newtella_free(packet->data);
		newtella_free(packet);

		if (dcon->write_tag) // Igor: if ist neu
			gdk_input_remove(dcon->write_tag);
	}

	return 1;
}

int download_read(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct newtella_connection *dcon = (struct newtella_connection *)gdata;
	struct http_header *http;

	gchar *save_file;
	gchar  servent_info[1024];
	gint   bytes_to_read, bytes_readed, http_skip, http_size;
	gchar ip[INET6_ADDRSTRLEN] = "\0";

	if (cond == GDK_INPUT_EXCEPTION) {
		download_stop(dcon, "Failed to read!");
		return -1;
	}

	bytes_to_read = bytes_avail(dcon->s);

	if (!bytes_to_read) {
		if (dcon->total_handled == dcon->fi->size){
			dcon->state = GS_COMPLETE;
			if (dcon->read_tag)
				gdk_input_remove(dcon->read_tag);

			//
			if (dcon->write_tag)
				gdk_input_remove(dcon->write_tag);
			//
			close(dcon->s);

			/* close the downloaded file */
			if (dcon->fi->des)
				close(dcon->fi->des);
		}
		else {
			dcon->state = GS_FAILED;
			download_stop(dcon, "Failed to read!");
			return -1;
		}

		gui_update_download(dcon);

		return -1;
	}

	dcon->b = dcon->buffer + dcon->buffer_len;
	bytes_readed = con_recv_packet(dcon, bytes_to_read);


	if (bytes_readed != bytes_to_read) {
		download_stop(dcon, "Failed to read!");
		return -1;
	}

	dcon->buffer_len += bytes_readed;

        if (dcon->hs_sent) {
		write(dcon->fi->des, dcon->buffer, bytes_readed);
		dcon->total_handled += bytes_readed;
		con_reset_buffer(dcon);
		gui_update_download(dcon);
	} else {
		/* parse the http headers */
		do {
			http_size = http_header_size(dcon->buffer);

			// Igor: Jetzt wird auch auf GIV geprüft, ansonsten würde er die Schleife wieder verlassen
			if (!http_size)
				http_size = http_giv_size(dcon->buffer);

			if (!http_size)
				break;

			http = newtella_malloc(http_size);

			// Igor: Hier wird der Header geparst
			http = http_header_parse(dcon->buffer);

			if (!memcmp("GIV", dcon->buffer, 3)) {

				con_reset_buffer(dcon);

				http->func = HTTP_GET;
				http->user_agent = GNEWTELLIUM;
				http->connection = "Keep-Alive";
				http->file_name = dcon->fi->file_name;
				http->file_index = dcon->fi->index;
				http->range_start = 0;

				dcon->write_tag =
					gdk_input_add(dcon->s,
						      GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
						      (GdkInputFunction) download_write,
						      (gpointer)dcon);

				con_send_packet(dcon, http_header_build(http),
						strlen(http_header_build(http)), 0);
				return 1;
			}

			//g_print("***Got Response: %s\n", dcon->buffer);
			// Igor: Hab das Parsen vorverlegt
			//http = http_header_parse(dcon->buffer);

			/* GUI update */
			if (http->user_agent) {
				gint row;
				memcpy(ip, ip2str(dcon->fi->ip), sizeof(ip));
				g_snprintf(servent_info, sizeof(servent_info),
					   "[%s]:%d (%s)", ip,
					   dcon->fi->port, http->user_agent);

				row = gtk_clist_find_row_from_data(GTK_CLIST(download_list),
								   (gpointer) dcon);
				gtk_clist_set_text(GTK_CLIST(download_list), row, 4, servent_info);
			}

			if (http->valid && (http->code == 200 || http->code == 206)) {
				save_file = newtella_malloc(strlen(gl_options->download_dir)+
							    strlen(dcon->fi->file_name)+1);
				strcpy(save_file, gl_options->download_dir);
				strcat(save_file, dcon->fi->file_name);

				if (!http->range_start)
					dcon->fi->des = open(save_file,
							     O_WRONLY | O_CREAT | O_TRUNC,
							     0644);
				else {
					gui_update_download_status(dcon, "Resuming");

					dcon->fi->des = open(save_file,
							     O_WRONLY,
							     0644);
					lseek(dcon->fi->des, http->range_start, SEEK_SET);
				}

				newtella_free(save_file);

				/* cut the HTTP header */
				http_skip = bytes_readed - http_size;
				write(dcon->fi->des, dcon->buffer+http_size, http_skip);

				dcon->total_handled = http->range_start - http_skip;

				con_reset_buffer(dcon);

				dcon->hs_sent = 1;
				dcon->state = GS_CONNECTED;
			} else {

				switch (http->code) {
				case 404:
					dcon->state = GS_NOTFOUND;
					break;
				case 503:
					dcon->state = GS_BUSY;
					break;
				default:
					dcon->state = GS_FAILED;
					break;
				}

				download_stop(dcon, NULL);

				break;
			}

		} while (!dcon->hs_sent);
	}

	return 1;
}

int download_accept(gpointer data, gint source, GdkInputCondition cond)
{
	//struct sockaddr_in6 addr;
	struct sockaddr_storage addr;
	size_t len = sizeof(addr);
	struct newtella_connection *dcon =  data;

	int rval = accept(dcon->s, (struct sockaddr *) &addr, &len);

	if (rval == -1){
		printf("Can't accept\n");
	}

	dcon->s = rval;

	if (dcon->read_tag) // Igor: if ist neu
		gdk_input_remove(dcon->read_tag);


	dcon->read_tag = gdk_input_add(dcon->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
					(GdkInputFunction) download_read, (gpointer)dcon);
	return 1;
}

int download_add(struct file_found *download_file)
{
	struct newtella_connection *dcon = newtella_malloc(sizeof(struct newtella_connection));
	gint resume;

	/* copy the file info */
	dcon->fi = newtella_malloc(sizeof *(dcon->fi));
	*(dcon->fi) = *download_file;

	bzero(dcon->ip, sizeof(dcon->ip));
	memcpy(dcon->ip ,gl_my_host_info->ip, sizeof(dcon->ip));

	con_zero_stats(dcon);
	con_init_queue(dcon);

	dcon->hs_sent = 0;

	// Igor: Hab hier den Wert von 1 auf 0 gesetzt
	if (gl_options->dcons == 0)
	//if (gl_options->dcons == 1)
		gl_down_con = g_list_append(NULL, dcon);
	else
		gl_down_con = g_list_append(gl_down_con, dcon);

	gui_add_download(dcon);

	resume = file_get_size(dcon->fi->file_name, gl_options->download_dir);

	download_start(dcon, resume);

	return 1;
}

void download_resume(struct newtella_connection *dcon)
{
	gint resume;

	dcon->state = GS_RESUME;
	gui_update_download_status(dcon, "Trying to resume...");

	con_reset_buffer(dcon);
	con_zero_stats(dcon);
	con_init_queue(dcon);

	dcon->hs_sent = 0;

	resume = file_get_size(dcon->fi->file_name, gl_options->download_dir);

	download_start(dcon, resume);
}

int download_start_old(struct newtella_connection *dcon, gint resume)
{
	struct sockaddr_in6 gnode, addr;
	size_t option;
	struct http_header *http = newtella_malloc(sizeof(struct http_header));

	if (!dcon->fi->push) {
		/* direct connection */
		dcon->state = GS_WAITINGCONNECT;
		dcon->s = newtella_force_connect(dcon->fi->ip, dcon->fi->port);

		if (dcon->s == -1) {
			perror("connect");
			dcon->state = GS_FAILED;
			return (-1);
		}

		setsockopt(dcon->s, SOL_SOCKET, SO_KEEPALIVE, (void *) &option, sizeof(option));
		setsockopt(dcon->s, SOL_SOCKET, SO_REUSEADDR, (void *) &option, sizeof(option));

		dcon->read_tag = gdk_input_add(dcon->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
			      (GdkInputFunction) download_read, (gpointer)dcon);

		dcon->write_tag = gdk_input_add(dcon->s, GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
			      (GdkInputFunction) download_write, (gpointer)dcon);


		http->func = HTTP_GET;
		http->user_agent = GNEWTELLIUM;
		http->connection = "Keep-Alive";
		http->file_name = dcon->fi->file_name;
		http->file_index = dcon->fi->index;
		http->range_start = resume;
		http->range_end = dcon->fi->size;

		//g_print("***Sending %s\n", http_header_build(http));
		con_send_packet(dcon, http_header_build(http),
				strlen(http_header_build(http)), 0);

	} else {
		/* firewall - send a push */
		dcon->state = GS_WAITINGPUSH;
		gui_update_download(dcon);

		dcon->s = socket(AF_INET6, SOCK_STREAM, 0);

		if (fcntl(dcon->s, F_SETFL, O_NONBLOCK) == -1) {
			perror("fcntl");
			exit(EXIT_FAILURE);
		}

		gnode.sin6_family = AF_INET6;
		gnode.sin6_addr = in6addr_any;
		gnode.sin6_port = htons(0);

		bind(dcon->s, (struct sockaddr *)&gnode, sizeof(gnode));

		if (listen(dcon->s, 1) == -1)
			printf("Can't listen\n");

		/* get the port number */
		option = sizeof(struct sockaddr_in6);

		if (getsockname(dcon->s, (struct sockaddr *) &addr, &option) == -1)
        		printf("Can't getsockname\n");

		dcon->port = ntohs(addr.sin6_port);
		//dcon->port = addr.sin6_port;

		gnutella_send_push(dcon);

		dcon->read_tag = gdk_input_add(dcon->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
					(GdkInputFunction) download_accept, (gpointer) dcon);
	}


	/* update time */
	dcon->last_time = time((time_t)NULL);

	return 1;
}

int download_start(struct newtella_connection *dcon, gint resume)
{
	size_t option;
	struct http_header *http = newtella_malloc(sizeof(struct http_header));
	struct addrinfo *aip, *aipsave, hints;
	int error;
	struct sockaddr_in6 *sin6;

	//dcon->fi->push=1;
	if (!dcon->fi->push) {
		/* direct connection */
		dcon->state = GS_WAITINGCONNECT;
		dcon->s = newtella_force_connect(dcon->fi->ip, dcon->fi->port);
		if (dcon->s == -1) {
			perror("connect");
			dcon->state = GS_FAILED;
			return (-1);
		}

		setsockopt(dcon->s, SOL_SOCKET, SO_KEEPALIVE, (void *) &option, sizeof(option));
		setsockopt(dcon->s, SOL_SOCKET, SO_REUSEADDR, (void *) &option, sizeof(option));

		dcon->read_tag = gdk_input_add(dcon->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
			      (GdkInputFunction) download_read, (gpointer)dcon);
		dcon->write_tag = gdk_input_add(dcon->s, GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
			      (GdkInputFunction) download_write, (gpointer)dcon);


		http->func = HTTP_GET;
		http->user_agent = GNEWTELLIUM;
		http->connection = "Keep-Alive";
		http->file_name = dcon->fi->file_name;
		http->file_index = dcon->fi->index;
		http->range_start = resume;
		//http->range_start = 0;
		http->range_end = dcon->fi->size;

		//g_print("***Sending %s\n\n", http_header_build(http));
		con_send_packet(dcon, http_header_build(http),
				strlen(http_header_build(http)), 0);

	} else {
		/* firewall - send a push */
		dcon->state = GS_WAITINGPUSH;
		gui_update_download(dcon);

		bzero(&hints, sizeof(hints));
		hints.ai_flags = AI_PASSIVE;
		hints.ai_family = AF_INET6;
		hints.ai_socktype = SOCK_STREAM;

		error = getaddrinfo(NULL, "0", &hints, &aip);
		if (error != 0){
		    perror("getaddrinfo");
		    dcon->state = GS_FAILED;
		    return (-1);
		}

		aipsave = aip;

		dcon->s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
		if (dcon->s == -1){
		   perror("socket");
                   dcon->state = GS_FAILED;
		   freeaddrinfo(aipsave);
		   return (-1);
		}

		if (fcntl(dcon->s, F_SETFL, O_NONBLOCK) == -1) {
			perror("fcntl");
			freeaddrinfo(aipsave);
			exit(EXIT_FAILURE);
		}

		bind(dcon->s, aip->ai_addr, aip->ai_addrlen);

		if (listen(dcon->s, 1) == -1)
			printf("Can't listen\n");

		/* get the port number */
		//option = sizeof(struct sockaddr_in6);

		if (getsockname(dcon->s, aip->ai_addr, &aip->ai_addrlen) == -1)
        		printf("Can't getsockname\n");

		sin6 = (struct sockaddr_in6 *) aip->ai_addr;
		dcon->port = ntohs(sin6->sin6_port);

		gnutella_send_push(dcon);

		dcon->read_tag = gdk_input_add(dcon->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
					(GdkInputFunction) download_accept, (gpointer) dcon);

		freeaddrinfo(aipsave);
	}


	/* update time */
	dcon->last_time = time((time_t)NULL);

	return 1;
}


int download_stop(struct newtella_connection *dcon, const gchar *reason)
{

	if (dcon->read_tag)
		gdk_input_remove(dcon->read_tag);

	// Igor: Hab ich neu eingefügt, damit keine Probleme mit einer neuen gleichen Socket-Id auftreten
	if (dcon->write_tag)
		gdk_input_remove(dcon->write_tag);
	// ---------------------------
	close(dcon->s);

	/* close the downloaded file */
	if (dcon->fi->des)
		close(dcon->fi->des);

	gui_update_download(dcon);

	if (reason)
		gui_update_download_status(dcon, reason);

	dcon->state = GS_STOPPED;

	return 1;
}

int download_kill(struct newtella_connection *dcon)
{
	if (dcon->state != GS_STOPPED)
		download_stop(dcon, "Killed");

	gl_down_con = g_list_remove(gl_down_con, dcon);

	gui_remove_download(dcon);

	newtella_free(dcon->fi);
	newtella_free(dcon);

	return 1;
}
