/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-10-0a/engines/parallaction/animation.cpp $
 * $Id: animation.cpp 28198 2007-07-25 19:01:58Z peres001 $
 *
 */

#include "common/stdafx.h"

#include "parallaction/parallaction.h"


namespace Parallaction {


#define INST_ON 						1
#define INST_OFF						2
#define INST_X							3
#define INST_Y							4
#define INST_Z							5
#define INST_F							6
#define INST_LOOP						7
#define INST_ENDLOOP					8
#define INST_SHOW						9
#define INST_INC						10
#define INST_DEC						11
#define INST_SET						12
#define INST_PUT						13
#define INST_CALL						14
#define INST_WAIT						15
#define INST_START						16
#define INST_SOUND						17
#define INST_MOVE						18
#define INST_END						1000


void	wrapLocalVar(LocalVariable *local);




uint16	_numLocals = 0;
char	_localNames[10][10];

Animation *Parallaction::findAnimation(const char *name) {

	for (AnimationList::iterator it = _animations.begin(); it != _animations.end(); it++)
		if (!scumm_stricmp((*it)->_label._text, name)) return *it;

	return NULL;
}


Animation *Parallaction::parseAnimation(Script& script, AnimationList &list, char *name) {
//	printf("parseAnimation(%s)\n", name);

	Animation *vD0 = new Animation;

	vD0->_label._text = (char*)malloc(strlen(name)+1);
	strcpy(vD0->_label._text, name);

	list.push_front(vD0);

	fillBuffers(script, true);
	while (scumm_stricmp(_tokens[0], "endanimation")) {
//		printf("token[0] = %s\n", _tokens[0]);

		if (!scumm_stricmp(_tokens[0], "script")) {
			loadProgram(vD0, _tokens[1]);
		}
		if (!scumm_stricmp(_tokens[0], "commands")) {
			 parseCommands(script, vD0->_commands);
		}
		if (!scumm_stricmp(_tokens[0], "type")) {
			if (_tokens[2][0] != '\0') {
				vD0->_type = ((4 + _objectsNames->lookup(_tokens[2])) << 16) & 0xFFFF0000;
			}
			int16 _si = _zoneTypeNames->lookup(_tokens[1]);
			if (_si != -1) {
				vD0->_type |= 1 << (_si-1);
				if (((vD0->_type & 0xFFFF) != kZoneNone) && ((vD0->_type & 0xFFFF) != kZoneCommand)) {
					parseZoneTypeBlock(script, vD0);
				}
			}
		}
		if (!scumm_stricmp(_tokens[0], "label")) {
			_gfx->makeCnvFromString(&vD0->_label._cnv, _tokens[1]);
		}
		if (!scumm_stricmp(_tokens[0], "flags")) {
			uint16 _si = 1;

			do {
				byte _al = _zoneFlagNames->lookup(_tokens[_si]);
				_si++;
				vD0->_flags |= 1 << (_al - 1);
			} while (!scumm_stricmp(_tokens[_si++], "|"));
		}
		if (!scumm_stricmp(_tokens[0], "file")) {
			char vC8[200];
			strcpy(vC8, _tokens[1]);
			if (_engineFlags & kEngineTransformedDonna) {
				if (!scumm_stricmp(_tokens[1], "donnap") || !scumm_stricmp(_tokens[1], "donnapa")) {
					strcat(vC8, "tras");
				}
			}
			vD0->_cnv = _disk->loadFrames(vC8);
		}
		if (!scumm_stricmp(_tokens[0], "position")) {
			vD0->_left = atoi(_tokens[1]);
			vD0->_top = atoi(_tokens[2]);
			vD0->_z = atoi(_tokens[3]);
		}
		if (!scumm_stricmp(_tokens[0], "moveto")) {
			vD0->_moveTo.x = atoi(_tokens[1]);
			vD0->_moveTo.y = atoi(_tokens[2]);
		}

		fillBuffers(script, true);
	}

	vD0->_oldPos.x = -1000;
	vD0->_oldPos.y = -1000;

	vD0->_flags |= 0x1000000;

	return vD0;
}


void Parallaction::freeAnimations() {
	_animations.clear();
	return;
}



void jobDisplayAnimations(void *parm, Job *j) {
//	printf("jobDisplayAnimations()...\n");

	StaticCnv v14;

	uint16 _si = 0;

	for (AnimationList::iterator it = _vm->_animations.begin(); it != _vm->_animations.end(); it++) {

		Animation *v18 = *it;

		if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0))   {
			v14._width = v18->width();
			v14._height = v18->height();

			int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1);

