/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * 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.
 *
 * =======================================================================
 *
 * This file implements the game media download from the server
 *
 * =======================================================================
 */

#include "header/client.h"

extern cvar_t *allow_download;
extern cvar_t *allow_download_players;
extern cvar_t *allow_download_models;
extern cvar_t *allow_download_sounds;
extern cvar_t *allow_download_maps;

extern int precache_check;
extern int precache_spawncount;
extern int precache_tex;
extern int precache_model_skin;

extern byte *precache_model;

static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};

#define PLAYER_MULT 5

/* ENV_CNT is map load, ENV_CNT+1 is first env map */
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT + 13)

void
CL_RequestNextDownload(void)
{
	unsigned int map_checksum; /* for detecting cheater maps */
	char fn[MAX_OSPATH];
	dmdl_t *pheader;

	if (cls.state != ca_connected)
	{
		return;
	}

	if (!allow_download->value && (precache_check < ENV_CNT))
	{
		precache_check = ENV_CNT;
	}

	if (precache_check == CS_MODELS)
	{
		precache_check = CS_MODELS + 2;

		if (allow_download_maps->value)
		{
			if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS + 1]))
			{
				return; /* started a download */
			}
		}
	}

	if ((precache_check >= CS_MODELS) &&
		(precache_check < CS_MODELS + MAX_MODELS))
	{
		if (allow_download_models->value)
		{
			while (precache_check < CS_MODELS + MAX_MODELS &&
				   cl.configstrings[precache_check][0])
			{
				if ((cl.configstrings[precache_check][0] == '*') ||
					(cl.configstrings[precache_check][0] == '#'))
				{
					precache_check++;
					continue;
				}

				if (precache_model_skin == 0)
				{
					if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check]))
					{
						precache_model_skin = 1;
						return; /* started a download */
					}

					precache_model_skin = 1;
				}

				/* checking for skins in the model */
				if (!precache_model)
				{
					FS_LoadFile(cl.configstrings[precache_check],
							(void **)&precache_model);

					if (!precache_model)
					{
						precache_model_skin = 0;
						precache_check++;
						continue; /* couldn't load it */
					}

					if (LittleLong(*(unsigned *)precache_model) !=
						IDALIASHEADER)
					{
						/* not an alias model */
						FS_FreeFile(precache_model);
						precache_model = 0;
						precache_model_skin = 0;
						precache_check++;
						continue;
					}

					pheader = (dmdl_t *)precache_model;

					if (LittleLong(pheader->version) != ALIAS_VERSION)
					{
						precache_check++;
						precache_model_skin = 0;
						continue; /* couldn't load it */
					}
				}

				pheader = (dmdl_t *)precache_model;

				while (precache_model_skin - 1 < LittleLong(pheader->num_skins))
				{
					if (!CL_CheckOrDownloadFile((char *)precache_model +
								LittleLong(pheader->ofs_skins) +
								(precache_model_skin - 1) * MAX_SKINNAME))
					{
						precache_model_skin++;
						return; /* started a download */
					}

					precache_model_skin++;
				}

				if (precache_model)
				{
					FS_FreeFile(precache_model);
					precache_model = 0;
				}

				precache_model_skin = 0;

				precache_check++;
			}
		}

		precache_check = CS_SOUNDS;
	}

	if ((precache_check >= CS_SOUNDS) &&
		(precache_check < CS_SOUNDS + MAX_SOUNDS))
	{
		if (allow_download_sounds->value)
		{
			if (precache_check == CS_SOUNDS)
			{
				precache_check++;
			}

			while (precache_check < CS_SOUNDS + MAX_SOUNDS &&
				   cl.configstrings[precache_check][0])
			{
				if (cl.configstrings[precache_check][0] == '*')
				{
					precache_check++;
					continue;
				}

				Com_sprintf(fn, sizeof(fn), "sound/%s",
						cl.configstrings[precache_check++]);

				if (!CL_CheckOrDownloadFile(fn))
				{
					return; /* started a download */
				}
			}
		}

		precache_check = CS_IMAGES;
	}

	if ((precache_check >= CS_IMAGES) &&
		(precache_check < CS_IMAGES + MAX_IMAGES))
	{
		if (precache_check == CS_IMAGES)
		{
			precache_check++;
		}

		while (precache_check < CS_IMAGES + MAX_IMAGES &&
			   cl.configstrings[precache_check][0])
		{
			Com_sprintf(fn, sizeof(fn), "pics/%s.pcx",
					cl.configstrings[precache_check++]);

			if (!CL_CheckOrDownloadFile(fn))
			{
				return; /* started a download */
			}
		}

		precache_check = CS_PLAYERSKINS;
	}

	/* skins are special, since a player has three 
	   things to download:  model, weapon model and
	   skin so precache_check is now *3 */
	if ((precache_check >= CS_PLAYERSKINS) &&
		(precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT))
	{
		if (allow_download_players->value)
		{
			while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
			{
				int i, n;
				char model[MAX_QPATH], skin[MAX_QPATH], *p;

				i = (precache_check - CS_PLAYERSKINS) / PLAYER_MULT;
				n = (precache_check - CS_PLAYERSKINS) % PLAYER_MULT;

				if (!cl.configstrings[CS_PLAYERSKINS + i][0])
				{
					precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
					continue;
				}

				if ((p = strchr(cl.configstrings[CS_PLAYERSKINS + i], '\\')) != NULL)
				{
					p++;
				}
				else
				{
					p = cl.configstrings[CS_PLAYERSKINS + i];
				}

				strcpy(model, p);

				p = strchr(model, '/');

				if (!p)
				{
					p = strchr(model, '\\');
				}

				if (p)
				{
					*p++ = 0;
					strcpy(skin, p);
				}

				else
				{
					*skin = 0;
				}

				switch (n)
				{
					case 0: /* model */
						Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);

						if (!CL_CheckOrDownloadFile(fn))
						{
							precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
							return;
						}

						n++;

					case 1: /* weapon model */
						Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);

						if (!CL_CheckOrDownloadFile(fn))
						{
							precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
							return;
						}

						n++;

					case 2: /* weapon skin */
						Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model);

						if (!CL_CheckOrDownloadFile(fn))
						{
							precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
							return;
						}

						n++;

					case 3: /* skin */
						Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin);

						if (!CL_CheckOrDownloadFile(fn))
						{
							precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
							return;
						}

						n++;

					case 4: /* skin_i */
						Com_sprintf(fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin);

						if (!CL_CheckOrDownloadFile(fn))
						{
							precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
							return; /* started a download */
						}

						/* move on to next model */
						precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
				}
			}
		}

		/* precache phase completed */
		precache_check = ENV_CNT;
	}

	if (precache_check == ENV_CNT)
	{
		precache_check = ENV_CNT + 1;

		CM_LoadMap(cl.configstrings[CS_MODELS + 1], true, &map_checksum);

		if (map_checksum != (int)strtol(cl.configstrings[CS_MAPCHECKSUM], (char **)NULL, 10))
		{
			Com_Error(ERR_DROP, "Local map version differs from server: %i != '%s'\n",
					map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
			return;
		}
	}

	if ((precache_check > ENV_CNT) && (precache_check < TEXTURE_CNT))
	{
		if (allow_download->value && allow_download_maps->value)
		{
			while (precache_check < TEXTURE_CNT)
			{
				int n = precache_check++ - ENV_CNT - 1;

				if (n & 1)
				{
					Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx",
							cl.configstrings[CS_SKY], env_suf[n / 2]);
				}
				else
				{
					Com_sprintf(fn, sizeof(fn), "env/%s%s.tga",
							cl.configstrings[CS_SKY], env_suf[n / 2]);
				}

				if (!CL_CheckOrDownloadFile(fn))
				{
					return;
				}
			}
		}

		precache_check = TEXTURE_CNT;
	}

	if (precache_check == TEXTURE_CNT)
	{
		precache_check = TEXTURE_CNT + 1;
		precache_tex = 0;
	}

	/* confirm existance of textures, download any that don't exist */
	if (precache_check == TEXTURE_CNT + 1)
	{
		extern int numtexinfo;
		extern mapsurface_t map_surfaces[];

		if (allow_download->value && allow_download_maps->value)
		{
			while (precache_tex < numtexinfo)
			{
				char fn[MAX_OSPATH];

				sprintf(fn, "textures/%s.wal",
						map_surfaces[precache_tex++].rname);

				if (!CL_CheckOrDownloadFile(fn))
				{
					return; /* started a download */
				}
			}
		}

		precache_check = TEXTURE_CNT + 999;
	}

	CL_RegisterSounds();

	CL_PrepRefresh();

	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
	MSG_WriteString(&cls.netchan.message, va("begin %i\n", precache_spawncount));
}

