#include "quakedef.h"

#ifdef WEBCLIENT
#define DOWNLOADMENU
#endif

#ifdef DOWNLOADMENU

#define ROOTDOWNLOADABLESSOURCE "http://fteqw.sourceforge.net/downloadables.txt"
#define INSTALLEDFILES	"installed.lst"	//the file that resides in the quakedir (saying what's installed).

#define DPF_HAVEAVERSION	1	//any old version
#define DPF_WANTTOINSTALL	2	//user selected it
#define DPF_DISPLAYVERSION	4	//some sort of conflict, the package is listed twice, so show versions so the user knows what's old.
#define DPF_DELETEONUNINSTALL 8	//for previously installed packages, remove them from the list
#define DPF_DOWNLOADING 16
#define DPF_ENQUED	32


int dlcount=1;

//note: these are allocated for the life of the exe
char *downloadablelist[256] = {
	ROOTDOWNLOADABLESSOURCE
};
char *downloadablelistnameprefix[256] = {
	""
};
char downloadablelistreceived[256];	//well
int numdownloadablelists = 1;

typedef struct package_s {
	char fullname[256];
	char *name;

	char src[256];
	char dest[64];
	char gamedir[16];
	unsigned int version;	//integral.

	int dlnum;

	int flags;
	struct package_s *next;
} package_t;

typedef struct {
	menucustom_t *list;
	char intermediatefilename[MAX_QPATH];
	char pathprefix[MAX_QPATH];
	int parsedsourcenum;
	qboolean populated;
} dlmenu_t;

package_t *availablepackages;
int numpackages;

static package_t *BuildPackageList(vfsfile_t *f, int flags, char *prefix)
{
	char line[1024];
	package_t *p;
	package_t *first = NULL;
	char *sl;

	int version;

	do
	{
		if (!VFS_GETS(f, line, sizeof(line)-1))
			break;
		while((sl=strchr(line, '\n')))
			*sl = '\0';
		while((sl=strchr(line, '\r')))
			*sl = '\0';
		Cmd_TokenizeString (line, false, false);
	} while (!Cmd_Argc());

	if (strcmp(Cmd_Argv(0), "version"))
		return NULL;	//it's not the right format.

	version = atoi(Cmd_Argv(1));
	if (version != 0 && version != 1)
	{
		Con_Printf("Packagelist is of a future or incompatable version\n");
		return NULL;	//it's not the right version.
	}

	while(1)
	{
		if (!VFS_GETS(f, line, sizeof(line)-1))
			break;
		while((sl=strchr(line, '\n')))
			*sl = '\0';
		while((sl=strchr(line, '\r')))
			*sl = '\0';
		Cmd_TokenizeString (line, false, false);
		if (Cmd_Argc())
		{
			if (!strcmp(Cmd_Argv(0), "sublist"))
			{
				int i;
				sl = Cmd_Argv(1);

				for (i = 0; i < numdownloadablelists; i++)
				{
					if (!strcmp(downloadablelist[i], sl))
						break;
				}
				if (i == numdownloadablelists && i != 256)
				{
					downloadablelist[i] = BZ_Malloc(strlen(sl)+1);
					strcpy(downloadablelist[i], sl);

					if (*prefix)
						sl = va("%s/%s", prefix, Cmd_Argv(2));
					else
						sl = Cmd_Argv(2);
					downloadablelistnameprefix[i] = BZ_Malloc(strlen(sl)+1);
					strcpy(downloadablelistnameprefix[i], sl);

					numdownloadablelists++;

					i++;
				}
				continue;
			}

			if (Cmd_Argc() > 5 || Cmd_Argc() < 3)
			{
				Con_Printf("Package list is bad - %s\n", line);
				continue;	//but try the next line away
			}

			p = BZ_Malloc(sizeof(*p));

			if (*prefix)
				Q_strncpyz(p->fullname, va("%s/%s", prefix, Cmd_Argv(0)), sizeof(p->fullname));
			else
				Q_strncpyz(p->fullname, Cmd_Argv(0), sizeof(p->fullname));
			p->name = p->fullname;
			while((sl = strchr(p->name, '/')))
				p->name = sl+1;

			Q_strncpyz(p->src, Cmd_Argv(1), sizeof(p->src));
			Q_strncpyz(p->dest, Cmd_Argv(2), sizeof(p->dest));
			p->version = atoi(Cmd_Argv(3));
			Q_strncpyz(p->gamedir, Cmd_Argv(4), sizeof(p->gamedir));
			if (!*p->gamedir)
				strcpy(p->gamedir, "id1");

			p->flags = flags;

			p->next = first;
			first = p;
		}
	}

	return first;
}

