/*
 * OpenTyrian: A modern cross-platform port of Tyrian
 * Copyright (C) 2007-2009  The OpenTyrian Development Team
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "animlib.h"

#include "file.h"
#include "keyboard.h"
#include "network.h"
#include "nortsong.h"
#include "palette.h"
#include "sizebuf.h"
#include "video.h"

#include <assert.h>
#include <string.h>

/*** Structs ***/
/* The actual header has a lot of fields that are basically useless to us since
 * we both set our own framerate and the format itself only allows for
 * 320x200x8.  Should a (nonexistent) ani be played that doesn't have the same
 * assumed values we are going to use, TOO BAD.  It'll just be treated as
 * corrupt in playback.
 */
#define PALETTE_OFFSET    0x100 // 128 + sizeof(header)
#define PAGEHEADER_OFFSET 0x500 // PALETTE_OFFSET + sizeof(palette)
#define ANIM_OFFSET   0x0B00    // PAGEHEADER_OFFSET + sizeof(largepageheader) * 256
#define ANI_PAGE_SIZE 0x10000   // 65536.

typedef struct anim_FileHeader_s
{
	Uint16 nlps;            /* Number of 'pages', max 256. */
	Uint32 nRecords;        /* Number of 'records', max 65535 */
} anim_FileHeader_t;

typedef struct anim_LargePageHeader_s
{
	Uint16 baseRecord;      /* The first record's number */
	Uint16 nRecords;        /* Number of records.  Supposedly there are bit flags but I saw no such code */
	Uint16 nBytes;	        /* Number of bytes used, excluding headers */
} anim_LargePageHeader_t;

/*** Globals ***/
Uint8 CurrentPageBuffer[65536];
anim_LargePageHeader_t PageHeader[256];
Uint16 CurrentPageRecordSizes[256];

anim_LargePageHeader_t CurrentPageHeader;
anim_FileHeader_t FileHeader;

unsigned int Curlpnum;

FILE * InFile;

/*** Function decs ***/
int JE_playRunSkipDump(Uint8 *, unsigned int);
void JE_closeAnim(void);
int JE_loadAnim(const char *);
int JE_renderFrame(unsigned int);
int JE_findPage (unsigned int);
int JE_loadPage(unsigned int);

/*** Implementation ***/

/* Loads the given page into memory.
 *
 * Returns  0 on success or nonzero on failure (bad data)
 */
int JE_loadPage(unsigned int pagenumber)
{
	unsigned int i, pageSize;

	if (Curlpnum == pagenumber)
		return 0; /* Already loaded */
	Curlpnum = pagenumber;

	/* We need to seek to the page and load it into our buffer.
	 * Pages have a fixed size of 0x10000; any left over space is padded
	 * unless it's the end of the file.
	 *
	 * Pages repeat their headers for some reason.  They then have two bytes of
	 * padding followed by a word for every record.  THEN the data starts.
	 */
	fseek(InFile, ANIM_OFFSET + (pagenumber * ANI_PAGE_SIZE), SEEK_SET);
	fread_u16_die(&CurrentPageHeader.baseRecord, 1, InFile);
	fread_u16_die(&CurrentPageHeader.nRecords,   1, InFile);
	fread_u16_die(&CurrentPageHeader.nBytes,     1, InFile);

	fseek(InFile, 2, SEEK_CUR);
	fread_u16_die(CurrentPageRecordSizes, CurrentPageHeader.nRecords, InFile);

	/* What remains is the 'compressed' data */
	fread_die(CurrentPageBuffer, 1, CurrentPageHeader.nBytes, InFile);

	/* Okay, we've succeeded in all our IO checks.  Now, make sure the
	 * headers aren't lying or damaged or something.
	 */
	pageSize = 0;
	for (i = 0; i < CurrentPageHeader.nRecords; i++)
		pageSize += CurrentPageRecordSizes[i];

	if (pageSize != CurrentPageHeader.nBytes)
		return -1;

	/* So far, so good */
	return 0;
}

int JE_findPage(unsigned int framenumber)
{
	unsigned int i;

	for (i = 0; i < FileHeader.nlps; i++)
	{
		if (PageHeader[i].baseRecord <= framenumber &&
		    PageHeader[i].baseRecord + PageHeader[i].nRecords > framenumber)
		{
			return i;
		}
	}

	return -1; /* Did not find */
}

