/*
** File sharing
*/

#include "include/config.h"

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

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

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

#include <libraries/asl.h>
#include <libraries/dos.h>

#include <exec/memory.h>
#include <dos/exall.h>

#include "md5.h"
#include "include/gui.h"
#include "include/info.h"
#include "include/share.h"
#include "include/prefs.h"
#include "include/panel.h"
#include "include/napster.h"
#include "amster_Cat.h"


int numsongs=0, numbytes=0;
char sharefile[512], sharepath[512];


MUIF share_dispatch(REG(a0) struct IClass *cl, REG(a2) Object *obj, REG(a1) Msg msg)
{
	struct shdata *data = INST_DATA(cl, obj);

	switch(msg->MethodID) {
		case OM_NEW:
			strcpy(sharefile, "PROGDIR:Amster.default");
			/* This dublication is premature, since prefs isn't loaded at this time */
			return(share_new(cl,obj, (APTR)msg));
		case SHARE_OPEN:
			update_stat(data);
			set(obj, MUIA_Window_Open, TRUE);
			return(NULL);
		case SHARE_CLOSE:
			set(obj, MUIA_Window_Open, FALSE);
			return(NULL);
		case SHARE_ADD:
			add_shares(data);
			return(NULL);
		case SHARE_REMOVE:
			remove_shares(data, (long)(((muimsg)msg)->arg1));
			return(NULL);
		case SHARE_SAVE:
			save_shares(data);
			return(NULL);
		case SHARE_SAVEAS:
			save_shares_as(data);
			return(NULL);
		case SHARE_LOAD:
			load_shares(data);
			return(NULL);
		case SHARE_LOADAS:
			load_shares_as(data);
			return(NULL);
		case SHARE_UPLOAD:
			upload_file(data, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2));
			return(NULL);
		case SHARE_NOTIFYALL:
			notify_shares(data);
			return(NULL);
	}
	return(DoSuperMethodA(cl, obj, msg));
}


MUIF sharelistdest(REG(a2) APTR pool, REG(a1) sharedata sd)
{
	numsongs--;
	numbytes -= sd->size;
	if(gui_napon) nap_sendbuf(NAPC_REMOVESHARE, sd->title);
	free(sd);

	return(0);
}


MUIF sharelistdisp(REG(a2) char **array, REG(a1) sharedata sd)
{
	static char size[16], md5[33], time[20], bitrate[16], freq[16];

	if (sd) {
			*array++ = sd->title;
			sprintf(size, "\33r%ld", sd->size);
			*array++ = size;
			sprintf(time, "\33r%ld:%02ld", sd->time/60, sd->time%60);
			*array++ = time;
			sprintf(bitrate, "\33r%ld", sd->bitrate);
			*array++ = bitrate;
			sprintf(freq, "\33r%ld", sd->freq);
			*array++ = freq;
			strcpy(md5, sd->md5);
			*array = md5;
	} else {
		*array++ = (char *)MSG_LH_FILE;
		*array++ = (char *)MSG_LH_SIZE;
		*array++ = (char *)MSG_LH_TIME;
		*array++ = (char *)MSG_LH_BITRATE;
		*array++ = (char *)MSG_LH_FREQ;
		*array   = (char *)MSG_LH_CHECKSUM;
	}
	return(0);
}