			v14._data0 = v18->getFrameData(frame);
//			v14._data1 = v18->_cnv->field_8[frame];

			if (v18->_flags & kFlagsNoMasked)
				_si = 3;
			else
				_si = _vm->_gfx->queryMask(v18->_top + v18->height());

			debugC(9, kDebugLocation, "jobDisplayAnimations(%s, x:%i, y:%i, z:%i, w:%i, h:%i, f:%i/%i, %p)", v18->_label._text, v18->_left, v18->_top, _si, v14._width, v14._height,
				frame, v18->getFrameNum(), v14._data0);
			_vm->_gfx->blitCnv(&v14, v18->_left, v18->_top, _si, Gfx::kBitBack);

		}

		if (((v18->_flags & kFlagsActive) == 0) && (v18->_flags & kFlagsRemove))   {
			v18->_flags &= ~kFlagsRemove;
			v18->_oldPos.x = -1000;
		}

		if ((v18->_flags & kFlagsActive) && (v18->_flags & kFlagsRemove))	{
			v18->_flags &= ~kFlagsActive;
			v18->_flags |= kFlagsRemove;
		}

	}

//	  printf("done\n");

	return;
}


void jobEraseAnimations(void *arg_0, Job *j) {
	debugC(3, kDebugJobs, "jobEraseAnimations");

	for (AnimationList::iterator it = _vm->_animations.begin(); it != _vm->_animations.end(); it++) {

		Animation *a = *it;

		if (((a->_flags & kFlagsActive) == 0) && ((a->_flags & kFlagsRemove) == 0)) continue;

		Common::Rect r(a->width(), a->height());
		r.moveTo(a->_oldPos);
		_vm->_gfx->restoreBackground(r);

		if (arg_0) {
			a->_oldPos.x = a->_left;
			a->_oldPos.y = a->_top;
		}

	}

//	printf("done\n");

	return;
}


void Parallaction::loadProgram(Animation *a, char *filename) {
//	printf("loadProgram(%s)\n", filename);

	Script *script = _disk->loadScript(filename);

	_numLocals = 0;

	fillBuffers(*script);

	a->_program = new Program;

	Instruction *vCC = new Instruction;

	while (scumm_stricmp(_tokens[0], "endscript")) {
		parseScriptLine(vCC, a, a->_program->_locals);
		a->_program->_instructions.push_back(vCC);
		vCC = new Instruction;
		fillBuffers(*script);
	}

	// TODO: use List<>::end() to detect the end of the program
	vCC->_index = INST_END;
	a->_program->_instructions.push_back(vCC);
	a->_program->_ip = a->_program->_instructions.begin();

	delete script;

	return;
}






