/* ScummVM - Scumm Interpreter
 * Copyright (C) 2003 The ScummVM project
 *
 * 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.
 *
 * $Header$
 *
 */

#include "stdafx.h"
#include <string.h>
#include "common/scummsys.h"
#include "sky/skydefs.h"
#include "sky/sky.h"

#define FREE_IF_NOT_0(ptr)	if (ptr != NULL) { free (ptr); ptr = 0; }
#define REMOVE_INTRO	commandPtr = (uint32 *)zeroCommands; \
			FREE_IF_NOT_0(_introTextSpace) \
			FREE_IF_NOT_0(_introTextSave)
#define CHECK_ESC if (_key_pressed == 27) { _skyScreen->stopSequence(); REMOVE_INTRO return; }
#define WAIT_SEQUENCE while (_skyScreen->sequenceRunning()) { checkCommands(commandPtr); delay(50); CHECK_ESC }

#define INTRO_TEXT_WIDTH    128

#define FN_A_PAL	60080
#define FN_1A_LOG	60081
#define FN_1A	60082
#define FN_1B	60083
#define FN_1C	60084
#define FN_1D	60085
#define FN_1E	60086
#define FN_4A	60087
#define FN_4B_LOG	60088
#define FN_4B	60089
#define FN_4C_LOG	60090
#define FN_4C	60091
#define FN_5_PAL	60092
#define FN_5_LOG	60093
#define FN_5	60094
#define FN_6_PAL	60095
#define FN_6_LOG	60096
#define FN_6A	60097
#define FN_6B	60098

#define IC_PREPARE_TEXT 0
#define IC_SHOW_TEXT    1
#define IC_REMOVE_TEXT  2
#define IC_MAKE_SOUND   3
#define IC_FX_VOLUME    4

uint32 cockpitCommands[] = {

	1000, //do straight away
	IC_PREPARE_TEXT,
	77,
	220,
	IC_SHOW_TEXT, //radar detects jamming signal
	20,
	160,
	105,
	IC_REMOVE_TEXT,
	105,
	IC_PREPARE_TEXT,
	81,
	105,
	IC_SHOW_TEXT, //well switch to override you fool
	170,
	86,
	35,
	IC_REMOVE_TEXT,
	35,
	IC_PREPARE_TEXT,
	477,
	35,
	IC_SHOW_TEXT,
	30,
	160,
	3,
	IC_REMOVE_TEXT,
	0
};

uint32 zeroCommands[] = { 0 };

uint32 anim5Commands[] = {

	31,
	IC_MAKE_SOUND,
	2,
	127,
	0
};

uint32 anim4aCommands[] = {

	136,
	IC_MAKE_SOUND,
	1,
	70,
	90,
	IC_FX_VOLUME,
	80,
	50,
	IC_FX_VOLUME,
	90,
	5,
	IC_FX_VOLUME,
	100,
	0
};

uint32 anim4cCommands[] = {

	1000,
	IC_FX_VOLUME,
	100,
	25,
	IC_FX_VOLUME,
	110,
	15,
	IC_FX_VOLUME,
	120,
	4,
	IC_FX_VOLUME,
	127,
	0
};

uint32 anim6aCommands[] = {

	1000,
	IC_PREPARE_TEXT,
	478,
	13,
	IC_SHOW_TEXT,
	175,
	155,
	0
};

uint32 anim6bCommands[] = {

	131,
	IC_REMOVE_TEXT,
	131,
	IC_PREPARE_TEXT,
	479,
	74,
	IC_SHOW_TEXT,
	175,
	155,
	45,
	IC_REMOVE_TEXT,
	45,
	IC_PREPARE_TEXT,
	162,
	44,
	IC_SHOW_TEXT,
	175,
	155,
	4,
	IC_REMOVE_TEXT,
	0
};

typedef void (SkyState::*commandRoutinesProc)(uint32 *&cmdPtr);
static const commandRoutinesProc commandRoutines[] = {
	&SkyState::prepareText,
	&SkyState::showIntroText,
	&SkyState::removeText,
	&SkyState::introFx,
	&SkyState::introVol,
};

void SkyState::initVirgin() {
	
	_skyScreen->setPalette(60111);
	_skyScreen->showScreen(60110);
}

