/* Copyright (C) 1994-1998 Revolution Software Ltd.
 * Copyright (C) 2003-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 */

// ---------------------------------------------------------------------------
// A more intelligent version of the old ANIMS.C
// All this stuff by James
// DON'T TOUCH!
// ---------------------------------------------------------------------------

#include "common/stdafx.h"
#include "common/file.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/screen.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/sound.h"
#include "sword2/animation.h"

namespace Sword2 {

int Router::doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool reverse) {
	AnimHeader anim_head;
	byte *anim_file;

	ObjectLogic obLogic(ob_logic);
	ObjectGraphic obGraph(ob_graph);

	if (obLogic.getLooping() == 0) {
		byte *ptr;

		// This is the start of the anim - set up the first frame

		// For testing all anims!
		// A script loop can send every resource number to the anim
		// function & it will only run the valid ones. See
		// 'testing_routines' object in George's Player Character
		// section of linc

		if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
			if (!_vm->_resman->checkValid(animRes)) {
				// Not a valid resource number. Switch off
				// the sprite. Don't animate - just continue
				// script next cycle.
				setSpriteStatus(ob_graph, NO_SPRITE);
				return IR_STOP;
			}

			ptr = _vm->_resman->openResource(animRes);

			// if it's not an animation file
			if (_vm->_resman->fetchType(animRes) != ANIMATION_FILE) {
				_vm->_resman->closeResource(animRes);

				// switch off the sprite
				// don't animate - just continue
				// script next cycle
				setSpriteStatus(ob_graph, NO_SPRITE);
				return IR_STOP;
			}

			_vm->_resman->closeResource(animRes);

			// switch on the sprite
			setSpriteStatus(ob_graph, SORT_SPRITE);
		}

		assert(animRes);

		// open anim file
		anim_file = _vm->_resman->openResource(animRes);

		assert(_vm->_resman->fetchType(animRes) == ANIMATION_FILE);

		// point to anim header
		anim_head.read(_vm->fetchAnimHeader(anim_file));

		// now running an anim, looping back to this call again
		obLogic.setLooping(1);
		obGraph.setAnimResource(animRes);

		if (reverse)
			obGraph.setAnimPc(anim_head.noAnimFrames - 1);
		else
			obGraph.setAnimPc(0);
	} else if (_vm->_logic->getSync() != -1) {
		// We've received a sync - return to script immediately
		debug(5, "**sync stopped %d**", _vm->_logic->readVar(ID));

		// If sync received, anim finishes right now (remaining on
		// last frame). Quit animation, but continue script.
		obLogic.setLooping(0);
		return IR_CONT;
	} else {
		// Not first frame, and no sync received - set up the next
		// frame of the anim.

		// open anim file and point to anim header
		anim_file = _vm->_resman->openResource(obGraph.getAnimResource());
		anim_head.read(_vm->fetchAnimHeader(anim_file));

		if (reverse)
			obGraph.setAnimPc(obGraph.getAnimPc() - 1);
		else
			obGraph.setAnimPc(obGraph.getAnimPc() + 1);
	}

	// check for end of anim

	if (reverse) {
		if (obGraph.getAnimPc() == 0)
			obLogic.setLooping(0);
	} else {
		if (obGraph.getAnimPc() == anim_head.noAnimFrames - 1)
			obLogic.setLooping(0);
	}

	// close the anim file
	_vm->_resman->closeResource(obGraph.getAnimResource());

	// check if we want the script to loop back & call this function again
	return obLogic.getLooping() ? IR_REPEAT : IR_STOP;
}

int Router::megaTableAnimate(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *animTable, bool reverse) {
	int32 animRes = 0;

	// If this is the start of the anim, read the anim table to get the
	// appropriate anim resource

	ObjectLogic obLogic(ob_logic);

	if (obLogic.getLooping() == 0) {
		ObjectMega obMega(ob_mega);

		// Appropriate anim resource is in 'table[direction]'
		animRes = READ_LE_UINT32(animTable + 4 * obMega.getCurDir());
	}

	return doAnimate(ob_logic, ob_graph, animRes, reverse);
}

void Router::setSpriteStatus(byte *ob_graph, uint32 type) {
	ObjectGraphic obGraph(ob_graph);

	// Remove the previous status, but don't affect the shading upper-word
	obGraph.setType((obGraph.getType() & 0xffff0000) | type);
}

void Router::setSpriteShading(byte *ob_graph, uint32 type) {
	ObjectGraphic obGraph(ob_graph);

	// Remove the previous shading, but don't affect the status lower-word.
	// Note that mega frames may still be shaded automatically, even when
	// not sent 'RDSPR_SHADOW'.
	obGraph.setType((obGraph.getType() & 0x0000ffff) | type);
}

