/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001  Ludvig Strigeus
 * Copyright (C) 2001-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: https://svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-9-1/engines/simon/event.cpp $
 * $Id: event.cpp 23054 2006-06-12 06:47:33Z kirben $
 *
 */

#include "common/stdafx.h"

#include "simon/simon.h"
#include "simon/intern.h"

namespace Simon {

void SimonEngine::addTimeEvent(uint timeout, uint subroutine_id) {
	TimeEvent *te = (TimeEvent *)malloc(sizeof(TimeEvent)), *first, *last = NULL;
	time_t cur_time;

	time(&cur_time);

	te->time = cur_time + timeout - _gameStoppedClock;
	if (_clockStopped)
		te->time -= ((uint32)time(NULL) - _clockStopped);
	te->subroutine_id = subroutine_id;

	first = _firstTimeStruct;
	while (first) {
		if (te->time <= first->time) {
			if (last) {
				last->next = te;
				te->next = first;
				return;
			}
			te->next = _firstTimeStruct;
			_firstTimeStruct = te;
			return;
		}

		last = first;
		first = first->next;
	}

	if (last) {
		last->next = te;
		te->next = NULL;
	} else {
		_firstTimeStruct = te;
		te->next = NULL;
	}
}

void SimonEngine::delTimeEvent(TimeEvent *te) {
	TimeEvent *cur;

	if (te == _pendingDeleteTimeEvent)
		_pendingDeleteTimeEvent = NULL;

	if (te == _firstTimeStruct) {
		_firstTimeStruct = te->next;
		free(te);
		return;
	}

	cur = _firstTimeStruct;
	if (cur == NULL)
		error("delTimeEvent: none available");

	for (;;) {
		if (cur->next == NULL)
			error("delTimeEvent: no such te");
		if (te == cur->next) {
			cur->next = te->next;
			free(te);
			return;
		}
		cur = cur->next;
	}
}

void SimonEngine::invokeTimeEvent(TimeEvent *te) {
	Subroutine *sub;

	_scriptVerb = 0;

	if (_runScriptReturn1)
		return;

	sub = getSubroutineByID(te->subroutine_id);
	if (sub != NULL)
		startSubroutineEx(sub);

	_runScriptReturn1 = false;
}

void SimonEngine::killAllTimers() {
	TimeEvent *cur, *next;

	for (cur = _firstTimeStruct; cur; cur = next) {
		next = cur->next;
		delTimeEvent(cur);
	}
}

bool SimonEngine::kickoffTimeEvents() {
	time_t cur_time;
	TimeEvent *te;
	bool result = false;

	if (_clockStopped)
		return result;

	time(&cur_time);
	cur_time -= _gameStoppedClock;

	while ((te = _firstTimeStruct) != NULL && te->time <= (uint32)cur_time) {
		result = true;
		_pendingDeleteTimeEvent = te;
		invokeTimeEvent(te);
		if (_pendingDeleteTimeEvent) {
			_pendingDeleteTimeEvent = NULL;
			delTimeEvent(te);
		}
	}

	return result;
}

void SimonEngine::addVgaEvent(uint16 num, const byte *code_ptr, uint16 cur_sprite, uint16 curZoneNum, int32 param) {
	VgaTimerEntry *vte;

	// When Simon talks to the Golum about stew in French version of
	// Simon the Sorcerer 1 the code_ptr is at wrong location for
	// sprite 200. This  was a bug in the original game, which
	// caused several glitches in this scene.
	// We work around the problem by correcting the code_ptr for sprite
	// 200 in this scene, if it is wrong.
	if (getGameType() == GType_SIMON1 && _language == Common::FR_FRA &&
		(code_ptr - _vgaBufferPointers[curZoneNum].vgaFile1 == 4) && (cur_sprite == 200) && (curZoneNum == 2))
		code_ptr += 0x66;

	_lockWord |= 1;

	for (vte = _vgaTimerList; vte->delay; vte++) {
	}

	vte->delay = num;
	vte->script_pointer = code_ptr;
	vte->sprite_id = cur_sprite;
	vte->cur_vga_file = curZoneNum;
	vte->param = param;

	_lockWord &= ~1;
}

void SimonEngine::deleteVgaEvent(VgaTimerEntry * vte) {
	_lockWord |= 1;

	if (vte + 1 <= _nextVgaTimerToProcess) {
		_nextVgaTimerToProcess--;
	}

	do {
		memcpy(vte, vte + 1, sizeof(VgaTimerEntry));
		vte++;
	} while (vte->delay);

	_lockWord &= ~1;
}

void SimonEngine::processVgaEvents() {
	VgaTimerEntry *vte = _vgaTimerList;
	uint timer = (getGameType() == GType_FF) ? 5 : 1;

	_vgaTickCounter++;

	while (vte->delay) {
		vte->delay -= timer;
		if (vte->delay <= 0) {
			uint16 curZoneNum = vte->cur_vga_file;
			uint16 cur_sprite = vte->sprite_id;
			const byte *script_ptr = vte->script_pointer;
			int32 param = vte->param;

			_nextVgaTimerToProcess = vte + 1;
			deleteVgaEvent(vte);

			if (getGameType() == GType_FF && script_ptr == NULL) {
				panEvent(curZoneNum, cur_sprite, param);
			} else if (getGameType() == GType_SIMON2 && script_ptr == NULL) {
				scrollEvent();
			} else {
				animateEvent(script_ptr, curZoneNum, cur_sprite);
			}
			vte = _nextVgaTimerToProcess;
		} else {
			vte++;
		}
	}
}

void SimonEngine::animateEvent(const byte *code_ptr, uint16 curZoneNum, uint16 cur_sprite) {
	VgaPointersEntry *vpe;

	_vgaCurSpriteId = cur_sprite;

	_vgaCurZoneNum = curZoneNum;
	_zoneNumber = curZoneNum;
	vpe = &_vgaBufferPointers[curZoneNum];

	_curVgaFile1 = vpe->vgaFile1;
	_curVgaFile2 = vpe->vgaFile2;
	_curSfxFile = vpe->sfxFile;

	_vcPtr = code_ptr;

	runVgaScript();
}

void SimonEngine::panEvent(uint16 curZoneNum, uint16 cur_sprite, int32 param) {
	_vgaCurSpriteId = cur_sprite;
	_vgaCurZoneNum = curZoneNum;

	VgaSprite *vsp = findCurSprite();

	param &= 0x10;

	int32 pan = (vsp->x - _scrollX + param) * 8 - 2560;
	if (pan < -10000)
		pan = -10000;
	if (pan > 10000)
		pan = 10000;

	//setSfxPan(param, pan);

	if (pan != 0)
		addVgaEvent(10, NULL, _vgaCurSpriteId, _vgaCurZoneNum); /* pan event */
	debug(0, "panEvent: param %d pan %d", param, pan);
}

void SimonEngine::scrollEvent() {
	if (_scrollCount == 0)
		return;

	if (getGameType() == GType_FF) {
		if (_scrollCount < 0) {
			if (_scrollFlag != -8) {
				_scrollFlag = -8;
				_scrollCount += 8;
			}
		} else {
			if (_scrollFlag != 8) {
				_scrollFlag = 8;
				_scrollCount -= 8;
			}
		}
	} else {
		if (_scrollCount < 0) {
			if (_scrollFlag != -1) {
				_scrollFlag = -1;
				if (++_scrollCount == 0)
					return;
			}
		} else {
			if (_scrollFlag != 1) {
				_scrollFlag = 1;
				if (--_scrollCount == 0)
					return;
			}
		}

		addVgaEvent(6, NULL, 0, 0); /* scroll event */
	}
}

void SimonEngine::timer_callback() {
	if (_timer5 != 0) {
		_syncFlag2 = true;
		_timer5--;
	} else {
		timer_proc1();
	}
}

void SimonEngine::timer_proc1() {
	_timer4++;

	if (_lockWord & 0x80E9 || _lockWord & 2)
		return;

	_syncCount++;

	_lockWord |= 2;

	if (!(_lockWord & 0x10)) {
		if (getGameType() == GType_FF) {
			_syncFlag2 ^= 1;
			if (!_syncFlag2) {
				processVgaEvents();
			} else {
				// Double speed on Oracle
				if (getBitFlag(99)) {
					processVgaEvents();
				} else if (_scrollCount == 0) {
					_lockWord &= ~2;
					return;
				}
			}
		} else {
			processVgaEvents();
			processVgaEvents();
			_syncFlag2 ^= 1;
			_cepeFlag ^= 1;
			if (!_cepeFlag)
				processVgaEvents();

			if (_mouseHideCount != 0 && _syncFlag2) {
				_lockWord &= ~2;
				return;
			}
		}
	}

	if (getGameType() == GType_FF)
		_moviePlay->nextFrame();

	animateSprites();
	if (_drawImagesDebug)
		animateSpritesDebug();

	if (_copyPartialMode == 1) {
		fillBackFromFront(80, 46, 208 - 80, 94 - 46);
	}

	if (_copyPartialMode == 2) {
		if (getGameType() == GType_FF) {
			fillFrontFromBack(0, 0, _screenWidth, _screenHeight);
		} else {
			fillFrontFromBack(176, 61, _screenWidth - 176, 134 - 61);
		}
		_copyPartialMode = 0;
	}

	if (_updateScreen) {
		if (getGameType() == GType_FF) {
			if (!getBitFlag(78)) {
				oracleLogo();
			}
			if (getBitFlag(76)) {
				swapCharacterLogo();
			}
		}
		handleMouseMoved();
		dx_update_screen_and_palette();
		_updateScreen = false;
	}

	_lockWord &= ~2;
}

} // End of namespace Simon