void SkyState::intro(void) {

	uint32 *commandPtr = (uint32 *)zeroCommands;

	_skyDisk->prefetchFile(60112); // revolution screen
	_skyDisk->prefetchFile(60113); // revolution palette

	_skyMusic->loadSection(0);
	_skySound->loadSection(0);
	
	delay(3000); //keep virgin screen up for 3 seconds
	CHECK_ESC
	
	if (!isCDVersion(_gameVersion))
		_skyMusic->startMusic(1);
	
	delay(3000); //and another 3 seconds.
	CHECK_ESC
	
	_skyScreen->fnFadeDown(0); //remove virgin screen
	
	_skyScreen->showScreen(60112);
	_skyScreen->paletteFadeUp(60113);

	//while rev is up, load gibbons screen
	_skyDisk->prefetchFile(60114); // gibbo screen
	_skyDisk->prefetchFile(60115); // gibbo palette

	_introTextSpace = (uint8 *)calloc(10000, 1);
	_introTextSave = (uint8 *)calloc(10000, 1);

	_skyText->getText(77);
	
	delay(8000); // keep revolution up for 8 seconds
	CHECK_ESC
	
	_skyScreen->fnFadeDown(0);
	_skyScreen->showScreen(60114);
	_skyScreen->paletteFadeUp(60115);


	if (isCDVersion(_gameVersion)) {
		doCDIntro();
	} else {
		_skyDisk->prefetchFile(FN_A_PAL);
		_skyDisk->prefetchFile(FN_1A_LOG);
		_skyDisk->prefetchFile(FN_1A);

		//keep gibbo up for 2 seconds
		delay(2000);
		CHECK_ESC
		_skyScreen->fnFadeDown(0);

		_skyScreen->showScreen(FN_1A_LOG);
		_skyScreen->paletteFadeUp(FN_A_PAL);
		_skyScreen->startSequence(FN_1A);	
		_skyDisk->prefetchFile(FN_1B);
		WAIT_SEQUENCE;

		_skyScreen->startSequence(FN_1B);
		_skyDisk->prefetchFile(FN_1C);
		WAIT_SEQUENCE;

		_skyScreen->startSequence(FN_1C);
		_skyDisk->prefetchFile(FN_1D);
		WAIT_SEQUENCE;

		_skyScreen->startSequence(FN_1D);
		_skyDisk->prefetchFile(FN_1E);
		WAIT_SEQUENCE;

		_skyScreen->startSequence(FN_1E);

		uint8 *vgaData, *diffData, *vgaPointer, *diffPointer, *scrollData;
		uint8 *currScreenPos;
		vgaData = _skyDisk->loadFile(60100, NULL);
		vgaPointer = vgaData;
		diffData = _skyDisk->loadFile(60101, NULL);
		diffPointer = diffData + 2;
		uint16 noFrames = READ_LE_UINT16(diffData);

		_skyDisk->prefetchFile(FN_4A);
		
		WAIT_SEQUENCE;		

		
		//set up the scrolling intro
		scrollData = (byte *)malloc(GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT * 2);
		//clear the base
		memset(scrollData, 0, GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT);	
		memcpy(scrollData + (GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT), _skyScreen->giveCurrent(), GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT); 
		currScreenPos = scrollData + (GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT);

		byte scrollByte; 
		for (uint16 frameCounter = 1; frameCounter < noFrames; frameCounter++) {
			scrollByte = *diffPointer++;
			if (scrollByte)
				currScreenPos -= scrollByte * GAME_SCREEN_WIDTH;

			delay(40); 
			if (_key_pressed == 27) {
				REMOVE_INTRO;
				return;
			}
			
			//non-scrolling frame update
			introFrame(&diffPointer, &vgaPointer, currScreenPos);
			_skyScreen->showScreen(currScreenPos);
		}
		memcpy(_skyScreen->giveCurrent(), currScreenPos, GAME_SCREEN_HEIGHT * GAME_SCREEN_WIDTH);
		_skyScreen->startSequence(FN_4A);
		free(scrollData);
		free(vgaData);
		free(diffData);
		commandPtr = (uint32 *)anim4aCommands;
		_skyDisk->prefetchFile(FN_4B);
		_skyDisk->prefetchFile(FN_4B_LOG);
		WAIT_SEQUENCE;
 		
		_skyScreen->showScreen(FN_4B_LOG);
		commandPtr = (uint32 *)cockpitCommands;
		_skyScreen->startSequence(FN_4B);
		checkCommands(commandPtr);
		checkCommands(commandPtr);
		_skyDisk->prefetchFile(FN_4C);
		WAIT_SEQUENCE; //4b

		_skyScreen->showScreen(FN_4C_LOG);
		_skyScreen->startSequence(FN_4C);
		commandPtr = (uint32 *)anim4cCommands;
		WAIT_SEQUENCE; //4c
		
		_skyDisk->prefetchFile(FN_5_PAL);
		_skyDisk->prefetchFile(FN_5_LOG);
		_skyDisk->prefetchFile(FN_5);
		
		_skyScreen->fnFadeDown(0);
		_skyScreen->showScreen(FN_5_LOG);
		_skyScreen->paletteFadeUp(FN_5_PAL);
		
		_skyScreen->startSequence(FN_5);

		commandPtr = (uint32 *)anim5Commands;
		
		WAIT_SEQUENCE;
		_skyDisk->prefetchFile(FN_6_PAL);
		_skyDisk->prefetchFile(FN_6_LOG);
		_skyDisk->prefetchFile(FN_6A);

		_skyScreen->fnFadeDown(0);
		_skyScreen->showScreen(FN_6_LOG);

		_skyMusic->startMusic(2);
		_skyScreen->paletteFadeUp(FN_6_PAL);

		_skyScreen->startSequence(FN_6A);
		_skyDisk->prefetchFile(FN_6B);
		
		commandPtr = (uint32 *)anim6aCommands;
		WAIT_SEQUENCE; //6a
		
		_skyScreen->startSequence(FN_6B);
		commandPtr = (uint32 *)anim6bCommands;
		WAIT_SEQUENCE; //6b
	}
}