int JE_renderFrame(unsigned int framenumber)
{
	unsigned int i, offset, destframe;

	destframe = framenumber - CurrentPageHeader.baseRecord;

	offset = 0;
	for (i = 0; i < destframe; i++)
		offset += CurrentPageRecordSizes[i];

	return (JE_playRunSkipDump(CurrentPageBuffer + offset + 4, CurrentPageRecordSizes[destframe] - 4));
}

void JE_playAnim(const char *animfile, JE_byte startingframe, JE_byte speed)
{
	unsigned int i;
	int pageNum;

	if (JE_loadAnim(animfile) != 0)
		return; /* Failed to open or process file */

	/* Blank screen */
	JE_clr256(VGAScreen);
	JE_showVGA();

	/* re FileHeader.nRecords-1: It's -1 in the pascal too.
	 * The final frame is a delta of the first, and we don't need that.
	 * We could also, if we ever ended up needing to loop anis, check
	 * the bools in the header to see if we should render the last
	 * frame.  But that's never going to be necessary :)
	 */
	for (i = startingframe; i < FileHeader.nRecords-1; i++)
	{
		/* Handle boring crap */
		setDelay(speed);

		/* Load required frame.  The loading function is smart enough to not re-load an already loaded frame */
		pageNum = JE_findPage(i);
		if (pageNum == -1)
			break;
		if (JE_loadPage(pageNum) != 0)
			break;

		/* render frame. */
		if (JE_renderFrame(i) != 0)
			break;
		JE_showVGA();

		/* Return early if user presses a key */
		service_SDL_events(true);
		if (newkey)
			break;

		/* Wait until we need the next frame */
		NETWORK_KEEP_ALIVE();
		wait_delay();
	}

	JE_closeAnim();
}

/* loadAnim opens the file and loads data from it into the header structs.
 * It should take care to clean up after itself should an error occur.
 */
int JE_loadAnim(const char *filename)
{
	unsigned int i;
	long fileSize;
	char temp[4];

	Curlpnum = -1;
	InFile = dir_fopen(data_dir(), filename, "rb");
	if (InFile == NULL)
		return -1;

	fileSize = ftell_eof(InFile);
	if (fileSize < ANIM_OFFSET)
	{
		/* We don't know the exact size our file should be yet,
		 * but we do know it should be way more than this */
		fclose(InFile);
		return -1;
	}

	/* Read in the header.  The header is 256 bytes long or so,
	 * but that includes a lot of padding as well as several
	 * vars we really don't care about.  We shall check the ID and extract
	 * the handful of vars we care about.  Every value in the header that
	 * is constant will be ignored.
	 */

	fread_die(&temp, 1, 4, InFile); /* The ID, should equal "LPF " */
	fseek(InFile, 2, SEEK_CUR); /* skip over this word */
	fread_u16_die(&FileHeader.nlps,     1, InFile); /* Number of pages */
	fread_u32_die(&FileHeader.nRecords, 1, InFile); /* Number of records */

	if (memcmp(temp, "LPF ", 4) != 0 ||
	    FileHeader.nlps == 0  || FileHeader.nRecords == 0 ||
	    FileHeader.nlps > 256 || FileHeader.nRecords > 65535)
	{
		fclose(InFile);
		return -1;
	}

	/* Read in headers */
	fseek(InFile, PAGEHEADER_OFFSET, SEEK_SET);
	for (i = 0; i < FileHeader.nlps; i++)
	{
		fread_u16_die(&PageHeader[i].baseRecord, 1, InFile);
		fread_u16_die(&PageHeader[i].nRecords,   1, InFile);
		fread_u16_die(&PageHeader[i].nBytes,     1, InFile);
	}

	/* Now we have enough information to calculate the 'expected' file size.
	 * Our calculation SHOULD be equal to fileSize, but we won't begrudge
	 * padding */
	if (fileSize < (FileHeader.nlps-1) * ANI_PAGE_SIZE + ANIM_OFFSET
	  + PageHeader[FileHeader.nlps-1].nBytes
	  + PageHeader[FileHeader.nlps-1].nRecords * 2 + 8)
	{
		fclose(InFile);
		return -1;
	}

	/* Now read in the palette. */
	fseek(InFile, PALETTE_OFFSET, SEEK_SET);
	for (i = 0; i < 256; i++)
	{
		Uint8 bgru[4];
		fread_u8_die(bgru, 4, InFile);
		colors[i].b = bgru[0];
		colors[i].g = bgru[1];
		colors[i].r = bgru[2];
	}
	set_palette(colors, 0, 255);

	/* Whew!  That was hard.  Let's go grab some beers! */
	return 0;
}