void
CL_DownloadFileName(char *dest, int destlen, char *fn)
{
	if (strncmp(fn, "players", 7) == 0)
	{
		Com_sprintf(dest, destlen, "%s/%s", BASEDIRNAME, fn);
	}

	else
	{
		Com_sprintf(dest, destlen, "%s/%s", FS_Gamedir(), fn);
	}
}

/*
 * Returns true if the file exists, otherwise it attempts
 * to start a download from the server.
 */
qboolean
CL_CheckOrDownloadFile(char *filename)
{
	FILE *fp;
	char name[MAX_OSPATH];
	char *ptr;

	if (strstr(filename, ".."))
	{
		Com_Printf("Refusing to download a path with ..\n");
		return true;
	}

	/* fix backslashes - this is mostly für UNIX comaptiblity */
	while ((ptr = strchr(filename, '\\')))
	{
		*ptr = '/';
	}

	if (FS_LoadFile(filename, NULL) != -1)
	{
		/* it exists, no need to download */
		return true;
	}

	strcpy(cls.downloadname, filename);

	/* download to a temp name, and only rename
	   to the real name when done, so if interrupted
	   a runt file wont be left */
	COM_StripExtension(cls.downloadname, cls.downloadtempname);
	strcat(cls.downloadtempname, ".tmp");

	/* check to see if we already have a tmp for this 
	   file, if so, try to resume and open the file if
	   not opened yet */
	CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);

	fp = fopen(name, "r+b");

	if (fp)
	{
		/* it exists */
		int len;
		fseek(fp, 0, SEEK_END);
		len = ftell(fp);

		cls.download = fp;

		/* give the server an offset to start the download */
		Com_Printf("Resuming %s\n", cls.downloadname);
		MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
		MSG_WriteString(&cls.netchan.message, va("download %s %i", cls.downloadname, len));
	}
	else
	{
		Com_Printf("Downloading %s\n", cls.downloadname);
		MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
		MSG_WriteString(&cls.netchan.message, va("download %s", cls.downloadname));
	}

	cls.downloadnumber++;

	return false;
}