void Logic::createSequenceSpeech(MovieTextObject *sequenceText[]) {
	uint32 line;
 	uint32 local_text;
	uint32 text_res;
	byte *text;
	uint32 wavId;	// ie. offical text number (actor text number)
	bool speechRunning;

	// for each sequence text line that's been logged
	for (line = 0; line < _sequenceTextLines; line++) {
		// allocate this structure
		sequenceText[line] = new MovieTextObject;

		sequenceText[line]->startFrame = _sequenceTextList[line].startFrame;
		sequenceText[line]->endFrame = _sequenceTextList[line].endFrame;

		// pull out the text line to get the official text number
		// (for wav id)

  		text_res = _sequenceTextList[line].textNumber / SIZE;
		local_text = _sequenceTextList[line].textNumber & 0xffff;

		// open text resource & get the line
		text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
		wavId = (int32)READ_LE_UINT16(text);

		// now ok to close the text file
		_vm->_resman->closeResource(text_res);

		// 1st word of text line is the official line number
		debug(5,"(%d) SEQUENCE TEXT: %s", READ_LE_UINT16(text), text + 2);

		// is it to be speech or subtitles or both?
		// assume speech is not running until know otherwise

		speechRunning = false;
		_sequenceTextList[line].speech_mem = NULL;
		sequenceText[line]->speech = NULL;

		if (!_vm->_sound->isSpeechMute()) {
			_sequenceTextList[line].speechBufferSize = _vm->_sound->preFetchCompSpeech(wavId, &_sequenceTextList[line].speech_mem);
			if (_sequenceTextList[line].speechBufferSize) {
				// ok, we've got speech!
				speechRunning = true;
			}
		}

		// if we want subtitles, or speech failed to load

		if (_vm->getSubtitles() || !speechRunning) {
			// open text resource & get the line
			text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
			// make the sprite
			// 'text+2' to skip the first 2 bytes which form the
			// line reference number

			// NB. The mem block containing the text sprite is
			// currently FLOATING!

			// When rendering text over a sequence we need a
			// different colour for the border.

			_sequenceTextList[line].text_mem = _vm->_fontRenderer->makeTextSprite(text + 2, 600, 255, _vm->_speechFontId, 1);

			// ok to close the text resource now
			_vm->_resman->closeResource(text_res);
		} else {
			_sequenceTextList[line].text_mem = NULL;
			sequenceText[line]->textSprite = NULL;
		}
	}

	// for drivers: NULL-terminate the array of pointers to
	// MovieTextObject's
	sequenceText[_sequenceTextLines] = NULL;

	for (line = 0; line < _sequenceTextLines; line++) {
		// if we've made a text sprite for this line...

		if (_sequenceTextList[line].text_mem) {
			// now fill out the SpriteInfo structure in the
			// MovieTextObjectStructure
			FrameHeader frame;

			frame.read(_sequenceTextList[line].text_mem);

			sequenceText[line]->textSprite = new SpriteInfo;

			// center text at bottom of screen
			sequenceText[line]->textSprite->x = 320 - frame.width / 2;
			sequenceText[line]->textSprite->y = 440 - frame.height;
			sequenceText[line]->textSprite->w = frame.width;
			sequenceText[line]->textSprite->h = frame.height;
			sequenceText[line]->textSprite->type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
			sequenceText[line]->textSprite->data = _sequenceTextList[line].text_mem + FrameHeader::size();
		}

		// if we've loaded a speech sample for this line...

 		if (_sequenceTextList[line].speech_mem) {
			// for drivers: set up pointer to decompressed wav in
			// memory

			sequenceText[line]->speechBufferSize = _sequenceTextList[line].speechBufferSize;
			sequenceText[line]->speech = _sequenceTextList[line].speech_mem;
		}
	}
}

void Logic::clearSequenceSpeech(MovieTextObject *sequenceText[]) {
	for (uint i = 0; i < _sequenceTextLines; i++) {
		// free up the memory used by this MovieTextObject
		delete sequenceText[i];

		// free up the mem block containing this text sprite
		if (_sequenceTextList[i].text_mem)
			free(_sequenceTextList[i].text_mem);

		// free up the mem block containing this speech sample
		if (_sequenceTextList[i].speech_mem)
			free(_sequenceTextList[i].speech_mem);
	}

	// IMPORTANT! Reset the line count ready for the next sequence!
	_sequenceTextLines = 0;
}

} // End of namespace Sword2