void JE_closeAnim(void)
{
	fclose(InFile);
}

/* RunSkipDump decompresses the video.  There are three operations, run, skip,
 * and dump.  They can be used in either byte or word variations, making six
 * possible actions, and there's a seventh 'stop' action, which looks
 * like 0x80 0x00 0x00.
 *
 * Run is a memset.
 * Dump is a memcpy.
 * Skip leaves the old data intact and simply increments the pointers.
 *
 * returns 0 on success or 1 if decompressing failed.  Failure to decompress
 * indicates a broken or malicious file; playback should terminate.
 */
int JE_playRunSkipDump(Uint8 *incomingBuffer, unsigned int IncomingBufferLength)
{
	sizebuf_t Buffer_IN, Buffer_OUT;
	sizebuf_t * pBuffer_IN = &Buffer_IN, * pBuffer_OUT = &Buffer_OUT;

	#define ANI_SHORT_RLE  0x00
	#define ANI_SHORT_SKIP 0x80
	#define ANI_LONG_OP    0x80
	#define ANI_LONG_COPY_OR_RLE  0x8000
	#define ANI_LONG_RLE   0x4000
	#define ANI_STOP       0x0000

	SZ_Init(pBuffer_IN,  incomingBuffer,    IncomingBufferLength);
	SZ_Init(pBuffer_OUT, VGAScreen->pixels, VGAScreen->h * VGAScreen->pitch);

	/* 320x200 is the only supported format.
	 * Assert is here as a hint should our screen size ever changes.
	 * As for how to decompress to the wrong screen size... */
	assert(VGAScreen->h * VGAScreen->pitch == 320 * 200);

	while (true)
	{
		/* Get one byte.  This byte may have flags that tell us more */
		unsigned int opcode = MSG_ReadByte(pBuffer_IN);

		/* Before we continue, check the error states/
		 * We should *probably* check these after every read and write, but
		 * I've rigged it so that the buffers will never go out of bounds.
		 * So we can afford to be lazy; if the buffer overflows below it will
		 * silently fail its writes and we'll catch the failure on our next
		 * run through the loop.  A failure means we should be
		 * leaving ANYWAY.  The contents of our buffers doesn't matter.
		 */
		if (SZ_Error(pBuffer_IN) || SZ_Error(pBuffer_OUT))
			return -1;

		/* Divide into 'short' and 'long' */
		if (opcode == ANI_LONG_OP) /* long ops */
		{
			opcode = MSG_ReadWord(pBuffer_IN);

			if (opcode == ANI_STOP) /* We are done decompressing.  Leave */
			{
				break;
			}
			else if (!(opcode & ANI_LONG_COPY_OR_RLE)) /* If it's not those two, it's a skip */
			{
				unsigned int count = opcode;
				SZ_Seek(pBuffer_OUT, count, SEEK_CUR);
			}
			else /* Now things get a bit more interesting... */
			{
				opcode &= ~ANI_LONG_COPY_OR_RLE; /* Clear that flag */

				if (opcode & ANI_LONG_RLE) /* RLE */
				{
					unsigned int count = opcode & ~ANI_LONG_RLE; /* Clear flag */

					/* Extract another byte */
					unsigned int value = MSG_ReadByte(pBuffer_IN);

					/* The actual run */
					SZ_Memset(pBuffer_OUT, value, count);
				}
				else
				{ /* Long copy */
					unsigned int count = opcode;

					/* Copy */
					SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count);
				}
			}
		} /* End of long ops */
		else /* short ops */
		{
			if (opcode & ANI_SHORT_SKIP) /* Short skip, move pointer only */
			{
				unsigned int count = opcode & ~ANI_SHORT_SKIP; /* clear flag to get count */
				SZ_Seek(pBuffer_OUT, count, SEEK_CUR);
			}
			else if (opcode == ANI_SHORT_RLE) /* Short RLE, memset the destination */
			{
				/* Extract a few more bytes */
				unsigned int count = MSG_ReadByte(pBuffer_IN);
				unsigned int value = MSG_ReadByte(pBuffer_IN);

				/* Run */
				SZ_Memset(pBuffer_OUT, value, count);
			}
			else /* Short copy, memcpy from src to dest. */
			{
				unsigned int count = opcode;

				/* Dump */
				SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count);
			}
		} /* End of short ops */
	}

	/* And that's that */
	return 0;
}