void Parallaction::parseScriptLine(Instruction *inst, Animation *a, LocalVariable *locals) {
//	printf("parseScriptLine()\n");

	if (_tokens[0][1] == '.') {
		_tokens[0][1] = '\0';
		a = findAnimation(&_tokens[0][2]);
	}

	if (_tokens[1][1] == '.') {
		_tokens[1][1] = '\0';
		a = findAnimation(&_tokens[1][2]);
	}

	int16 _si = _instructionNames->lookup(_tokens[0]);
	inst->_index = _si;

//	printf("token[0] = %s (%i)\n", _tokens[0], inst->_index);

	switch (inst->_index) {
	case INST_ON:	// on
	case INST_OFF:	// off
	case INST_START:	// start
		if (!scumm_stricmp(_tokens[1], a->_label._text)) {
			inst->_opBase._a = a;
		} else {
			inst->_opBase._a = findAnimation(_tokens[1]);
		}
		break;

	case INST_LOOP: // loop
		inst->_opBase._loopCounter = getLValue(inst, _tokens[1], locals, a);
		break;

	case INST_X:	// x
		inst->_opA._pvalue = &a->_left;
		inst->_opB = getLValue(inst, _tokens[1], locals, a);
		break;

	case INST_Y:	// y
		inst->_opA._pvalue = &a->_top;
		inst->_opB = getLValue(inst, _tokens[1], locals, a);
		break;

	case INST_Z:	// z
		inst->_opA._pvalue = &a->_z;
		inst->_opB = getLValue(inst, _tokens[1], locals, a);
		break;

	case INST_F:	// f
		inst->_opA._pvalue = &a->_frame;
		inst->_opB = getLValue(inst, _tokens[1], locals, a);
		break;

	case INST_INC:	// inc
	case INST_DEC:	// dec
		if (!scumm_stricmp(_tokens[1], "X")) {
			inst->_opA._pvalue = &a->_left;
		} else
		if (!scumm_stricmp(_tokens[1], "Y")) {
			inst->_opA._pvalue = &a->_top;
		} else
		if (!scumm_stricmp(_tokens[1], "Z")) {
			inst->_opA._pvalue = &a->_z;
		} else
		if (!scumm_stricmp(_tokens[1], "F")) {
			inst->_opA._pvalue = &a->_frame;
		} else {
			inst->_flags |= kInstUsesLocal;
			inst->_opA = getLValue(inst, _tokens[1], locals, a);
		}

		inst->_opB = getLValue(inst, _tokens[2], locals, a);

		if (!scumm_stricmp(_tokens[3], "mod")) {
			inst->_flags |= kInstMod;
		}
		break;

	case INST_SET:	// set
		inst->_opA = getLValue(inst, _tokens[1], locals, a);
		inst->_flags |= kInstUsesLocal;
		inst->_opB = getLValue(inst, _tokens[2], locals, a);
		break;

	case INST_MOVE: // move
		inst->_opA = getLValue(inst, _tokens[1], locals, a);
		inst->_opB = getLValue(inst, _tokens[2], locals, a);
		break;

	case INST_PUT:	// put
		if (!scumm_stricmp(_tokens[1], a->_label._text)) {
			inst->_opBase._a = a;
		} else {
			inst->_opBase._a = findAnimation(_tokens[1]);
		}

		inst->_opA = getLValue(inst, _tokens[2], locals, a);
		inst->_opB = getLValue(inst, _tokens[3], locals, a);
		if (!scumm_stricmp(_tokens[4], "masked")) {
			inst->_flags |= kInstMaskedPut;
		}
		break;

	case INST_CALL: {	// call
		int16 _ax = _callableNames->lookup(_tokens[1]);
		inst->_opBase._index = _ax - 1;
		if (_ax - 1 < 0) exit(0);
	}
	break;

	case INST_SOUND:	// sound
		inst->_opBase._z = findZone(_tokens[1]);
		break;

	case INST_ENDLOOP:	// endloop
	case INST_SHOW: // show
	case INST_WAIT: // wait
		break;

	default:	// local definition
		strcpy(_localNames[_numLocals], _tokens[0]);
		locals[_numLocals]._value = atoi(_tokens[2]);

		if (_tokens[3][0] != '\0') {
			locals[_numLocals]._min = atoi(_tokens[3]);
			locals[_numLocals]._max = atoi(_tokens[4]);
		} else {
			locals[_numLocals]._min = -10000;
			locals[_numLocals]._max = 10000;
		}

		inst->_opA._local = &locals[_numLocals];
		inst->_opB._value = locals[_numLocals]._value;

		inst->_flags = kInstUsesLiteral | kInstUsesLocal;
		inst->_index = INST_SET;
		_numLocals++;
		break;

	}


	return;
}

