/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 */

/*
 * This code is based on Labyrinth of Time code with assistance of
 *
 * Copyright (c) 1993 Terra Nova Development
 * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
 *
 */

#include "lab/lab.h"
#include "lab/stddefines.h"
#include "lab/diff.h"
#include "lab/parsetypes.h"
#include "lab/labfun.h"
#include "lab/parsefun.h"
#include "lab/mouse.h"
#include "lab/vga.h"
#include "lab/text.h"
#include "lab/resource.h"

namespace Lab {

BitMap bit1, bit2, *DispBitMap = &bit1, *DrawBitMap = &bit1;


extern BitMap RawDiffBM;
extern char diffcmap[256 * 3];
extern bool IsBM, nopalchange;

extern bool DoBlack, stopsound;
extern bool IsHiRes;
extern TextFont *MsgFont;
extern const char *CurFileName;





/*---------------------------------------------------------------------------*/
/*------ From readPict.c.  Reads in pictures and animations from disk. ------*/
/*---------------------------------------------------------------------------*/


extern uint32 VGAScreenWidth, VGAScreenHeight, VGABytesPerPage;

/*****************************************************************************/
/* Reads in a picture into the dest bitmap.                                  */
/*****************************************************************************/
bool readPict(const char *filename, bool PlayOnce) {
	byte **file = NULL;

	stopDiff();

	file = g_music->newOpen(filename);

	if (file == NULL) {
		if ((filename[0] == 'p') || (filename[0] == 'P'))
			blackScreen();

		return false;
	}

	DispBitMap->BytesPerRow = VGAScreenWidth;
	DispBitMap->Rows        = VGAScreenHeight;
	DispBitMap->Flags       = BITMAPF_VIDEO;

	readDiff(PlayOnce);

	return true;
}




/*****************************************************************************/
/* Reads in a music file.  Ignores any graphics.                             */
/*****************************************************************************/
bool readMusic(const char *filename, bool waitTillFinished) {
	Common::File *file = g_resource->openDataFile(filename, MKTAG('D', 'I', 'F', 'F'));
	g_music->updateMusic();
	if (!g_music->_doNotFilestopSoundEffect)
		g_music->stopSoundEffect();
	if (!file)
		return false;

	DoBlack = false;
	readSound(waitTillFinished, file);

	return true;
}




/*****************************************************************************/
/* Reads in a picture into buffer memory.                                    */
/*****************************************************************************/
byte *readPictToMem(const char *filename, uint16 x, uint16 y) {
	byte **file = NULL;
	byte *Mem, *CurMem;

	stopDiff();

	allocFile((void **)&Mem, (int32) x * (int32) y, "Bitmap");
	CurMem = Mem;

	file = g_music->newOpen(filename);

	if (file == NULL)
		return NULL;

	DispBitMap->BytesPerRow = x;
	DispBitMap->Rows        = y;
	DispBitMap->Flags       = 0;
	DispBitMap->Planes[0] = CurMem;
	DispBitMap->Planes[1] = DispBitMap->Planes[0] + 0x10000;
	DispBitMap->Planes[2] = DispBitMap->Planes[1] + 0x10000;
	DispBitMap->Planes[3] = DispBitMap->Planes[2] + 0x10000;
	DispBitMap->Planes[4] = DispBitMap->Planes[3] + 0x10000;

	readDiff(true);

	return Mem;
}




/*---------------------------------------------------------------------------*/
/*------------ Does all the text rendering to the message boxes. ------------*/
/*---------------------------------------------------------------------------*/


bool DoNotDrawMessage = false;

extern bool LongWinInFront, Alternate;


/*----- The flowText routines -----*/




/******************************************************************************/
/* Extracts the first word from a string.                                     */
/******************************************************************************/
static void getWord(char *WordBuffer, const char *MainBuffer, uint16 *WordWidth) {
	uint16 width = 0;

	while ((MainBuffer[width] != ' ') && MainBuffer[width] &&
	        (MainBuffer[width] != '\n')) {
		WordBuffer[width] = MainBuffer[width];
		width++;
	}

	WordBuffer[width] = 0;

	*WordWidth = width;
}





/******************************************************************************/
/* Gets a line of text for flowText; makes sure that its length is less than  */
/* or equal to the maximum width.                                             */
/******************************************************************************/
static void getLine(TextFont *tf, char *LineBuffer, const char **MainBuffer, uint16 LineWidth) {
	uint16 CurWidth = 0, WordWidth;
	char WordBuffer[100];
	bool doit = true;

	LineWidth += textLength(tf, " ", 1);

	LineBuffer[0] = 0;

	while ((*MainBuffer)[0] && doit) {
		getWord(WordBuffer, *MainBuffer, &WordWidth);
		strcat(WordBuffer, " ");

		if ((CurWidth + textLength(tf, WordBuffer, WordWidth + 1)) <= LineWidth) {
			strcat(LineBuffer, WordBuffer);
			(*MainBuffer) += WordWidth;

			if ((*MainBuffer)[0] == '\n')
				doit = false;

			if ((*MainBuffer)[0])
				(*MainBuffer)++;

			CurWidth = textLength(tf, LineBuffer, strlen(LineBuffer));
		} else
			doit = false;
	}
}




/******************************************************************************/
/* Dumps a chunk of text to an arbitrary box; flows it within that box and    */
/* optionally centers it. Returns the number of characters that were          */
/* processed.                                                                 */
/*                                                                            */
/* Note: Every individual word MUST be int16 enough to fit on a line, and     */
/* each line less than 255 characters.                                        */
/******************************************************************************/
uint32 flowText(void *font,      /* the TextAttr pointer */
                int16 spacing,          /* How much vertical spacing between the lines */
                uint16 pencolor,         /* pen number to use for text */
                uint16 backpen,          /* the background color */
                bool fillback,                /* Whether to fill the background */
                bool centerh,                 /* Whether to center the text horizontally */
                bool centerv,                 /* Whether to center the text vertically */
                bool output,                  /* Whether to output any text */
                uint16 x1,               /* Cords */
                uint16 y1, uint16 x2, uint16 y2, const char *str) { /* The text itself */
	TextFont *msgfont = (TextFont *)font;
	char linebuffer[256];
	const char *temp;
	uint16 numlines, actlines, fontheight, width;
	uint16 x, y;

	if (fillback) {
		setAPen(backpen);
		rectFill(x1, y1, x2, y2);
	}

	if (str == NULL)
		return 0L;

	setAPen(pencolor);

	fontheight = textHeight(msgfont) + spacing;
	numlines   = (y2 - y1 + 1) / fontheight;
	width      = x2 - x1 + 1;
	y          = y1;

	if (centerv && output) {
		temp = str;
		actlines = 0;

		while (temp[0]) {
			getLine(msgfont, linebuffer, &temp, width);
			actlines++;
		}

		if (actlines <= numlines)
			y += ((y2 - y1 + 1) - (actlines * fontheight)) / 2;
	}

	temp = str;

	while (numlines && str[0]) {
		getLine(msgfont, linebuffer, &str, width);

		x = x1;

		if (centerh)
			x += (width - textLength(msgfont, linebuffer, strlen(linebuffer))) / 2;

		if (output)
			text(msgfont, x, y, pencolor, linebuffer, strlen(linebuffer));

		numlines--;
		y += fontheight;
	}

	return (str - temp);
}


extern uint32 VGABytesPerPage;
extern byte *VGABASEADDRESS;


/******************************************************************************/
/* Calls flowText, but flows it to memory.  Same restrictions as flowText.    */
/******************************************************************************/
uint32 flowTextToMem(Image *DestIm, void *font,     /* the TextAttr pointer */
                     int16 spacing,          /* How much vertical spacing between the lines */
                     uint16 pencolor,         /* pen number to use for text */
                     uint16 backpen,          /* the background color */
                     bool fillback,                /* Whether to fill the background */
                     bool centerh,                 /* Whether to center the text horizontally */
                     bool centerv,                 /* Whether to center the text vertically */
                     bool output,                  /* Whether to output any text */
                     uint16 x1,               /* Cords */
                     uint16 y1, uint16 x2, uint16 y2, const char *str) { /* The text itself */
	uint32 res, vgabyte = VGABytesPerPage;
	byte *tmp = VGABASEADDRESS;

	VGABASEADDRESS = DestIm->ImageData;
	VGABytesPerPage = (uint32) DestIm->Width * (int32) DestIm->Height;

	res = flowText(font, spacing, pencolor, backpen, fillback, centerh, centerv, output, x1, y1, x2, y2, str);

	VGABytesPerPage = vgabyte;
	VGABASEADDRESS = tmp;

	return res;
}




/*----- The control panel stuff -----*/



void createBox(uint16 y2) {
	setAPen(7);                 /* Message box area */
	rectFill(VGAScaleX(4), VGAScaleY(154), VGAScaleX(315), VGAScaleY(y2 - 2));

	setAPen(0);                 /* Box around message area */
	drawHLine(VGAScaleX(2), VGAScaleY(152), VGAScaleX(317));
	drawVLine(VGAScaleX(317), VGAScaleY(152), VGAScaleY(y2));
	drawHLine(VGAScaleX(2), VGAScaleY(y2), VGAScaleX(317));
	drawVLine(VGAScaleX(2), VGAScaleY(152), VGAScaleY(y2));
}




bool LastMessageLong = false;

int32 longDrawMessage(const char *str) {
	char NewText[512];

	if (str == NULL)
		return 0;

	attachGadgetList(NULL);
	mouseHide();
	strcpy(NewText, str);

	if (!LongWinInFront) {
		LongWinInFront = true;
		setAPen(3);                 /* Clear Area */
		rectFill(0, VGAScaleY(149) + SVGACord(2), VGAScaleX(319), VGAScaleY(199));
	}

	createBox(198);
	mouseShow();

	return flowText(MsgFont, 0, 1, 7, false, true, true, true, VGAScaleX(6), VGAScaleY(155), VGAScaleX(313), VGAScaleY(195), str);
}



void drawStaticMessage(byte index) {
	drawMessage(g_resource->getStaticText((StaticText)index).c_str());
}

/******************************************************************************/
/* Draws a message to the message box.                                        */
/******************************************************************************/
void drawMessage(const char *str) {
	if (DoNotDrawMessage) {
		DoNotDrawMessage = false;
		return;
	}

	if (str) {
		if ((textLength(MsgFont, str, strlen(str)) > VGAScaleX(306))) {
			longDrawMessage(str);
			LastMessageLong = true;
		} else {
			if (LongWinInFront) {
				LongWinInFront = false;
				drawPanel();
			}

			mouseHide();
			createBox(168);
			text(MsgFont, VGAScaleX(7), VGAScaleY(155) + SVGACord(2), 1, str, strlen(str));
			mouseShow();
			LastMessageLong = false;
		}
	}
}


/*---------------------------------------------------------------------------*/
/*--------------------------- All the wipe stuff. ---------------------------*/
/*---------------------------------------------------------------------------*/



#define TRANSWIPE      1
#define SCROLLWIPE     2
#define SCROLLBLACK    3
#define SCROLLBOUNCE   4
#define TRANSPORTER    5
#define READFIRSTFRAME 6
#define READNEXTFRAME  7

/*****************************************************************************/
/* Scrolls the display to black.                                             */
/*****************************************************************************/
static void doScrollBlack() {
	byte *mem, *tempmem;
	Image Im;
	uint16 width, height, by, nheight;
	uint32 size, copysize;
	uint32 *BaseAddr;

	mouseHide();
	width = VGAScaleX(320);
	height = VGAScaleY(149) + SVGACord(2);

	allocFile((void **) &mem, (int32) width * (int32) height, "Temp Mem");

	Im.Width = width;
	Im.Height = height;
	Im.ImageData = mem;
	g_music->updateMusic();
	readScreenImage(&Im, 0, 0);
	g_music->updateMusic();

	BaseAddr = (uint32 *) getVGABaseAddr();

	by      = VGAScaleX(4);
	nheight = height;

	while (nheight) {
		g_music->updateMusic();

		if (!IsHiRes)
			waitTOF();

		BaseAddr = (uint32 *) getVGABaseAddr();

		if (by > nheight)
			by = nheight;

		mem += by * width;
		nheight -= by;
		size = (int32) nheight * (int32) width;
		tempmem = mem;

		while (size) {
			if (size > VGABytesPerPage)
				copysize = VGABytesPerPage;
			else
				copysize = size;

			size -= copysize;

			memcpy(BaseAddr, tempmem, copysize);
			tempmem += copysize;
		}

		setAPen(0);
		rectFill(0, nheight, width - 1, nheight + by - 1);

		WSDL_UpdateScreen();

		if (!IsHiRes) {
			if (nheight <= (height / 8))
				by = 1;
			else if (nheight <= (height / 4))
				by = 2;
			else if (nheight <= (height / 2))
				by = 3;
		}
	}

	freeAllStolenMem();
	mouseShow();
}




extern BitMap RawDiffBM;
extern DIFFHeader headerdata;




static void copyPage(uint16 width, uint16 height, uint16 nheight, uint16 startline, byte *mem) {
	uint32 size, OffSet, copysize;
	uint16 CurPage;
	uint32 *BaseAddr;

	BaseAddr = (uint32 *)getVGABaseAddr();

	size = (int32)(height - nheight) * (int32) width;
	mem += startline * width;
	CurPage = ((int32) nheight * (int32) width) / VGABytesPerPage;
	OffSet = ((int32) nheight * (int32) width) - (CurPage * VGABytesPerPage);

	while (size) {
		if (size > (VGABytesPerPage - OffSet))
			copysize = VGABytesPerPage - OffSet;
		else
			copysize = size;

		size -= copysize;

		memcpy(BaseAddr + (OffSet >> 2), mem, copysize);
		mem += copysize;
		CurPage++;
		OffSet = 0;
	}
}


/*****************************************************************************/
/* Scrolls the display to a new picture from a black screen.                 */
/*****************************************************************************/
static void doScrollWipe(char *filename) {
	byte *mem;
	uint16 width, height, by, nheight, startline = 0, onrow = 0;

	mouseHide();
	width = VGAScaleX(320);
	height = VGAScaleY(149) + SVGACord(2);

	while (g_music->isSoundEffectActive()) {
		g_music->updateMusic();
		waitTOF();
	}

	IsBM = true;
	readPict(filename, true);
	VGASetPal(diffcmap, 256);
	IsBM = false;
	mem = RawDiffBM.Planes[0];

	g_music->updateMusic();
	by      = VGAScaleX(3);
	nheight = height;

	while (onrow < headerdata.y) {
		g_music->updateMusic();

		if ((by > nheight) && nheight)
			by = nheight;

		if ((startline + by) > (headerdata.y - height - 1))
			break;

		if (nheight)
			nheight -= by;

		copyPage(width, height, nheight, startline, mem);

		WSDL_UpdateScreen();

		if (!nheight)
			startline += by;

		onrow += by;

		if (nheight <= (height / 4))
			by = VGAScaleX(5);
		else if (nheight <= (height / 3))
			by = VGAScaleX(4);
		else if (nheight <= (height / 2))
			by = VGAScaleX(3);
	}

	mouseShow();
}




/*****************************************************************************/
/* Does the scroll bounce.  Assumes bitmap already in memory.                */
/*****************************************************************************/
static void doScrollBounce() {
	const uint16 *newby, *newby1;

	const uint16 newbyd[5] = {5, 4, 3, 2, 1}, newby1d[8] = {3, 3, 2, 2, 2, 1, 1, 1};
	const uint16 newbyw[5] = {10, 8, 6, 4, 2}, newby1w[8] = {6, 6, 4, 4, 4, 2, 2, 2};

	if (g_lab->getPlatform() != Common::kPlatformWindows) {
		newby = newbyd;
		newby1 = newby1d;
	} else {
		newby = newbyw;
		newby1 = newby1w;
	}


	mouseHide();
	int width = VGAScaleX(320);
	int height = VGAScaleY(149) + SVGACord(2);
	byte *mem = RawDiffBM.Planes[0];

	g_music->updateMusic();
	int startline = headerdata.y - height - 1;

	for (int counter = 0; counter < 5; counter++) {
		g_music->updateMusic();
		startline -= newby[counter];
		copyPage(width, height, 0, startline, mem);

		WSDL_UpdateScreen();
		waitTOF();
	}

	for (int counter = 8; counter > 0; counter--) {
		g_music->updateMusic();
		startline += newby1[counter - 1];
		copyPage(width, height, 0, startline, mem);

		WSDL_UpdateScreen();
		waitTOF();

	}

	mouseShow();
}



/*****************************************************************************/
/* Does the transporter wipe.                                                */
/*****************************************************************************/
static void doTransWipe(CloseDataPtr *CPtr, char *filename) {
	uint16 LastY, CurY, counter, linesdone = 0, lineslast;
	Image ImSource, ImDest;

	if (IsHiRes) {
		lineslast = 3;
		LastY = 358;
	} else {
		lineslast = 1;
		LastY = 148;
	}

	for (counter = 0; counter < 2; counter++) {
		CurY = counter * 2;

		while (CurY < LastY) {
			if (linesdone >= lineslast) {
				g_music->updateMusic();
				waitTOF();
				linesdone = 0;
			}

			ghoastRect(0, 0, CurY, VGAScreenWidth - 1, CurY + 1);
			CurY += 4;
			linesdone++;
		}
	}

	setAPen(0);

	for (counter = 0; counter < 2; counter++) {
		CurY = counter * 2;

		while (CurY <= LastY) {
			if (linesdone >= lineslast) {
				g_music->updateMusic();
				waitTOF();
				linesdone = 0;
			}

			rectFill(0, CurY, VGAScreenWidth - 1, CurY + 1);
			CurY += 4;
			linesdone++;
		}
	}

	if (filename == NULL)
		CurFileName = getPictName(CPtr);
	else if (filename[0] > ' ')
		CurFileName = filename;
	else
		CurFileName = getPictName(CPtr);

	byte *BitMapMem = readPictToMem(CurFileName, VGAScreenWidth, LastY + 5);
	VGASetPal(diffcmap, 256);

	if (BitMapMem) {
		ImSource.Width = VGAScreenWidth;
		ImSource.Height = LastY;
		ImSource.ImageData = BitMapMem;

		ImDest.Width = VGAScreenWidth;
		ImDest.Height = VGAScreenHeight;
		ImDest.ImageData = getVGABaseAddr();

		for (counter = 0; counter < 2; counter++) {
			CurY = counter * 2;

			while (CurY < LastY) {
				if (linesdone >= lineslast) {
					g_music->updateMusic();
					waitTOF();
					linesdone = 0;
				}

				ImDest.ImageData = getVGABaseAddr();

				bltBitMap(&ImSource, 0, CurY, &ImDest, 0, CurY, VGAScreenWidth, 2);
				ghoastRect(0, 0, CurY, VGAScreenWidth - 1, CurY + 1);
				CurY += 4;
				linesdone++;
			}
		}

		for (counter = 0; counter < 2; counter++) {
			CurY = counter * 2;

			while (CurY <= LastY) {
				if (linesdone >= lineslast) {
					g_music->updateMusic();
					waitTOF();
					linesdone = 0;
				}

				ImDest.ImageData = getVGABaseAddr();

				if (CurY == LastY)
					bltBitMap(&ImSource, 0, CurY, &ImDest, 0, CurY, VGAScreenWidth, 1);
				else
					bltBitMap(&ImSource, 0, CurY, &ImDest, 0, CurY, VGAScreenWidth, 2);

				CurY += 4;
				linesdone++;
			}
		}
	}
}



/*****************************************************************************/
/* Does a certain number of pre-programmed wipes.                            */
/*****************************************************************************/
void doWipe(uint16 WipeType, CloseDataPtr *CPtr, char *filename) {
	if ((WipeType == TRANSWIPE) || (WipeType == TRANSPORTER))
		doTransWipe(CPtr, filename);
	else if (WipeType == SCROLLWIPE)
		doScrollWipe(filename);
	else if (WipeType == SCROLLBLACK)
		doScrollBlack();
	else if (WipeType == SCROLLBOUNCE)
		doScrollBounce();
	else if (WipeType == READFIRSTFRAME)
		readPict(filename, false);
	else if (WipeType == READNEXTFRAME)
		diffNextFrame();
}

} // End of namespace Lab