ULONG share_new(struct IClass *cl, Object *obj, struct opSet *msg)
{
	static struct Hook sharelistdispHook = { {0,0}, &sharelistdisp, NULL, NULL };
	static struct Hook sharelistdestHook = { {0,0}, &sharelistdest, NULL, NULL };
	struct shdata *data;
	Object *list, *stat, *addbut, *rembut, *remallbut;

	if (obj = (Object *)DoSuperNew(cl,obj,
		WindowContents, VGroup,
			MUIA_HelpNode, "sharing",
			Child, stat = TextObject,
				TextFrame,
				MUIA_Background, MUII_TextBack,
				MUIA_Text_PreParse, "\33c",
			End,
			Child, list = NListviewObject,
				MUIA_NListview_NList, NListObject,
					InputListFrame,
					MUIA_NList_Title, TRUE,
					MUIA_NList_Format, "BAR, BAR, BAR, BAR, BAR, BAR",
					MUIA_NList_DisplayHook, &sharelistdispHook,
					MUIA_NList_DestructHook, &sharelistdestHook,
					MUIA_NList_MultiSelect, MUIV_NList_MultiSelect_Default,
					MUIA_CycleChain, 1,
				End,
			End,
			Child, HGroup,
				Child, addbut    = SimpleButton(MSG_SHARE_ADD_GAD      ),
				Child, rembut    = SimpleButton(MSG_SHARE_REMOVE_GAD   ),
				Child, remallbut = SimpleButton(MSG_SHARE_REMOVEALL_GAD),
			End,
		End,
		TAG_MORE, msg->ops_AttrList))
	{
		data = INST_DATA(cl,obj);
		data->list = list;
		data->stat = stat;

		set(obj, MUIA_Window_ID, MAKE_ID('S','H','A','R'));
		set(obj, MUIA_Window_Title, MSG_SHARE_TITLE),

		DoMethod(obj, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, gui->iconpanel, 1, PANEL_CLOSESHARE);

		DoMethod(addbut,    MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, SHARE_ADD      );
		DoMethod(rembut,    MUIM_Notify, MUIA_Pressed, FALSE, obj, 2, SHARE_REMOVE, 0);
		DoMethod(remallbut, MUIM_Notify, MUIA_Pressed, FALSE, obj, 2, SHARE_REMOVE, 1);

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


/* Private functions */


void add_shares(struct shdata *data)
{
	sharedata sd;
	BPTR lock;
	struct FileInfoBlock *fib;
	struct FileRequester *freq;
	int i;

	freq = AllocAslRequestTags(ASL_FileRequest, TAG_DONE);
	if (freq != NULL) {
		if (AslRequestTags(freq,
			ASLFR_TitleText, MSG_SHARE_ADD_REQ,
			ASLFR_InitialPattern, "#?.mp3",
			ASLFR_InitialDrawer, sharepath,
			ASLFR_DoPatterns, TRUE,
			ASLFR_DoMultiSelect, TRUE,
			ASLFR_RejectIcons, TRUE,
			TAG_DONE)) {
				strcpy(sharepath, freq->fr_Drawer);
				if (fib = AllocMem(sizeof(*fib), MEMF_PUBLIC)) {
					for (i=0; i<freq->fr_NumArgs; i++) {
						if (sd = malloc(sizeof(_sharedata))) {
							memset(sd, 0, sizeof(_sharedata));
							strcpy(sd->title, freq->fr_Drawer);
							AddPart(sd->title, freq->fr_ArgList[i].wa_Name, 255);
							if (lock = Lock(sd->title, ACCESS_READ)) {
								if (Examine(lock, fib)) {
									sd->size = fib->fib_Size;
									if (NameFromLock(lock, sd->title, 255)) {
										if (fib->fib_DirEntryType < 0) add_shareinfo(data, sd, lock);
										else if (fib->fib_DirEntryType > 0) add_recursive(data, sd, lock, sd->title);
									}
									else UnLock(lock);
								}
							}
						}
					}
					FreeMem(fib, sizeof(*fib));
				}
		}
		FreeAslRequest(freq);
	}
}


void add_recursive(struct shdata *data, sharedata sd, BPTR dirlock, const char *dirname)
{
	ULONG namelen;
	long len;
	struct ExAllControl *eac;
	struct ExAllData *ead;
	APTR eadata;
	BOOL more;
	char *name;
	BPTR lock;
	struct FileInfoBlock *fib;
	const long buffersize = 4096;

	if (dirlock) {
		namelen = strlen(dirname);
		eac = AllocDosObject(DOS_EXALLCONTROL, NULL);
		if (eac) {
			eac->eac_LastKey = 0;
			eac->eac_MatchString = 0;
			eac->eac_MatchFunc = 0;
			eadata = malloc(buffersize);
			if (eadata) {
				do {
					more = ExAll(dirlock, eadata, buffersize, ED_TYPE, eac);
					if (eac->eac_Entries > 0) {
						ead = eadata;
						do {
							len = namelen + strlen(ead->ed_Name);
							name = (char *)malloc(len+5);
							if (name) {
								strcpy(name, dirname);
								if (AddPart(name, ead->ed_Name, len+5)) {
									if (ead->ed_Type > 0) {
										lock = Lock(name, ACCESS_READ);
										if (lock) {
											add_recursive(data, sd, lock, name);
											UnLock(lock);
                                        }
                                    }
									else if (ead->ed_Type < 0) {
										if (sd = malloc(sizeof(_sharedata))) {
											memset(sd, 0, sizeof(_sharedata));
											strcpy(sd->title, name);
											if (fib = AllocMem(sizeof(*fib), MEMF_PUBLIC)) {
												lock = Lock(name, ACCESS_READ);
												if (lock) {
													if (Examine(lock, fib)) {
														sd->size = fib->fib_Size;
														add_shareinfo(data, sd, lock);
													}
												}
											}
											FreeMem(fib, sizeof(*fib));
										}
									}
								}
								free(name);
							}
							ead = ead->ed_Next;
						} while(ead);
					}
					if ((!more) && (IoErr() != ERROR_NO_MORE_ENTRIES)) break;
					if (eac->eac_Entries == 0) continue;
				} while (more);
				free(eadata);
			}
			FreeDosObject(DOS_EXALLCONTROL, eac);
		}
	}
}


void add_shareinfo(struct shdata *data, sharedata sd, BPTR lock)
{
	unsigned int m_id, m_layer, m_bit, m_freq;
	APTR buffer;
	BPTR fh;
	int i, len, col = 0;

	md5_state_t state;
	md5_byte_t digest[16];
	int di;

	int mp3_bitrates[14][6] = {
		{  32,  32,  32,  32, 32,    8 },
		{  64,  48,  40,  64, 48,   16 },
		{  96,  56,  48,  96, 56,   24 },
		{ 128,  64,  56, 128, 64,   32 },
		{ 160,  80,  64, 160, 80,   64 },
		{ 192,  96,  80, 192, 96,   80 },
		{ 224, 112,  96, 224, 112,  56 },
		{ 256, 128, 112, 256, 128,  64 },
		{ 288, 160, 128, 288, 160, 128 },
		{ 320, 192, 160, 320, 192, 160 },
		{ 352, 224, 192, 352, 224, 112 },
		{ 384, 256, 224, 384, 256, 128 },
		{ 416, 320, 256, 416, 320, 256 },
		{ 448, 384, 320, 448, 384, 320 }
	};

	if (fh = OpenFromLock(lock)) {
		if (buffer = AllocMem(300032, MEMF_ANY)) {

			md5_init(&state);

			len = Read(fh, buffer, 300032);
			if (len > 0) {

				md5_append(&state, (const md5_byte_t *)buffer, len);
				md5_finish(&state, digest);
				for (di = 0; di < 16; ++di)
					sprintf(sd->md5+di*2, "%02x", digest[di]);

				for (i=0; i<len; i++) {
					if (*((unsigned char *)buffer+i) == 255)
						if (*((unsigned char *)buffer+i+1) >= 224) break;
						/* We seek until synchronization is found (%11111111.111xxxxx) */
				}
				if (i < len-1) {

					/* MPEG2-III unsupported! */

					m_id    = (*((char *)buffer+i+1) & 8)>>3;
					m_layer = (*((char *)buffer+i+1) & 6)>>1;
					m_bit   = (*((char *)buffer+i+2) & (16+32+64+128))>>4;
					m_freq  = (*((char *)buffer+i+2) & 12)>>2;

					switch(m_layer) {
						case 1: col=2; break;
						case 2: col=1; break;
						case 3: col=0; break;
					}
					if(m_id==0) col=col+3;

					switch(m_freq) {
						case 0:
							if(m_id==0) sd->freq=22050; else sd->freq=44100;
							break;
						case 1:
							if(m_id==0) sd->freq=24000; else sd->freq=48000;
							break;
						case 2:
							if(m_id==0) sd->freq=16000; else sd->freq=32000;
							break;
					}

					if(m_bit>0 & m_bit<15) { /* In case of an error */
						sd->bitrate = mp3_bitrates[m_bit-1][col];
						sd->time = (sd->size-i)/(sd->bitrate*125);
						/* 'i' is the length of ID3v2 tag. ID3v1 size is always 128 bytes,
						   so it's not worth the overhead to check for this.
						   128 bytes of an MP3 in 128.000 kbps = 0.000008 seconds! */
					}

				}
			}
			FreeMem(buffer, 300032);
		}
		Close(fh);
		if (sd->size > 0 & sd->bitrate > 0 & sd->freq > 0) {
			DoMethod(data->list, MUIM_NList_InsertSingle, sd, MUIV_NList_Insert_Bottom);
			numsongs++; numbytes += sd->size;
			update_stat(data);
			if(gui_napon) nap_notifyshare(sd);
		}
		else gui_debugf((char *)MSG_SHARE_ADDERROR, sd->title, sd->size, sd->bitrate, sd->freq);
	}
	else UnLock(lock);

}


void notify_shares(struct shdata *data)
{
	sharedata sd;
	int i;

	if(!gui_napon) return;

	for(i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (!sd) break;
		nap_notifyshare(sd);
	}
}


void remove_shares(struct shdata *data, long t)
{
	sharedata sd;

	switch(t) {
		case 0: /* Remove marked */
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);
			DoMethod(data->list, MUIM_NList_Remove, MUIV_NList_Remove_Selected);
			update_stat(data);
			set(data->list,MUIA_NList_Quiet, MUIV_NList_Quiet_None);
			break;
		case 1: /* Remove all */
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);
			for(;;) {
				DoMethod(data->list, MUIM_NList_GetEntry, 0, &sd);
				if(!sd) break;
				DoMethod(data->list, MUIM_NList_Remove, MUIV_NList_Remove_First);
			}
			update_stat(data);
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_None);
			break;
	}
}


