/*
** Amster - Upload
** Copyright © 2000-2001 by Jacob Laursen
**
** 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 "amster.h"

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

#include "network.h"

#include <MUI/NListview_mcc.h>
#include "amster_Cat.h"


#define ULC_STATE 0
#define ULC_SIZE 1

int ul_count = 0;

ULONG ul_new(struct IClass *cl, Object *obj, struct opSet *msg);
void ul_cps(struct TransferData *data);


MUI_DISPATCH(ul_dispatch)
{
	struct TransferData *data;

	switch(msg->MethodID) {
		case OM_NEW:
			return(ul_new(cl, obj, (APTR)msg));
		case MUIM_Window_Setup:
			return(dl_setup(cl, obj, (APTR)msg));
		case MUIM_Window_Cleanup:
			return(dl_muicleanup(cl, obj, (APTR)msg));
		case UPLOAD_OPEN:
			set(obj, MUIA_Window_Open, TRUE);
			return NULL;
		case UPLOAD_CLOSE:
			set(obj, MUIA_Window_Open, FALSE);
			return NULL;
		case UPLOAD_UPDATE:
			{
			long pos = MUIV_NList_GetPos_Start;
			data = INST_DATA(cl, obj);
			DoMethod(data->list, MUIM_NList_GetPos, (((muimsg)msg)->arg1), &pos);
			DoMethod(data->list, MUIM_NList_Redraw, pos);
			return NULL;
			}
		case UPLOAD_CPS:
			data = INST_DATA(cl, obj);
			ul_cps(data);
			return NULL;
		case UPLOAD_ADD:
			data = INST_DATA(cl, obj);
			DoMethod(data->list, MUIM_NList_InsertSingle, (songtrans)(((muimsg)msg)->arg1), MUIV_NList_Insert_Bottom);
			return NULL;
		case UPLOAD_START:
			data = INST_DATA(cl, obj);
			ul_startq2(data, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2), (u_long)(((muimsg)msg)->arg3), (int)(((muimsg)msg)->arg4));
			return NULL;
		case UPLOAD_INFO:
			data = INST_DATA(cl, obj);
			TransferInfo(data);
			return NULL;
		case UPLOAD_ABORT:
			data = INST_DATA(cl, obj);
			TransferAbort(data);
			return NULL;
		case UPLOAD_CLEANUP:
			data = INST_DATA(cl, obj);
			TransferCleanup(data);
			return NULL;
		case UPLOAD_CLEANUP_SINGLE:
			data = INST_DATA(cl, obj);
			TransferCleanupSingle(data, (songtrans)(((muimsg)msg)->arg1));
			return NULL;
		case UPLOAD_WATCHER:
			data = INST_DATA(cl, obj);
			TransferWatcher(data);
			return NULL;
		case UPLOAD_COUNTDECREMENT:
			data = INST_DATA(cl, obj);
			if (--ul_count == 0) DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->watchnode);
			return NULL;
		case UPLOAD_COUNTINCREMENT:
			data = INST_DATA(cl, obj);
			if (ul_count++ == 0) DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->watchnode);
			return NULL;
	}
	return DoSuperMethodA(cl, obj, msg);
}


ULONG ul_new(struct IClass *cl, Object *obj, struct opSet *msg)
{
	static struct Hook uplistdispHook = { {0,0}, &translistdisp, NULL, NULL };
	static struct Hook uplistdestHook = { {0,0}, &translistdest, NULL, NULL };
	struct TransferData *data;
	Object *list, *info, *BT_Abort, *BT_Clean;

	if (obj = (Object *)DoSuperNew(cl,obj,
		MUIA_Window_Title, MSG_UPLOAD_TITLE,
		MUIA_Window_ID, MAKE_ID('U','P','L','D'),
		WindowContents, VGroup,
			Child, list = NListviewObject,
				MUIA_NListview_NList, NListObject,
					InputListFrame,
					MUIA_Font, MUIV_Font_Tiny,
					MUIA_NList_Title, TRUE,
					MUIA_NList_Format, "BAR, BAR, BAR, BAR, BAR",
					MUIA_NList_DisplayHook, &uplistdispHook,
					MUIA_NList_DestructHook, &uplistdestHook,
					MUIA_CycleChain, 1,
				End,
			End,
			Child, info = TextObject,
				TextFrame,
				MUIA_Background, MUII_TextBack,
				MUIA_Text_PreParse, "\33c",
			End,
			Child, HGroup,
				Child, BT_Abort = SimpleButton(MSG_UPLOAD_ABORT_GAD),
				Child, BT_Clean = SimpleButton(MSG_UPLOAD_CLEANUP_GAD),
			End,
		End,
		TAG_MORE, msg->ops_AttrList))
	{
		data = INST_DATA(cl,obj);
		data->list = list;
		data->info = info;

		data->ihnode.ihn_Object = obj;
		data->ihnode.ihn_Millis = 1000;
		data->ihnode.ihn_Method = UPLOAD_CPS;
		data->ihnode.ihn_Flags  = MUIIHNF_TIMER;

		data->watchnode.ihn_Object = obj;
		data->watchnode.ihn_Millis = 10000;
		data->watchnode.ihn_Method = UPLOAD_WATCHER;
		data->watchnode.ihn_Flags  = MUIIHNF_TIMER;

		DoMethod(BT_Abort, MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, UPLOAD_ABORT  );
		DoMethod(BT_Clean, MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, UPLOAD_CLEANUP);
		DoMethod(list, MUIM_Notify, MUIA_NList_EntryClick, MUIV_EveryTime, obj, 1, UPLOAD_INFO);
		DoMethod(obj, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, obj, 1, UPLOAD_CLOSE);

		return((ULONG)obj);
	}
	return(0);
}


void ul_cps(struct TransferData *data)
{
	songtrans sd;
	u_long item;
	int i;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &item);
		if (!item) return;
		sd = (songtrans)item;
		if (sd->state == DLS_UP) {
			CalculateCps(sd);
			DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
		}
	}
}


void ul_addq(song s)
/* This is the initial function, called from share.c */
{
	songtrans sd;
	char *path;

#ifdef AMSTER_DEBUG
gui_debugf("ul_addq() - upload count: %d, queue limit: %d", ul_count, prf->UploadQueueLimit);
#endif

	path = MakeWinPath(s->title);
	if (ul_count < prf->UploadQueueLimit) {
		sprintf(nap_buf, "%s \"%s\"", s->user, path);
		free(path);
		nap_send(NAPC_UPLOADACCEPT);
	}
	else {
		sprintf(nap_buf, "%s \"%s\" %d", s->user, path, prf->UploadQueueLimit);
		free(path);
		nap_send(NAPC_LOCALQUEUEFULL);
#ifdef AMSTER_DEBUG
gui_debugf("Queue limit reached! (%d)", prf->UploadQueueLimit);
#endif
		return;
	}

	sd = malloc(sizeof(_songtrans));
	if (!sd) return;
	memset(sd, 0, sizeof(_songtrans));

	sd->song = nap_songdup(s);
	if (!sd->song) {
		free(sd);
		return;
	}

	sd->size = s->size;
	sd->mynick = prf->user;
	sd->type = TYPE_UPLOAD_OUT;
	sd->reqtime = GetDateStamp();

	prf_event(PRFE_ULSTART, s->title);

	DoMethod(gui->uwin, UPLOAD_COUNTINCREMENT);

#ifdef AMSTER_DEBUG
gui_debugf("upload count: %d", ul_count);
#endif

	DoMethod(gui->uwin, UPLOAD_ADD, sd);
}


