/*
** Transfer
*/

#include "include/config.h"

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

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

#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <bsdsocket/socketbasetags.h>
#include <error.h>
#include <time.h>

#include "include/mui.h"
#include <MUI/NListview_mcc.h>
#include "include/download.h"
#include "include/upload.h"
#include "include/gui.h"
#include "include/panel.h"
#include "include/prefs.h"
#include "include/transfer.h"
#include "amster_Cat.h"
#include "include/protos.h"


MUIF translistdisp(REG(a2) char **array, REG(a1) songtrans sd)
{
	static char buf2[50],buf3[50],buf4[50],buf5[50];

	static char *states[] = {
		(char *)_MSG_TRANS_STAT_PREPARE,
		(char *)_MSG_TRANS_STAT_QUEUE,
		(char *)_MSG_TRANS_STAT_WAIT,
		(char *)_MSG_TRANS_STAT_CONN,
		(char *)_MSG_TRANS_STAT_REQ,
		(char *)_MSG_TRANS_STAT_INIT,
		(char *)_MSG_TRANS_STAT_DL,
		(char *)_MSG_TRANS_STAT_UL,
		(char *)_MSG_TRANS_STAT_FIN,
		(char *)_MSG_TRANS_STAT_ABORT,
		(char *)_MSG_TRANS_STAT_ERROR,
		NULL
	};

	static char *errors[] = {
		(char *)_MSG_TRANS_STAT_ERROR,
		(char *)_MSG_TRANS_ERROR_FILEOPEN,
		(char *)_MSG_TRANS_ERROR_FILEREAD,
		(char *)_MSG_TRANS_ERROR_FILEWRITE,
		(char *)_MSG_TRANS_ERROR_NET_UNKNOWN,
		(char *)_MSG_TRANS_ERROR_LOGGEDOUT,
		(char *)_MSG_TRANS_ERROR_NOTFOUND,
		(char *)_MSG_TRANS_ERROR_INVALIDREQUEST,
		(char *)_MSG_TRANS_ERROR_TEASER,
		(char *)_MSG_TRANS_ERROR_BUSY,
		NULL
	};

	static char *NetError[] = {
		(char *)_MSG_TRANS_ERROR_NET_TIMEOUT,
		(char *)_MSG_TRANS_ERROR_NET_REFUSED,
		(char *)_MSG_TRANS_ERROR_NET_RESET,
		(char *)_MSG_TRANS_ERROR_NET_PIPE,
		NULL
	};

	if(states[0] == (char *)_MSG_TRANS_STAT_PREPARE)
		localize_array(states);

	if(errors[0] == (char *)_MSG_TRANS_STAT_ERROR)
		localize_array(errors);

	if(NetError[0] == (char *)_MSG_TRANS_ERROR_NET_TIMEOUT)
		localize_array(NetError);

	if(sd) {
		*array++ = nap_strippath(sd->song->title);

		if (sd->state == DLS_WAIT) {
			sprintf(buf5, states[sd->state], sd->ErrorCode);
			*array++ = buf5;
		}
		else if (sd->state != DLS_ERROR)
			*array++ = states[sd->state];
		else {
			switch (sd->error) {
				case ERROR_FILEOPEN:
				case ERROR_FILEREAD:
				case ERROR_FILEWRITE:
					sprintf(buf5, errors[sd->error], sd->ErrorCode);
					*array++ = buf5;
					break;
				case ERROR_NET:
					switch (sd->ErrorCode) {
						case ETIMEDOUT:
							*array++ = NetError[ERROR_NET_TIMEOUT];
							break;
						case ECONNREFUSED:
							*array++ = NetError[ERROR_NET_REFUSED];
							break;
						default:
							sprintf(buf5, errors[sd->error], sd->ErrorCode);
							*array++ = buf5;
					}
					break;
				default:
					*array++ = errors[sd->error];
			}
		}

		if (sd->size > 0)
			sprintf(buf2,"\33r%ld / %ld (%d%%)", sd->cur, sd->size, (sd->cur*100+5)/sd->size);
		else sprintf(buf2, "\33r0");	/* Can this happen? */
		*array++ = buf2;

		if (sd->cps > 0) {
			if (sd->stalltick<5) {	/* 5 seconds */
				sprintf(buf3,"%d",sd->cps);
				*array++ = buf3;
			}
			else {
				*array++ = (char *)MSG_TRANS_STAT_STALLED;
			}
			sprintf(buf4,"%ld:%02ld / %ld:%02ld",sd->transtime/60,sd->transtime%60,sd->timeleft/60,sd->timeleft%60);
			*array = buf4;
		}
		else {
			*array++ = "-";
			*array   = "-";
		}

	}
	else {
		*array++ = (char *)MSG_LH_FILE;
		*array++ = (char *)MSG_LH_STATE;
		*array++ = (char *)MSG_LH_SIZE;
		*array++ = (char *)MSG_LH_CPS;
		*array =   (char *)MSG_LH_ETIME;
	}
	return(0);
}