static void WriteInstalledPackages(void)
{
	char *s;
	package_t *p;
	vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "wb", FS_BASE);
	if (!f)
	{
		Con_Printf("menu_download: Can't update installed list\n");
		return;
	}

	s = "version 1\n";
	VFS_WRITE(f, s, strlen(s));
	for (p = availablepackages; p ; p=p->next)
	{
		if (p->flags & DPF_HAVEAVERSION)
		{
			s = va("\"%s\" \"%s\" \"%s\" %i \"%s\"\n", p->fullname, p->src, p->dest, p->version, p->gamedir);
			VFS_WRITE(f, s, strlen(s));
		}
	}

	VFS_CLOSE(f);
}

static qboolean ComparePackages(package_t **l, package_t *p)
{
	int v = strcmp((*l)->fullname, p->fullname);
	if (v < 0)
	{
		p->next = (*l);
		(*l) = p;
		return true;
	}
	else if (v == 0)
	{
		if (p->version == (*l)->version)
		if (!strcmp((*l)->dest, p->dest))
		{ /*package matches, free, don't add*/
			strcpy((*l)->src, p->src);	//use the source of the new package (existing packages are read FIRST)
			(*l)->flags |= p->flags;
			(*l)->flags &= ~DPF_DELETEONUNINSTALL;
			BZ_Free(p);
			return true;
		}

		p->flags |= DPF_DISPLAYVERSION;
		(*l)->flags |= DPF_DISPLAYVERSION;
	}
	return false;
}

static void InsertPackage(package_t **l, package_t *p)
{
	package_t *lp;
	if (!*l)	//there IS no list.
	{
		*l = p;
		p->next = NULL;
		return;
	}
	if (ComparePackages(l, p))
		return;
	for (lp = *l; lp->next; lp=lp->next)
	{
		if (ComparePackages(&lp->next, p))
			return;
	}
	lp->next = p;
	p->next = NULL;
}
static void ConcatPackageLists(package_t *l2)
{
	package_t *n;
	while(l2)
	{
		n = l2->next;
		InsertPackage(&availablepackages, l2);
		l2 = n;

		numpackages++;
	}
}

static void dlnotification(char *localfile, qboolean sucess)
{
	int i;
	vfsfile_t *f;
	COM_RefreshFSCache_f();

	i = atoi(localfile+7);

	f = FS_OpenVFS (localfile, "rb", FS_GAME);
	if (f)
	{
		downloadablelistreceived[i] = 1;
		ConcatPackageLists(BuildPackageList(f, 0, downloadablelistnameprefix[i]));
		VFS_CLOSE(f);
	}
	else
		downloadablelistreceived[i] = -1;
}