void ul_startq(char *title, char *user, u_long ip, int port, int link)
/* This is the initial function, when the actual upload is about to
   take place (acknowledged by server) - called from napster.c */
{
	DoMethod(gui->uwin, UPLOAD_START, title, user, ip, port);
}


void ul_startq2(struct TransferData *data, char *title, char *user, u_long ip, int port)
/* ul_startq() continued (to get TransferData struct) */
{
	u_long tmp;
	songtrans sd;
	long i;
	char *path;

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

	if (sd->t) return;	/* Already in a thread */

#ifdef AMSTER_DEBUG
gui_debugf("ul-ack [%s], port: %d, ip: %ld\n",title,port,ip);
#endif

	/* We haven't filled out sd->song completely yet, since not much
	   information was available when the request was made */
	sd->song->ip = ip;

	/* Why do we need the IP twice? Check and use only one of them! */
	sd->ip = ip;
	sd->port = port;

	sd->t = th_spawn(ul_handlemsg, "Amster uploader", &UploadThread, prf->UploadTaskPri, sd);
	if (!sd->t) {
		sd->state = DLS_ERROR;
		DoMethod(gui->uwin, DL_UPDATE, sd);
	}
}


void ul_handlemsg(thread t, int com, APTR data)
{
	songtrans sd=(songtrans)t->data;

	switch(com) {
		case THC_STARTUP:
#ifdef AMSTER_DEBUG
gui_debugf("upload thread start");
#endif
			sd->ts = 1;
			nap_sendbuf(NAPC_ULINC, "");
			break;
		case THC_EXIT:
#ifdef AMSTER_DEBUG
gui_debugf("upload thread exit = %ld", data);
#endif
			sd->error = (int)data;
			if (sd->error == 0) DoMethod(gui->shwin, SHARE_UPDCOUNT, sd->song->title);
			TransferHandleError(sd);
			sd->ts = 0;
			sd->t = NULL;
			DoMethod(gui->uwin, UPLOAD_COUNTDECREMENT);
			nap_sendbuf(NAPC_ULCOMPLETE, "");
			if (sd->state == DLS_FIN && (prf->AutoCleanup == 2 || prf->AutoCleanup == 3)) {
				DoMethod(gui->uwin, UPLOAD_CLEANUP_SINGLE, sd);
				break;
			}
			break;
		case DLC_STATE:
			sd->state = (int)data;
		case DLC_UPDATE:
			DoMethod(gui->uwin, UPLOAD_UPDATE, sd);
			break;
#ifdef AMSTER_DEBUG
		default:
			gui_debugf("upload thread c: %d, d: %ld", com, data);
#endif
	}
}