LValue Parallaction::getLValue(Instruction *inst, char *str, LocalVariable *locals, Animation *a) {

	LValue v;

	v._pvalue = 0;	// should stop compiler from complaining

	if (isdigit(str[0]) || str[0] == '-') {
		inst->_flags |= kInstUsesLiteral;
		v._value = atoi(str);
		return v;
	}

	for (uint16 _si = 0; _si < 10; _si++) {
		if (!scumm_stricmp(str, _localNames[_si])) {
			v._local = &locals[_si];
			return v;
		}
	}

	if (str[1] == '.') {
		a = findAnimation(&str[2]);
	}

	if (str[0] == 'X') {
		v._pvalue = &a->_left;
	} else
	if (str[0] == 'Y') {
		v._pvalue = &a->_top;
	} else
	if (str[0] == 'Z') {
		v._pvalue = &a->_z;
	} else
	if (str[0] == 'F') {
		v._pvalue = &a->_frame;
	}

	return v;
}



void jobRunScripts(void *parm, Job *j) {
	debugC(3, kDebugJobs, "jobRunScripts");

	static uint16 modCounter = 0;

	StaticCnv v18;

	for (AnimationList::iterator it = _vm->_animations.begin(); it != _vm->_animations.end(); it++) {

		Animation *a = *it;

		if (a->_flags & kFlagsCharacter) a->_z = a->_top + a->height();

		if ((a->_flags & kFlagsActing) == 0) continue;
		InstructionList::iterator inst = a->_program->_ip;

//		printf("Animation: %s, flags: %x\n", a->_name, a->_flags);

		while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) {

			debugC(9, kDebugJobs, "Animation: %s, instruction: %s", a->_label._text, (*inst)->_index == INST_END ? "end" : _instructionNamesRes[(*inst)->_index - 1]);

			switch ((*inst)->_index) {
			case INST_ENDLOOP:	// endloop
				if (--a->_program->_loopCounter > 0) {
					inst = a->_program->_loopStart;
				}
				break;

			case INST_OFF:	{// off
				(*inst)->_opBase._a->_flags |= kFlagsRemove;
//				v1C = (*inst)->_opBase;
				}
				break;

			case INST_ON:	// on
				(*inst)->_opBase._a->_flags |= kFlagsActive;
				(*inst)->_opBase._a->_flags &= ~kFlagsRemove;
				break;

			case INST_START:	// start
//				v1C = (*inst)->_opBase;
				(*inst)->_opBase._a->_flags |= (kFlagsActing | kFlagsActive);
				break;

			case INST_LOOP: // loop
				if ((*inst)->_flags & kInstUsesLiteral) {
					a->_program->_loopCounter = (*inst)->_opBase._loopCounter._value;
				} else {
					a->_program->_loopCounter = *(*inst)->_opBase._loopCounter._pvalue;
				}
				a->_program->_loopStart = inst;
				break;

			case INST_INC:	// inc
			case INST_DEC: {	// dec
				int16 _si = 0;
				int16 _ax = 0, _bx = 0;
				if ((*inst)->_flags & kInstUsesLiteral) {
					_si = (*inst)->_opB._value;
				} else {
					_si = *(*inst)->_opB._pvalue;
				}
				if ((*inst)->_flags & kInstMod) {	// mod
					_bx = (_si > 0 ? _si : -_si);
					if (modCounter % _bx != 0) break;

					_si = (_si > 0 ?  1 : -1);
				}
				if ((*inst)->_flags & kInstUsesLocal) {	// local
					if ((*inst)->_index == INST_INC) _ax = _si;
					else _ax = -_si;

					(*inst)->_opA._local->_value += _ax;
					wrapLocalVar((*inst)->_opA._local);
					break;
				}

				// built-in variable (x, y, z, f)
				if ((*inst)->_index == INST_INC) _ax = _si;
				else _ax = -_si;
				*(*inst)->_opA._pvalue += _ax;
			}
			break;

			case INST_MOVE: { // move
				WalkNodeList *v4 = _vm->_char._builder.buildPath(*(*inst)->_opA._pvalue, *(*inst)->_opB._pvalue);
				_vm->addJob(&jobWalk, v4, kPriority19 );
				_engineFlags |= kEngineWalking;
			}
				break;

			case INST_PUT:	// put
				v18._width = (*inst)->_opBase._a->width();
				v18._height = (*inst)->_opBase._a->height();
				v18._data0 = (*inst)->_opBase._a->getFrameData((*inst)->_opBase._a->_frame);
				v18._data1 = NULL; // (*inst)->_opBase._a->_cnv.field_8[(*inst)->_opBase._a->_frame];

				if ((*inst)->_flags & kInstMaskedPut) {
					uint16 _si = _vm->_gfx->queryMask((*inst)->_opB._value);
					_vm->_gfx->blitCnv(&v18, (*inst)->_opA._value, (*inst)->_opB._value, _si, Gfx::kBitBack);
					_vm->_gfx->blitCnv(&v18, (*inst)->_opA._value, (*inst)->_opB._value, _si, Gfx::kBit2);
				} else {
					_vm->_gfx->flatBlitCnv(&v18, (*inst)->_opA._value, (*inst)->_opB._value, Gfx::kBitBack);
					_vm->_gfx->flatBlitCnv(&v18, (*inst)->_opA._value, (*inst)->_opB._value, Gfx::kBit2);
				}
				break;

			case INST_END:	// exit
				if ((a->_flags & kFlagsLooping) == 0) {
					a->_flags &= ~kFlagsActing;
					_vm->runCommands(a->_commands, a);
				}
				a->_program->_ip = a->_program->_instructions.begin();
				goto label1;


			case INST_CALL: // call
				_callables[(*inst)->_opBase._index](0);
				break;

			case INST_WAIT: // wait
				if (_engineFlags & kEngineWalking) goto label1;
				break;

			case INST_SOUND:	// sound
				_activeZone = (*inst)->_opBase._z;
				break;

			default: {			// INST_SET, INST_X, INST_Y, INST_Z, INST_F
				int16 _si;
				if ((*inst)->_flags & kInstUsesLiteral) {
					_si = (*inst)->_opB._value;
				} else {
					_si = *(*inst)->_opB._pvalue;
				}

				if ((*inst)->_flags & kInstUsesLocal) {
					(*inst)->_opA._local->_value = _si;
				} else {
					*(*inst)->_opA._pvalue = _si;
				}
				}
				break;

			}

			inst++;
		}

		a->_program->_ip = ++inst;

label1:
		if (a->_flags & kFlagsCharacter)
			a->_z = a->_top + a->height();
	}

	_vm->sortAnimations();
	modCounter++;

	return;
}