static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m)
{
	package_t *p;
	p = c->data;
	if (p)
	{
		Draw_Character (x, y, 128);
		Draw_Character (x+8, y, 130);
		Draw_Character (x+16, y, 128);
		Draw_Character (x+24, y, 130);

		if (p->flags&DPF_WANTTOINSTALL)
			Draw_Character (x+4, y, 131);
		else
			Draw_Character (x+4, y, 129);

		//if you have it already
		if (p->flags&(DPF_HAVEAVERSION | ((((int)(realtime*4))&1)?(DPF_DOWNLOADING|DPF_ENQUED):0) ))
			Draw_Character (x+20, y, 131);
		else
			Draw_Character (x+20, y, 129);


		if (&m->selecteditem->common == &c->common)
			Draw_Alt_String (x+48, y, p->name);
		else
			Draw_String(x+48, y, p->name);

		if (p->flags & DPF_DISPLAYVERSION)
		{
			Draw_String(x+48+strlen(p->name)*8, y, va(" (%i.%i)", p->version/1000, p->version%1000));
		}
	}
}
static qboolean MD_Key (struct menucustom_s *c, struct menu_s *m, int key)
{
	package_t *p, *p2;
	p = c->data;
	if (key == K_ENTER || key == K_MOUSE1)
	{
		p->flags ^= DPF_WANTTOINSTALL;

		if (p->flags&DPF_WANTTOINSTALL)
		{
			for (p2 = availablepackages; p2; p2 = p2->next)
			{
				if (p == p2)
					continue;
				if (!strcmp(p->dest, p2->dest))
					p2->flags &= ~DPF_WANTTOINSTALL;
			}
		}
		else
			p->flags &= ~DPF_ENQUED;
		return true;
	}

	return false;
}

qboolean MD_PopMenu (union menuoption_s *mo,struct menu_s *m,int key)
{
	if (key == K_ENTER || key == K_MOUSE1)
	{
		M_RemoveMenu(m);
		return true;
	}
	return false;
}

static void Menu_Download_Got(char *fname, qboolean successful);
qboolean MD_ApplyDownloads (union menuoption_s *mo,struct menu_s *m,int key)
{
	if (key == K_ENTER || key == K_MOUSE1)
	{
		char *temp;
		package_t *last = NULL, *p;

		for (p = availablepackages; p ; p=p->next)
		{
			if (!(p->flags&DPF_WANTTOINSTALL) && (p->flags&DPF_HAVEAVERSION))
			{	//if we don't want it but we have it anyway:
				if (*p->gamedir)
				{
					char *fname = va("%s", p->gamedir, p->dest);
					FS_Remove(fname, FS_BASE);
				}
				else
					FS_Remove(p->dest, FS_GAME);
				p->flags&=~DPF_HAVEAVERSION;	//FIXME: This is error prone.

				WriteInstalledPackages();

				if (p->flags & DPF_DELETEONUNINSTALL)
				{
					if (last)
						last->next = p->next;
					else
						availablepackages = p->next;

				//	BZ_Free(p);

					return true;
				}
			}
			last = p;
		}

		for (p = availablepackages; p ; p=p->next)
		{
			if ((p->flags&DPF_WANTTOINSTALL) && !(p->flags&(DPF_HAVEAVERSION|DPF_DOWNLOADING)))
			{	//if we want it and don't have it:
				p->dlnum = dlcount++;
				temp = va("dl_%i.tmp", p->dlnum);
				Con_Printf("Downloading %s (to %s)\n", p->fullname, temp);
				p->flags|=DPF_DOWNLOADING;
				if (!HTTP_CL_Get(p->src, temp, Menu_Download_Got))
					p->flags&=~DPF_DOWNLOADING;
			}
		}
		return true;
	}
	return false;
}