/*
 * Request a download from the server
 */
void
CL_Download_f(void)
{
	char filename[MAX_OSPATH];

	if (Cmd_Argc() != 2)
	{
		Com_Printf("Usage: download <filename>\n");
		return;
	}

	Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));

	if (strstr(filename, ".."))
	{
		Com_Printf("Refusing to download a path with ..\n");
		return;
	}

	if (FS_LoadFile(filename, NULL) != -1)
	{
		/* it exists, no need to download */
		Com_Printf("File already exists.\n");
		return;
	}

	strcpy(cls.downloadname, filename);
	Com_Printf("Downloading %s\n", cls.downloadname);

	/* download to a temp name, and only rename
	   to the real name when done, so if interrupted
	   a runt file wont be left */
	COM_StripExtension(cls.downloadname, cls.downloadtempname);
	strcat(cls.downloadtempname, ".tmp");

	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
	MSG_WriteString(&cls.netchan.message, va("download %s", cls.downloadname));

	cls.downloadnumber++;
}

/*
 * A download message has been received from the server
 */
void
CL_ParseDownload(void)
{
	int size, percent;
	char name[MAX_OSPATH];
	int r;

	/* read the data */
	size = MSG_ReadShort(&net_message);
	percent = MSG_ReadByte(&net_message);

	if (size == -1)
	{
		Com_Printf("Server does not have this file.\n");

		if (cls.download)
		{
			/* if here, we tried to resume a
			 * file but the server said no */
			fclose(cls.download);
			cls.download = NULL;
		}

		CL_RequestNextDownload();
		return;
	}

	/* open the file if not opened yet */
	if (!cls.download)
	{
		CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);

		FS_CreatePath(name);

		cls.download = fopen(name, "wb");

		if (!cls.download)
		{
			net_message.readcount += size;
			Com_Printf("Failed to open %s\n", cls.downloadtempname);
			CL_RequestNextDownload();
			return;
		}
	}

	fwrite(net_message.data + net_message.readcount, 1, size, cls.download);
	net_message.readcount += size;

	if (percent != 100)
	{
		/* request next block */
		cls.downloadpercent = percent;

		MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
		SZ_Print(&cls.netchan.message, "nextdl");
	}
	else
	{
		char oldn[MAX_OSPATH];
		char newn[MAX_OSPATH];

		fclose(cls.download);

		/* rename the temp file to it's final name */
		CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
		CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
		r = rename(oldn, newn);

		if (r)
		{
			Com_Printf("failed to rename.\n");
		}

		cls.download = NULL;
		cls.downloadpercent = 0;

		/* get another file if needed */
		CL_RequestNextDownload();
	}
}