void save_shares(struct shdata *data)
{
	sharedata sd;
	int i;
	BPTR fh;
	char buf[512];

	fh = Open(sharefile, MODE_NEWFILE);
	if (!fh) return;

	for(i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (!sd) break;
		sprintf(buf, "\"%s\" %s %ld %ld %ld %ld\n", sd->title, sd->md5, sd->size, sd->bitrate, sd->freq, sd->time);
		Write(fh, buf, strlen(buf)); /* Should be buffered, check FWrite */
	}

	Close(fh);
}


void save_shares_as(struct shdata *data)
{
	struct FileRequester *freq;

	freq = AllocAslRequestTags(ASL_FileRequest, TAG_DONE);
	if (freq != NULL) {
		if (AslRequestTags(freq,
			ASLFR_TitleText, MSG_SHARE_SAVEAS,
			ASLFR_InitialDrawer, "PROGDIR:",
			ASLFR_DoSaveMode, TRUE,
			ASLFR_RejectIcons, TRUE,
			TAG_DONE)) {
					strcpy(sharefile, freq->fr_Drawer);
					AddPart(sharefile, freq->fr_File, 511);
					save_shares(data);
		}
		FreeAslRequest(freq);
	}
}


void load_shares(struct shdata *data)
{
	sharedata sd;
	int bufsize = 0, line = 0;
	BPTR fh, lock;
	char buf[512];
	struct FileInfoBlock *fib;
	LONG argarray[] = { NULL, NULL, NULL, NULL, NULL, NULL };
	UBYTE *argstr = "PATH/A,MD5/A,SIZE/A/N,BITRATE/A/N,FREQUENCY/A/N,TIME/A/N";
	struct RDArgs *rdargs;

	remove_shares(data, 1);

	if (!(lock = Lock(sharefile, ACCESS_READ))) return;

	if (fib = AllocMem(sizeof(*fib), MEMF_PUBLIC)) {
			if (Examine(lock, fib)) bufsize = fib->fib_Size;
			FreeMem(fib, sizeof(*fib));
	}

	if (bufsize > 0) {
		if (fh = OpenFromLock(lock)) {

			set(data->list,MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);

			while (FGets(fh, buf, 512)) {
				line++;
				if (rdargs = AllocDosObject(DOS_RDARGS, NULL)) {
					rdargs->RDA_Buffer = NULL;
					rdargs->RDA_Source.CS_Buffer = buf;
					rdargs->RDA_Source.CS_Length = strlen(buf);
					if (ReadArgs(argstr, argarray, rdargs)) {

						sd = malloc(sizeof(_sharedata));

						strcpy(sd->title, (char *)argarray[ ARG_PATH    ]);
						strcpy(sd->md5,   (char *)argarray[ ARG_MD5     ]);
						sd->size    =   *((long *)argarray[ ARG_SIZE    ]);
						sd->bitrate =   *((long *)argarray[ ARG_BITRATE ]);
						sd->freq    =   *((long *)argarray[ ARG_FREQ    ]);
						sd->time    =   *((long *)argarray[ ARG_TIME    ]);

						DoMethod(data->list, MUIM_NList_InsertSingle, sd, MUIV_NList_Insert_Bottom);
						numsongs++; numbytes += sd->size;
						if(gui_napon) nap_notifyshare(sd);

						FreeArgs(rdargs);
					}
					else gui_debugf("Error parsing '%s' line #%ld\n", sharefile, line);
					FreeDosObject(DOS_RDARGS, rdargs);
				}
			}

			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_None);
			update_stat(data);

			Close(fh);
		}
	}
	UnLock(lock);

}