/* Upload thread */

THREAD(UploadThread)
{
	thread t;
	songtrans sd;
	struct Library *DOSBase;
	struct Library *SocketBase;
	char *buffer, *path;
	long tmp;
	long s;
	thmsg m;
	int len;

	t = thr_init();
	if (!t) return;
	sd = t->data;

	if (!InitTransferThread(t, sd)) return;
	s = sd->s;
	buffer = sd->buffer;
	DOSBase = sd->DOSBase;
	SocketBase = sd->SocketBase;

	while (1) {
		u_long sigs;

		sigs = Wait(sd->nsigm|sd->msigm);
		if (sigs&(sd->msigm)) {
			m = (thmsg)GetMsg(t->port);
			if(m) {
				if (m->com == THC_EXIT) {
					thr_message(t, DLC_STATE, (APTR)DLS_ABORT);
					ExitTransferThread(sd, 0);
					return;
				}
				if (!m->isreply) {
					m->isreply=1;
					ReplyMsg((struct Message *)m);
				}
			}
		}

		if (sigs&(sd->nsigm)) {
			FD_ZERO(&sd->fds);
			FD_SET(s,&sd->fds);
			sd->tv.tv_sec = 0;
			sd->tv.tv_usec = 0;

			switch(sd->state) {
				case DLS_CON:
					if (WaitSelect(s+1,NULL,&sd->fds,NULL,&sd->tv,0) != 1) break;
					{
						sd->state = DLS_INIT;
						tmp = 0;
						IoctlSocket(s, FIONBIO, (char*)&tmp);
						/* Disable non-blocking I/O to the socket */
					}

				case DLS_INIT:
					if (WaitSelect(s+1,&sd->fds,NULL,NULL,&sd->tv,0) != 1) break;
					{
						tmp = recv(s, buffer, 1, 0);
						if (tmp != 1) {
							sd->ErrorCode = Errno();
							ExitTransferThread(sd, ERROR_NET);
							return;
						}
						if (buffer[0] != '1') {
#ifdef AMSTER_DEBUG
gui_debugf("tmp: %ld, buffer[0]: %d", tmp, buffer[0]);
#endif
							ExitTransferThread(sd, 30);
							return;
						}
						thr_message(t, DLC_UPDATE, 0);
						send(s, "SEND", 4, 0);

						path = MakeWinPath(sd->song->title);
						sprintf(buffer, "%s \"%s\" %ld", sd->mynick, path, sd->song->size);
						free(path);
						send(s, buffer, strlen(buffer),0);

						tmp = recv(s, buffer, 32, 0);
						if (tmp < 1) {
							sd->ErrorCode = Errno();
							ExitTransferThread(sd, ERROR_NET);
							return;
						}
						buffer[tmp] = '\0';

						sd->cur = atoi(buffer);
						if (sd->cur == 0 && buffer[0] != '0') {
							if (strcmp(buffer, "FILE NOT REQUESTED") == 0) {
								ExitTransferThread(sd, ERROR_NOTREQUESTED);
								return;
							}
                        	else if (strcmp(buffer, "INVALID REQUEST") == 0) {
								ExitTransferThread(sd, ERROR_INVALIDREQUEST);
								return;
                            }
#ifdef AMSTER_DEBUG
gui_debugf("error (please report this): %s\n", buffer);
#endif
							ExitTransferThread(sd, 31);
							return;
						}

						sd->f = Open(sd->song->title, MODE_OLDFILE);
						if (!sd->f) {
							sd->ErrorCode = IoErr();
							ExitTransferThread(sd, ERROR_FILEOPEN);
							return;
						}

						Seek(sd->f, sd->cur, OFFSET_BEGINNING);
						sd->state = DLS_UP;
						sd->starttime = GetDateStamp();
						sd->resumestart = sd->cur;
						thr_message(t, DLC_UPDATE, 0);
					}

				case DLS_UP:
					if (WaitSelect(s+1,NULL,&sd->fds,NULL,&sd->tv,0) != 1) break;
					while (sd->cur < sd->size) {

						len = Read(sd->f, buffer, 4096);
						if (len == -1) {	/* Error */
							sd->ErrorCode = IoErr();
							ExitTransferThread(sd, ERROR_FILEREAD);
							return;
						}
						if (len != 0) {
							tmp = send(s, buffer, len, 0);
							if (tmp == -1) {
								sd->ErrorCode = Errno();
								ExitTransferThread(sd, ERROR_NET);
								return;
							}
							if (tmp != len) {
#ifdef AMSTER_DEBUG
gui_debugf("WARNING (please notify Laursen): all data from Read() was not send() - %ld/%ld.\n", len, tmp);
#endif
							}
							sd->cur += len;
						}

						if (len < 4096 && sd->cur < sd->size) {
							/* Read buffer wasn't filled and file is not finished */
							thr_message(t, DLC_STATE, (APTR)DLS_ERROR);
							ExitTransferThread(sd, 33);
							return;
						}

						m = (thmsg)GetMsg(t->port);
						if (m) {
							if (m->com == THC_EXIT) {
								thr_message(t, DLC_STATE, (APTR)DLS_ABORT);
								ExitTransferThread(sd, 0);
								return;
							}
							if (!m->isreply) {
								m->isreply=1;
								ReplyMsg((struct Message *)m);
							}
						}

						FD_ZERO(&sd->fds);
						FD_SET(s,&sd->fds);
					}

					thr_message(t, DLC_STATE, (APTR)DLS_FIN);
					ExitTransferThread(sd, 0);
					return;

			}
		}
	}

	ExitTransferThread(sd, 0);
}