void M_AddItemsToDownloadMenu(menu_t *m)
{
	char path[MAX_QPATH];
	int y;
	package_t *p;
	menucustom_t *c;
	char *slash;
	menuoption_t *mo;
	dlmenu_t *info = m->data;
	int prefixlen;
	p = availablepackages;

	MC_AddRedText(m, 4, 40, "W H      (want, have)", false);

	prefixlen = strlen(info->pathprefix);
	y = 48+4;
	for (p = availablepackages; p; p = p->next)
	{
		if (strncmp(p->fullname, info->pathprefix, prefixlen))
			continue;
		slash = strchr(p->fullname+prefixlen, '/');
		if (slash)
		{
			Q_strncpyz(path, p->fullname, MAX_QPATH);
			slash = strchr(path+prefixlen, '/');
			if (slash)
				*slash = '\0';
			
			for (mo = m->options; mo; mo = mo->common.next)
				if (mo->common.type == mt_button)
					if (!strcmp(mo->button.text, path + prefixlen))
						break;
			if (!mo)
			{
				MC_AddConsoleCommand(m, 6*8, y, path+prefixlen, va("menu_download \"%s/\"", path));
				y += 8;
			}
		}
		else
		{
			c = MC_AddCustom(m, 0, y, p);
			c->draw = MD_Draw;
			c->key = MD_Key;
			c->common.width = 320;
			c->common.height = 8;
			y += 8;
		}
	}

	y+=4;
	MC_AddCommand(m, 0, y, "      Back", MD_PopMenu);
	y+=8;
	MC_AddCommand(m, 0, y, "      Apply", MD_ApplyDownloads);
/*
	for (pn = 1, p = availablepackages; p && pn < info->firstpackagenum ; p=p->next, pn++)

	m->

		if (lastpathlen != p->name - p->fullname || strncmp(p->fullname, lastpath, lastpathlen))
		{
			lastpathlen = p->name - p->fullname;
			lastpath = p->fullname;


			if (!lastpathlen)
				Draw_FunStringLen(x+40, y, "/", 1);
			else
				Draw_FunStringLen(x+40, y, p->fullname, lastpathlen);
			y+=8;
		}
		Draw_Character (x, y, 128);
		Draw_Character (x+8, y, 130);
		Draw_Character (x+16, y, 128);
		Draw_Character (x+24, y, 130);

		//if you want it
		if (p->flags&DPF_WANTTOINSTALL)
			Draw_Character (x+4, y, 131);
		else
			Draw_Character (x+4, y, 129);

		//if you have it already
		if (p->flags&(DPF_HAVEAVERSION | ((((int)(realtime*4))&1)?DPF_DOWNLOADING:0) ))
			Draw_Character (x+20, y, 131);
		else
			Draw_Character (x+20, y, 129);

		if (pn == info->highlightednum)
			Draw_Alt_String(x+48, y, p->name);
		else
			Draw_String(x+48, y, p->name);

		if (p->flags & DPF_DISPLAYVERSION)
		{
			Draw_String(x+48+strlen(p->name)*8, y, va(" (%i.%i)", p->version/1000, p->version%1000));
		}
*/
}