ULONG dl_setup(struct IClass *cl, Object *obj, Msg msg)
{
	struct TransferData *data = INST_DATA(cl,obj);

	if (!DoSuperMethodA(cl,obj,msg))
		return(FALSE);

	DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->ihnode);

	return(TRUE);
}


ULONG dl_muicleanup(struct IClass *cl, Object *obj, Msg msg)
{
	struct TransferData *data = INST_DATA(cl,obj);

	DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->ihnode);

	return(DoSuperMethodA(cl,obj,msg));
}


void CalculateCps(songtrans sd)
{
	if (sd->cur != sd->oldsize) {
		sd->stalltick = 0;
		sd->oldsize = sd->cur;
	}
	else sd->stalltick++;

	sd->transtime = time(NULL) - sd->starttime;
	if (sd->transtime == 0) sd->transtime = 1;
	sd->cps = (sd->cur - sd->resumestart) / sd->transtime;
	if (sd->cps > 0)
		sd->timeleft = (sd->size - sd->cur) / sd->cps;
	else sd->timeleft = 0;
}


void TransferSetError(struct TransferData *data, char *title, char *user, int error)
{
	u_long tmp;
	songtrans sd;
	long i;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) return;
		sd = (songtrans)tmp;
		if (strcmp(sd->song->title, title) == 0 && stricmp(sd->song->user, user) == 0) break;
	}

	sd->state = DLS_ERROR;
	sd->error = error;

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) DoMethod(gui->dwin, DL_UPDATE, sd);
	else DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
}


void TransferInfo(struct TransferData *data)
{
	static char buf[600];
	u_long tmp;
	songtrans sd;

	GetAttr(MUIA_NList_EntryClick,  data->list, &tmp);
	if (tmp == -1 || tmp == -2) return;
	DoMethod(data->list, MUIM_NList_GetEntry, tmp, &sd);
	if (!sd) return;

	if(sd->host[0] == '\0') {
		sprintf(buf, "%s (%s)", sd->song->user, Inet_NtoA(sd->song->ip));
	}
	else {
		sprintf(buf, "%s (%s)", sd->song->user, sd->host);
	}

	set(data->info, MUIA_Text_Contents, buf);
}


void TransferCleanup(struct TransferData *data)
{
	u_long item;
	int i=0, j;

	while (1) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &item);
		if (!item) break;
		j = ((songtrans)item)->state;
		if (j >= DLS_FIN) {
			DoMethod(data->list,MUIM_NList_Remove,i);
			nap_songfree(((songtrans)item)->song);
			if (((songtrans)item)->fname) free(((songtrans)item)->fname);
			free((songtrans)item);
		} else i++;
	}
}