void wrapLocalVar(LocalVariable *local) {
//	  printf("wrapLocalVar(v: %i, min: %i, max: %i)\n", local->_value, local->_min, local->_max);

	if (local->_value >= local->_max)
		local->_value = local->_min;
	if (local->_value < local->_min)
		local->_value = local->_max - 1;

	return;
}

int compareAnimationZ(const AnimationPointer &a1, const AnimationPointer &a2) {
	if (a1->_z == a2->_z) return 0;
	return (a1->_z < a2->_z ? -1 : 1);
}


void Parallaction::sortAnimations() {
	_char._ani._z = _char._ani.height() + _char._ani._top;
	_animations.sort(compareAnimationZ);
	return;
}

Animation::Animation() {
	_cnv = NULL;
	_program = NULL;
	_frame = 0;
	_z = 0;
}

Animation::~Animation() {
	if (_program)
		delete _program;

	if (_cnv)
		delete _cnv;
}

uint16 Animation::width() const {
	if (!_cnv) return 0;
	return _cnv->_width;
}

uint16 Animation::height() const {
	if (!_cnv) return 0;
	return _cnv->_height;
}

uint16 Animation::getFrameNum() const {
	if (!_cnv) return 0;
	return _cnv->_count;
}

byte* Animation::getFrameData(uint32 index) const {
	if (!_cnv) return NULL;
	return _cnv->getFramePtr(index);
}


Program::Program() {
	_loopCounter = 0;
	_locals = new LocalVariable[10];
}

Program::~Program() {
	delete[] _locals;
}


} // namespace Parallaction