void SkyState::introFrame(uint8 **diffPtr, uint8 **vgaPtr, uint8 *screenData) {

	uint32 scrPos = 0;
	uint8 nrToSkip, nrToDo;

	do {
		do {
			nrToSkip = **diffPtr;
			(*diffPtr)++;
			scrPos += nrToSkip;
		} while (nrToSkip == 255);

		do {
			nrToDo = **diffPtr;
			(*diffPtr)++;
			memcpy(screenData + scrPos, *vgaPtr, nrToDo);
			scrPos += nrToDo;
			*vgaPtr += nrToDo;
		} while (nrToDo == 255);

	} while (scrPos < GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT);
}

void SkyState::checkCommands(uint32 *&cmdPtr) {

	//check for sequence commands
	assert (cmdPtr != NULL);
	
	uint32 afterFrame = *(cmdPtr);
	
	if (afterFrame >= _skyScreen->seqFramesLeft()) { 

		//do a command
		uint32 command = *(cmdPtr + 1);
		(this->*commandRoutines[command])(cmdPtr); 
	} 
}

void SkyState::prepareText(uint32 *&cmdPtr) {
	
	uint32 textNum = *(cmdPtr + 2);
	_skyText->getText(textNum);
	_skyText->displayText(_introTextSpace, true, INTRO_TEXT_WIDTH, 255);
	cmdPtr += 3;  
}

#define _workScreen _skyScreen->giveCurrent()
void SkyState::showIntroText(uint32 *&cmdPtr) {
	
	uint32 xPos = *(cmdPtr + 2); 
	uint32 yPos = *(cmdPtr + 3); 
	uint32 startPos = (yPos * FULL_SCREEN_WIDTH) + xPos;
	byte *destBuf = _introTextSpace;
	byte *saveBuf = _introTextSave;
	uint16 width = FROM_LE_16(((struct dataFileHeader *)destBuf)->s_width);	
	uint16 height = FROM_LE_16(((struct dataFileHeader *)destBuf)->s_height);	
	
	*(uint32 *)saveBuf = TO_LE_32(startPos); 
	*(uint32 *)(saveBuf + 4) = TO_LE_32(height);
	*(uint32 *)(saveBuf + 8) = TO_LE_32(width);
	saveBuf += 12;
		
	//save current screen contents
	byte *savePtr = (byte *)_workScreen + startPos;

	uint i, j;

	for (i = 0; i < height; i++) {
		memcpy(saveBuf, savePtr, width);
		saveBuf += width;
		savePtr += GAME_SCREEN_WIDTH;
	}

	//now print the text

	byte *textBuf = _introTextSpace + sizeof(struct dataFileHeader);
	byte *curPos = (byte *)_workScreen + startPos;

	for (i = 0; i < height; i++) {

		byte *prevPos = curPos;

		for (j = 0; j < width; j++) {

			uint8 pixel = *textBuf++;
			if (pixel) 
				*curPos = pixel;

			curPos++;
		}

		curPos = prevPos + GAME_SCREEN_WIDTH;
	}

	cmdPtr += 4; 

}

void SkyState::removeText(uint32 *&cmdPtr) {
		
	byte *saveBuf = _introTextSave;
	uint32 startPos = READ_LE_UINT32(saveBuf);
	uint32 height = READ_LE_UINT32(saveBuf + 4);
	uint32 width = READ_LE_UINT32(saveBuf + 8);
	byte *dest = _workScreen + startPos;
	saveBuf += 12;
	
	for (unsigned int i = 0; i < height; i++) {
		memcpy(dest, saveBuf, width);
		saveBuf += width;
		dest += FULL_SCREEN_WIDTH;
	}

	cmdPtr += 2;
}
#undef _workScreen

void SkyState::introFx(uint32 *&cmdPtr) {

	_skySound->playSound((uint16)cmdPtr[2], (uint16)cmdPtr[3]);
	cmdPtr += 4;
}

void SkyState::introVol(uint32 *&cmdPtr) {

	_skySound->playSound(1, (uint16)(cmdPtr[2] & 0x7F));
	cmdPtr += 3;
}