void TransferHandleError(songtrans sd)
{
	char error[128];

	if (sd->error != 0) {
		sd->state = DLS_ERROR;
		if (sd->error >= ERROR_FILEOPEN && sd->error <= ERROR_FILEWRITE) {
			Fault(sd->ErrorCode, "", error, 127);
			gui_debugf((char *)MSG_INFO_IOERROR, sd->fname, error);
		}
		else if (sd->error >= ERROR_OUTOFBOUND) sd->error = ERROR_UNKNOWN;
		prf_event(PRFE_DLERROR);
	}
	else if (sd->state == DLS_FIN) {
		if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) prf_event(PRFE_DLFINISH);
		else prf_event(PRFE_ULFINISH);
	}

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) DoMethod(gui->dwin, DL_UPDATE, sd);
	else DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
}


/* Thread stuff */

BOOL InitTransferThread(thread t, songtrans sd)
{
	struct Library *DosBase;
	struct Library *SocketBase;
	char *buffer;
	long s;
	long tmp;
	struct hostent *he;

	sd->state = DLS_PREP;

	sd->nsig = AllocSignal(-1);
	if (sd->nsig == -1) {
		ExitTransferThread(sd, 1);
		return FALSE;
	}
	sd->nsigm = (1L << (sd->nsig));
	sd->msigm = (1L << (t->port->mp_SigBit));

	DosBase = OpenLibrary("dos.library", 0);
	if (!DosBase) {
		ExitTransferThread(sd, 1);
		return FALSE;
	}
	sd->DosBase = DosBase;

	SocketBase = OpenLibrary("bsdsocket.library", 0);
	if (!SocketBase) {
		ExitTransferThread(sd, 1);
		return FALSE;
	}
	sd->SocketBase = SocketBase;
	SocketBaseTags(SBTM_SETVAL(SBTC_SIGIOMASK), (char *)sd->nsigm, TAG_DONE);

	buffer = malloc(8192);
	if (!buffer) {
		ExitTransferThread(sd, 2);
		return FALSE;
	}
	sd->buffer = buffer;

	/* Perform DNS-lookup */
	he = gethostbyname(Inet_NtoA(sd->song->ip));
	he = gethostbyaddr(he->h_addr_list[0], he->h_length, AF_INET);
	if (he != 0) strcpy(sd->host, he->h_name);
	else strcpy(sd->host, Inet_NtoA(sd->song->ip));

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s<0) {
		ExitTransferThread(sd, 3);
		return FALSE;
	}
	sd->s = s;

	sd->sin.sin_addr.s_addr = sd->ip;
	sd->sin.sin_port = sd->port;
	sd->sin.sin_family = AF_INET;
	sd->sin.sin_len = sizeof(sd->sin);

	tmp = 1;
	IoctlSocket(s, FIOASYNC, (char *)&tmp);
	IoctlSocket(s, FIONBIO,  (char *)&tmp);
	/* Asynchronous and non-blocking I/O to the socket */

	tmp = connect(s, (struct sockaddr *)&sd->sin, sizeof(sd->sin));
	if (tmp == -1 && Errno() != EINPROGRESS) {
		sd->ErrorCode = Errno();
		ExitTransferThread(sd, ERROR_NET);
		return FALSE;
	}

	thr_message(t, DLC_STATE, (APTR)DLS_CON);

	return TRUE;
}


void ExitTransferThread(songtrans sd, int ret)
{
	struct Library *DosBase=sd->DosBase;
	struct Library *SocketBase=sd->SocketBase;

	if (sd->nsig != -1) FreeSignal(sd->nsig);
	sd->nsig = -1;

	if (sd->f) Close(sd->f);
	sd->f = 0;

	if (sd->s != -1) CloseSocket(sd->s);
	sd->s = -1;

	if (sd->buffer) free(sd->buffer);
	sd->buffer = NULL;

	if (SocketBase) CloseLibrary(SocketBase);
	sd->SocketBase = NULL;

	if (DosBase) CloseLibrary(DosBase);
	sd->DosBase = NULL;

	thr_exit(sd->t, ret);
}
