/*
** Amster - Transfer
** Copyright © 1999-2000 by Gürer Özen & Jacob Laursen
**
** Maintained by Jacob Laursen <laursen@myself.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#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 bufd2[50], bufd3[50], bufd4[50], bufd5[50];
	static char bufu2[50], bufu3[50], bufu4[50], bufu5[50];
	char *buf2, *buf3, *buf4, *buf5;

	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,
		(char *)_MSG_TRANS_ERROR_NOTREQ,
		(char *)_MSG_TRANS_ERROR_NET_TIMEOUT,
		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) {
		if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
			buf2 = bufd2;
			buf3 = bufd3;
			buf4 = bufd4;
			buf5 = bufd5;
		}
		else {
			buf2 = bufu2;
			buf3 = bufu3;
			buf4 = bufu4;
			buf5 = bufu5;
		}

		*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;
						case ECONNRESET:
							*array++ = NetError[ERROR_NET_RESET];
							break;
						case EPIPE:
							*array++ = NetError[ERROR_NET_PIPE];
							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 || sd->state >= DLS_FIN) {	/* 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;
}


MUIF translistdest(REG(a2) APTR pool, REG(a1) songtrans sd)
{
	nap_songfree(sd->song);
	if (sd->fname) free(sd->fname);
	free(sd);

	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);
/*	DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->watchnode);*/

	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);
/*	DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->watchnode);*/

	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 = GetDateStamp() - 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 && sd->state < DLS_FIN) break;
	}

	if ((sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) && sd->state == DLS_PREP) {
		DoMethod(gui->dwin, DL_COUNTDECREMENT);
	}

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

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


void TransferInfo(struct TransferData *data)
{
	static char bufd[600], bufu[600];
	char *buf;
	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->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
		if (sd->state < DLS_ABORT) set(data->BT_Resume, MUIA_Disabled, TRUE);
		else set(data->BT_Resume, MUIA_Disabled, FALSE);
		buf = bufd;
	}
	else buf = bufu;

	if (sd->song->ip == 0) return;

	if (sd->host[0] == '\0') {
		sprintf(buf, "%s (%d.%d.%d.%d)", sd->song->user, (sd->song->ip&0xFF000000)>>24, (sd->song->ip&0xFF0000)>>16, (sd->song->ip&0xFF00)>>8, sd->song->ip&0xFF);
	}
	else {
		sprintf(buf, "%s (%s)", sd->song->user, sd->host);
	}

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


void TransferAbort(struct TransferData *data)
{
	u_long item;
	songtrans sd;

	DoMethod(data->list, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &item);
	if (!item) return;

	sd = (songtrans)item;

	if (sd->state >= DLS_FIN) {
		return;
	}

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
		if (sd->state == DLS_WAIT) DoMethod(gui->dwin, DL_REMWAITING, sd);
		if (sd->state == DLS_QUEUE) QueueCount--;
	}

	if (sd->t) {
		th_message(sd->t, THC_EXIT, 0);
		Signal(sd->t->task, SIGBREAKF_CTRL_C);
	}
	else {
		if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
			if (sd->state != DLS_QUEUE && sd->state != DLS_WAIT) {
				DoMethod(gui->dwin, DL_COUNTDECREMENT);
			}
		}
		else DoMethod(gui->uwin, UPLOAD_COUNTDECREMENT);
	}

	sd->state = DLS_ABORT;

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
		set(data->BT_Resume, MUIA_Disabled, FALSE);
		DoMethod(gui->dwin, DL_UPDATE, sd);
		prf_event(PRFE_DLABORT, sd->fname);
	}
	else {
		DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
		prf_event(PRFE_ULABORT, sd->fname);
	}
}


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

	set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);
	while (1) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &item);
		if (!item) break;
		if (((songtrans)item)->state >= DLS_FIN) DoMethod(data->list, MUIM_NList_Remove, i);
		else i++;
	}
	set(data->list,MUIA_NList_Quiet, MUIV_NList_Quiet_None);
}


void TransferCleanupSingle(struct TransferData *data, songtrans sd)
{
	long pos;

	pos = MUIV_NList_GetPos_Start;
	DoMethod(data->list, MUIM_NList_GetPos, sd, &pos);
	DoMethod(data->list, MUIM_NList_Remove, pos);
}


void TransferWatcher(struct TransferData *data)
{
	u_long item;
	long i;
	songtrans sd;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &item);
		if (!item) break;
		sd = (songtrans)item;
		if (sd->state == DLS_PREP) {
			if (GetDateStamp()-sd->reqtime > 120) {	/* We consider this transfer dead/hanging (never started) */
				sd->state = DLS_ERROR;
				sd->error = ERROR_TIMEOUT;
				if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
					DoMethod(gui->dwin, DL_COUNTDECREMENT);
					DoMethod(gui->dwin, DL_UPDATE, sd);
					DoMethod(gui->dwin, DL_RESUME_UPDATE);
				}
				else {
					DoMethod(gui->uwin, UPLOAD_COUNTDECREMENT);
					DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
				}
			}
		}
	}
}


void TransferHandleError(songtrans sd)
{ /* Outside the thread */
	char error[128];

	if (sd->ErrorCode == EINTR) return;	/* Aborted with CTRL-C */

	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;
		if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) {
			DoMethod(gui->dwin, DL_RESUME_UPDATE);
			prf_event(PRFE_DLERROR, sd->fname);
		}
		else prf_event(PRFE_ULERROR, sd->fname);
	}
	else if (sd->state == DLS_FIN) {
		if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_DOWNLOAD_IN) prf_event(PRFE_DLFINISH, sd->fname);
		else prf_event(PRFE_ULFINISH, sd->fname);
	}

	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, 49);
		return FALSE;
	}
	sd->nsigm = (1L << (sd->nsig));
	sd->msigm = (1L << (t->port->mp_SigBit));

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

	SocketBase = OpenLibrary("bsdsocket.library", 0);
	if (!SocketBase) {
		ExitTransferThread(sd, 51);
		return FALSE;
	}
	sd->SocketBase = SocketBase;

	SocketBaseTags(SBTM_SETVAL(SBTC_SIGIOMASK), (char *)sd->nsigm, TAG_DONE);

	buffer = malloc(8192);
	if (!buffer) {
		ExitTransferThread(sd, 52);
		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));

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_UPLOAD_OUT) {
		s = socket(AF_INET, SOCK_STREAM, 0);
		if (s < 0) {
			ExitTransferThread(sd, 53);
			return FALSE;
		}
		sd->s = s;
	}
	else {
		/* Firewalled transfer - get the already existing socket */
		s = ObtainSocket(sd->s, AF_INET, SOCK_STREAM, 0);
		sd->s = s;
	}

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

	if (sd->type == TYPE_DOWNLOAD_OUT || sd->type == TYPE_UPLOAD_OUT) {
		sd->sin.sin_addr.s_addr = sd->ip;
		sd->sin.sin_port = htons(sd->port);
		sd->sin.sin_family = AF_INET;
		sd->sin.sin_len = sizeof(sd->sin);

		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);
	}
	else if (sd->type == TYPE_DOWNLOAD_IN) sd->state = DLS_INIT;

	return TRUE;
}


void ExitTransferThread(songtrans sd, int ret)
{ /* Inside the thread */
	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);
}


time_t GetDateStamp(void)
{
	struct DateStamp ds;
	struct DateStamp *rds;

	rds = DateStamp(&ds);
	return (rds->ds_Days*24*60*60 + rds->ds_Minute*60 + rds->ds_Tick/TICKS_PER_SECOND);
}