void load_shares_as(struct shdata *data)
{
	struct FileRequester *freq;

	freq = AllocAslRequestTags(ASL_FileRequest, TAG_DONE);
	if (freq != NULL) {
		if (AslRequestTags(freq,
			ASLFR_TitleText, MSG_SHARE_LOADAS,
			ASLFR_InitialDrawer, "PROGDIR:",
			ASLFR_RejectIcons, TRUE,
			TAG_DONE)) {
					strcpy(sharefile, freq->fr_Drawer);
					AddPart(sharefile, freq->fr_File, 511);
					load_shares(data);
		}
		FreeAslRequest(freq);
	}
}


void update_stat(struct shdata *data)
{
	static char buf[100];
	char descriptor[16];
	int number;

	if (numbytes >= (1024*1024*1024)) {
		strcpy(descriptor, MSG_SHARE_STAT_GB);
		number = numbytes/(1024*1024*1024);
	}
	else if (numbytes >= (1024*1024)) {
		strcpy(descriptor, MSG_SHARE_STAT_MB);
		number = numbytes/(1024*1024);
	}
	else {
		strcpy(descriptor, MSG_SHARE_STAT_KB);
		number = numbytes/1024;
	}

	sprintf(buf, MSG_SHARE_STAT, numsongs, number, descriptor);
	set(data->stat, MUIA_Text_Contents, buf);
}


void upload_req(char *user, char *fname)
{
	DoMethod(gui->shwin, SHARE_UPLOAD, user, fname);
}


void upload_file(struct shdata *data, char *user, char *fname)
{
	sharedata sd;
	int i=0, total;
	ULONG tmp;
	BOOL found = FALSE;

	GetAttr(MUIA_NList_Entries, data->list, &tmp);
	total = tmp;

	gui_debugf("User '%s' requested file '%s'.\n", user, fname);

	while(i<total && !found) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (sd) if (strcmp(sd->title, fname) == 0) found = TRUE;
		i++;
	}

	if (!found) {
		gui_debug("Requested file was not found in sharing database.\n");
		return;
	}

	sprintf(nap_buf, "%s \"%s\"", user, fname);
	nap_send(NAPC_UPLOADACCEPT);
}


void nap_notifyshare(sharedata sd)
{
	/* "<filename>" <md5> <size> <bitrate> <frequency> <time> */

	sprintf(nap_buf, "\"%s\" %s %ld %ld %ld %ld", sd->title, sd->md5, sd->size, sd->bitrate, sd->freq, sd->time);
	nap_send(NAPC_NOTIFYSHARE);
}