void M_Download_UpdateStatus(struct menu_s *m)
{
	dlmenu_t *info = m->data;
	int i;

	while (!cls.downloadmethod && (info->parsedsourcenum==-1 || info->parsedsourcenum < numdownloadablelists))
	{	//done downloading
		char basename[64];

		info->parsedsourcenum++;

		if (info->parsedsourcenum < numdownloadablelists)
		{
			if (!downloadablelistreceived[info->parsedsourcenum])
			{
				sprintf(basename, "dlinfo_%i.inf", info->parsedsourcenum);
				if (!HTTP_CL_Get(downloadablelist[info->parsedsourcenum], basename, dlnotification))
					Con_Printf("Could not contact server\n");
				return;
			}
		}
	}
	for (i = 0; i < numdownloadablelists; i++)
	{
		if (!downloadablelistreceived[i])
		{
//			Draw_String(x+8, y+8, "Waiting for package list");
			return;
		}
	}

	if (!availablepackages)
	{
//		Draw_String(x+8, y+8, "Could not obtain a package list");
		return;
	}

	if (!info->populated)
	{
		info->populated = true;
		M_AddItemsToDownloadMenu(m);
	}
}
/*
static void M_Download_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m)
{
	int pn;

	int lastpathlen = 0;
	char *lastpath="";

	package_t *p;
	dlmenu_t *info = m->data;

	int i;


	y+=8;
	Draw_Alt_String(x+4, y, "I H");
	(info->highlightednum==0?Draw_Alt_String:Draw_String)(x+40, y, "Apply changes");
	y+=8;

	for (pn = 1, p = availablepackages; p && pn < info->firstpackagenum ; p=p->next, pn++)
		;
	for (; p; p = p->next, y+=8, pn++)
	{
		if (lastpathlen != p->name - p->fullname || strncmp(p->fullname, lastpath, lastpathlen))
		{
			lastpathlen = p->name - p->fullname;
			lastpath = p->fullname;


			if (!lastpathlen)
				Draw_FunStringLen(x+40, y, "/", 1);
			else
				Draw_FunStringLen(x+40, y, p->fullname, lastpathlen);
			y+=8;
		}
		Draw_Character (x, y, 128);
		Draw_Character (x+8, y, 130);
		Draw_Character (x+16, y, 128);
		Draw_Character (x+24, y, 130);

		//if you want it
		if (p->flags&DPF_WANTTOINSTALL)
			Draw_Character (x+4, y, 131);
		else
			Draw_Character (x+4, y, 129);

		//if you have it already
		if (p->flags&(DPF_HAVEAVERSION | ((((int)(realtime*4))&1)?DPF_DOWNLOADING:0) ))
			Draw_Character (x+20, y, 131);
		else
			Draw_Character (x+20, y, 129);

		if (pn == info->highlightednum)
			Draw_Alt_String(x+48, y, p->name);
		else
			Draw_String(x+48, y, p->name);

		if (p->flags & DPF_DISPLAYVERSION)
		{
			Draw_String(x+48+strlen(p->name)*8, y, va(" (%i.%i)", p->version/1000, p->version%1000));
		}
	}
}
*/
static void Menu_Download_Got(char *fname, qboolean successful)
{
	char *ext;
	package_t *p;
	int dlnum = atoi(fname+3);

	for (p = availablepackages; p ; p=p->next)
	{
		if (p->dlnum == dlnum)
		{
			char *destname;
			char *diskname = fname;

			if (!successful)
			{
				p->flags &= ~DPF_DOWNLOADING;
				Con_Printf("Couldn't download %s (from %s to %s)\n", p->name, p->src, p->dest);
				return;
			}

			if (!(p->flags & DPF_DOWNLOADING))
			{
				Con_Printf("menu_download: We're not downloading %s, apparently\n", p->dest);
				return;
			}

			p->flags &= ~DPF_DOWNLOADING;

			ext = COM_FileExtension(p->dest);
			if (!stricmp(ext, "pak") || !stricmp(ext, "pk3"))
				FS_UnloadPackFiles();	//we reload them after

			if (*p->gamedir)
				destname = va("%s/%s", p->gamedir, p->dest);
			else
				destname = va("%s", p->dest);

			if (!FS_Remove(destname, *p->gamedir?FS_BASE:FS_GAME))
				Con_Printf("Deleted old %s\n", destname);
			if (FS_Rename2(diskname, destname, FS_GAME, *p->gamedir?FS_BASE:FS_GAME))
			{
				Con_Printf("Couldn't rename %s to %s. Removed instead.\n", diskname, destname);
				FS_Remove (diskname, FS_GAME);
				return;
			}
			Con_Printf("Downloaded %s (to %s)\n", p->name, destname);
			p->flags |= DPF_HAVEAVERSION;

			WriteInstalledPackages();

			ext = COM_FileExtension(p->dest);
			if (!stricmp(ext, "pak") || !stricmp(ext, "pk3"))
				FS_ReloadPackFiles();
			return;
		}
	}

	Con_Printf("menu_download: Can't figure out where %s came from\n", fname);
}
/*
static qboolean M_Download_Key (struct menucustom_s *c, struct menu_s *m, int key)
{
	char *temp;
	int pn;
	package_t *p, *p2;
	dlmenu_t *info = m->data;

	switch (key)
	{
	case K_UPARROW:
		if (info->highlightednum>0)
			info->highlightednum--;
		return true;
	case K_DOWNARROW:	//cap range when drawing
		if (info->highlightednum < numpackages)
			info->highlightednum++;
		return true;
	case K_ENTER:
		if (!info->highlightednum)
		{	//do it
			//uninstall packages first
			package_t *last = NULL;

			for (p = availablepackages; p ; p=p->next)
			{
				if (!(p->flags&DPF_WANTTOINSTALL) && (p->flags&DPF_HAVEAVERSION))
				{	//if we don't want it but we have it anyway:
					if (*p->gamedir)
					{
						char *fname = va("%s", p->gamedir, p->dest);
						FS_Remove(fname, FS_BASE);
					}
					else
						FS_Remove(p->dest, FS_GAME);
					p->flags&=~DPF_HAVEAVERSION;	//FIXME: This is error prone.

					WriteInstalledPackages();

					if (p->flags & DPF_DELETEONUNINSTALL)
					{
						if (last)
							last->next = p->next;
						else
							availablepackages = p->next;

						BZ_Free(p);

						return M_Download_Key(c, m, key);	//I'm lazy.
					}
				}
				last = p;
			}

			for (p = availablepackages; p ; p=p->next)
			{
				if ((p->flags&DPF_WANTTOINSTALL) && !(p->flags&(DPF_HAVEAVERSION|DPF_DOWNLOADING)))
				{	//if we want it and don't have it:
					p->dlnum = dlcount++;
					temp = va("dl_%i.tmp", p->dlnum);
					Con_Printf("Downloading %s (to %s)\n", p->fullname, temp);
					p->flags|=DPF_DOWNLOADING;
					if (!HTTP_CL_Get(p->src, temp, Menu_Download_Got))
						p->flags&=~DPF_DOWNLOADING;
				}
			}
		}
		else
		{
			for (pn = 1, p = availablepackages; p && pn < info->highlightednum ; p=p->next, pn++)
				;
			if (p)
			{
				p->flags = (p->flags&~DPF_WANTTOINSTALL) | DPF_WANTTOINSTALL - (p->flags&DPF_WANTTOINSTALL);

				if (p->flags&DPF_WANTTOINSTALL)
				{
					for (p2 = availablepackages; p2; p2 = p2->next)
					{
						if (p == p2)
							continue;
						if (!strcmp(p->dest, p2->dest))
							p2->flags &= ~DPF_WANTTOINSTALL;
					}
				}
			}
		}
		return true;
	}
	return false;
}
*/
void Menu_DownloadStuff_f (void)
{
	int i;
	menu_t *menu;
	dlmenu_t *info;

	key_dest = key_menu;
	m_state = m_complex;

	menu = M_CreateMenu(sizeof(dlmenu_t));
	info = menu->data;

	menu->event = M_Download_UpdateStatus;
/*
	menu->selecteditem = (menuoption_t *)(info->list = MC_AddCustom(menu, 0, 32, NULL));
	info->list->draw = M_Download_Draw;
	info->list->key = M_Download_Key;
*/
	info->parsedsourcenum = -1;

	Q_strncpyz(info->pathprefix, Cmd_Argv(1), sizeof(info->pathprefix));
	if (!*info->pathprefix)
	{
		for (i = 0; i < numdownloadablelists; i++)
			downloadablelistreceived[i] = 0;
	}

	MC_AddWhiteText(menu, 24, 8, "Downloads", false);
	MC_AddWhiteText(menu, 0, 16, "Probably buggy, press escape now and avoid this place!", false);
	MC_AddWhiteText(menu, 16, 24, "\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37", false);

	{
		static qboolean loadedinstalled;
		vfsfile_t *f = loadedinstalled?NULL:FS_OpenVFS(INSTALLEDFILES, "rb", FS_BASE);
		loadedinstalled = true;
		if (f)
		{
			ConcatPackageLists(BuildPackageList(f, DPF_DELETEONUNINSTALL|DPF_HAVEAVERSION|DPF_WANTTOINSTALL, ""));
			VFS_CLOSE(f);
		}
	}
}
#elif defined(WEBCLIENT)
void Menu_DownloadStuff_f (void)
{
	Con_Printf("Not yet reimplemented\n");
}
#endif