/* 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.
 *
 */

#include "engines/kyra/eobcommon.h"
#include "engines/kyra/timer.h"

#include "common/system.h"

#ifdef ENABLE_EOB

namespace Kyra {

#define TimerV2(x) new Common::Functor1Mem<int, void, EoBCoreEngine>(this, &EoBCoreEngine::x)

void EoBCoreEngine::setupTimers() {
	_timer->addTimer(0, TimerV2(timerProcessCharacterExchange), 9, false);
	_timer->addTimer(1, TimerV2(timerProcessFlyingObjects), 3, true);
	_timer->addTimer(0x20, TimerV2(timerProcessMonsters), 20, true);
	_timer->addTimer(0x21, TimerV2(timerProcessMonsters), 20, true);
	_timer->addTimer(0x22, TimerV2(timerProcessMonsters), 20, true);
	_timer->addTimer(0x23, TimerV2(timerProcessMonsters), 20, true);
	_timer->setNextRun(0x20, _system->getMillis());
	_timer->setNextRun(0x21, _system->getMillis() + 7 * _tickLength);
	_timer->setNextRun(0x22, _system->getMillis() + 14 * _tickLength);
	_timer->setNextRun(0x23, _system->getMillis() + 14 * _tickLength);
	_timer->addTimer(0x30, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(0x31, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(0x32, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(0x33, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(0x34, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(0x35, TimerV2(timerSpecialCharacterUpdate), 50, false);
	_timer->addTimer(4, TimerV2(timerProcessDoors), 5, true);
	_timer->addTimer(5, TimerV2(timerUpdateTeleporters), 10, true);
	_timer->addTimer(6, TimerV2(timerUpdateFoodStatus), 1080, true);
	_timer->addTimer(7, TimerV2(timerUpdateMonsterIdleAnim), 25, true);
	_timer->resetNextRun();
}

void EoBCoreEngine::setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer) {
	uint32 ntime = _system->getMillis() + countdown * _tickLength;
	uint8 timerId = 0x30 | (charIndex & 0x0F);
	EoBCharacter *c = &_characters[charIndex];

	if (!_timer->isEnabled(timerId)) {
		c->timers[0] = ntime;
		c->events[0] = evnt;
		_timer->setCountdown(timerId, countdown);
		enableTimer(timerId);
		return;
	}

	if (ntime < _timer->getNextRun(timerId))
		_timer->setNextRun(timerId, ntime);
	_timer->resetNextRun();

	if (updateExistingTimer) {
		bool updated = false;
		int d = -1;

		for (int i = 0; i < 10 && updated == false; i++) {
			if (d == -1 && !c->timers[i])
				d = i;

			if (!updated && c->events[i] == evnt) {
				d = i;
				updated = true;
			}
		}

		assert(d != -1);

		c->timers[d] = ntime;
		c->events[d] = evnt;
	} else {
		for (int i = 0; i < 10; i++) {
			if (c->timers[i])
				continue;
			c->timers[i] = ntime;
			c->events[i] = evnt;
			return;
		}
	}
}

void EoBCoreEngine::deleteCharEventTimer(int charIndex, int evnt) {
	EoBCharacter *c = &_characters[charIndex];
	for (int i = 0; i < 10; i++) {
		if (c->events[i] == evnt) {
			c->events[i] = 0;
			c->timers[i] = 0;
		}
	}
	setupCharacterTimers();
}

void EoBCoreEngine::setupCharacterTimers() {
	for (int i = 0; i < 6; i++) {
		EoBCharacter *c = &_characters[i];
		if (!testCharacter(i, 1))
			continue;

		uint32 nextTimer = 0xFFFFFFFF;

		for (int ii = 0; ii < 10; ii++) {
			if (c->timers[ii] && c->timers[ii] < nextTimer)
				nextTimer = c->timers[ii];
		}
		uint32 ctime = _system->getMillis();

		if (nextTimer == 0xFFFFFFFF)
			_timer->disable(0x30 | i);
		else {
			enableTimer(0x30 | i);
			_timer->setCountdown(0x30 | i, (nextTimer - ctime) / _tickLength);
		}
	}
	_timer->resetNextRun();
}

void EoBCoreEngine::advanceTimers(uint32 millis) {
	uint32 ct = _system->getMillis();
	for (int i = 0; i < 6; i++) {
		EoBCharacter *c = &_characters[i];
		for (int ii = 0; ii < 10; ii++) {
			if (c->timers[ii] > ct) {
				uint32 chrt = c->timers[ii] - ct;
				c->timers[ii] = chrt > millis ? ct + chrt - millis : ct;
			}
		}
	}

	setupCharacterTimers();

	if (_scriptTimersMode & 1) {
		for (int i = 0; i < _scriptTimersCount; i++) {
			if (_scriptTimers[i].next > ct) {
				uint32 chrt = _scriptTimers[i].next - ct;
				_scriptTimers[i].next = chrt > millis ? ct + chrt - millis : ct;
			}
		}
	}

	for (int i = 0; i < 5; i++) {
		if (_wallsOfForce[i].duration > ct) {
			uint32 chrt = _wallsOfForce[i].duration - ct;
			_wallsOfForce[i].duration = chrt > millis ? ct + chrt - millis : ct;
		}
	}
}

void EoBCoreEngine::timerProcessCharacterExchange(int timerNum) {
	_charExchangeSwap ^= 1;
	if (_charExchangeSwap) {
		int index = _exchangeCharacterId;
		_exchangeCharacterId = -1;
		gui_drawCharPortraitWithStats(index);
		_exchangeCharacterId = index;
	} else {
		gui_drawCharPortraitWithStats(_exchangeCharacterId);
	}
}

void EoBCoreEngine::timerProcessFlyingObjects(int timerNum) {
	static const uint8 dirPosIndex[] = { 0x82, 0x83, 0x00, 0x01, 0x01, 0x80, 0x03, 0x82, 0x02, 0x03, 0x80, 0x81, 0x81, 0x00, 0x83, 0x02 };
	for (int i = 0; i < 10; i++) {
		EoBFlyingObject *fo = &_flyingObjects[i];
		if (!fo->enable)
			continue;

		bool endFlight = fo->distance == 0;

		uint8 pos = dirPosIndex[(fo->direction << 2) + (fo->curPos & 3)];
		uint16 bl = fo->curBlock;
		bool newBl = (pos & 0x80) ? true : false;

		if (newBl) {
			bl = calcNewBlockPosition(fo->curBlock, fo->direction);
			pos &= 3;
			fo->starting = 0;
		}

		if (updateObjectFlight(fo, bl, pos)) {
			if (newBl)
				runLevelScript(bl, 0x10);
			if (updateFlyingObjectHitTest(fo, bl, pos))
				endFlight = true;
		} else {
			if (fo->flags & 0x20) {
				if (!updateFlyingObjectHitTest(fo, fo->curBlock, fo->curPos))
					explodeObject(fo, fo->curBlock, fo->item);
			}
			endFlight = true;
		}

		if (endFlight)
			endObjectFlight(fo);

		_sceneUpdateRequired = true;
	}
}

void EoBCoreEngine::timerProcessMonsters(int timerNum) {
	updateMonsters(timerNum & 0x0F);
}

void EoBCoreEngine::timerSpecialCharacterUpdate(int timerNum) {
	int charIndex = timerNum & 0x0F;
	EoBCharacter *c = &_characters[charIndex];
	uint32 ctime =  _system->getMillis();

	for (int i = 0; i < 10; i++) {
		if (!c->timers[i])
			continue;
		if (c->timers[i] > ctime)
			continue;

		c->timers[i] = 0;
		int evt = c->events[i];

		if (evt < 0) {
			removeCharacterEffect(-evt, charIndex, 1);
			continue;
		}

		int od = _screen->curDimIndex();
		Screen::FontId of = _screen->setFont(Screen::FID_6_FNT);
		_screen->setScreenDim(7);

		switch (evt) {
		case 2:
		case 3:
			setCharEventTimer(charIndex, (c->effectFlags & 0x10000) ? 9 : 36, evt + 2, 1);
			// fall through
		case 0:
		case 1:
		case 4:
		case 5:
			setWeaponSlotStatus(charIndex, evt / 2, evt & 1);
			break;

		case 6:
			c->damageTaken = 0;
			gui_drawCharPortraitWithStats(charIndex);
			break;

		case 7:
			_txt->printMessage(_characterStatusStrings7[0], -1, c->name);
			c->strengthCur = c->strengthMax;
			c->strengthExtCur = c->strengthExtMax;
			if (_currentControlMode == 2)
				gui_drawCharPortraitWithStats(charIndex);
			break;

		case 8:
			if (c->flags & 2) {
				calcAndInflictCharacterDamage(charIndex, 0, 0, 5, 0x400, 5, 3);
				setCharEventTimer(charIndex, 546, 8, 1);
			} else {
				c->flags &= ~2;
				gui_drawCharPortraitWithStats(charIndex);
			}
			break;

		case 9:
			if (c->flags & 4) {
				_txt->printMessage(_characterStatusStrings9[0], -1, c->name);
				c->flags &= ~4;
				gui_drawCharPortraitWithStats(charIndex);
			}
			break;

		case 11:
			if (c->disabledSlots & 4) {
				c->disabledSlots &= ~4;
				if (_openBookChar == charIndex && _updateFlags)
					gui_drawSpellbook();
			}
			break;

		case 12:
			c->effectFlags &= ~0x1000;
			if (_characterStatusStrings12)
				_txt->printMessage(_characterStatusStrings12[0], -1, c->name);
			break;

		default:
			break;
		}

		_screen->setScreenDim(od);
		_screen->setFont(of);
	}

	uint32 nextTimer = 0xFFFFFFFF;
	for (int i = 0; i < 10; i++) {
		if (c->timers[i] && c->timers[i] < nextTimer)
			nextTimer = c->timers[i];
	}

	if (nextTimer == 0xFFFFFFFF)
		_timer->disable(timerNum);
	else
		_timer->setCountdown(timerNum, (nextTimer - ctime) / _tickLength);
}

void EoBCoreEngine::timerUpdateTeleporters(int timerNum) {
	_teleporterPulse ^= 1;
	for (int i = 0; i < 18; i++) {
		uint8 w = _visibleBlocks[i]->walls[_sceneDrawVarDown];
		if ((w == _teleporterWallId) || (_flags.gameID == GI_EOB2 && w == 74)) {
			_sceneUpdateRequired = true;
			return;
		}
	}
}

void EoBCoreEngine::timerUpdateFoodStatus(int timerNum) {
	for (int i = 0; i < 6; i++) {
		// Ring of Sustenance check
		if (checkInventoryForRings(i, 2))
			continue;
		EoBCharacter *c = &_characters[i];
		if (c->food != 0 && c->flags & 1 && c->hitPointsCur > -10) {
			c->food--;
			gui_drawFoodStatusGraph(i);
		}
	}
}

void EoBCoreEngine::timerUpdateMonsterIdleAnim(int timerNum) {
	for (int i = 0; i < 30; i++) {
		EoBMonsterInPlay *m = &_monsters[i];
		if (m->mode == 7 || m->mode == 10 || (m->flags & 0x20) || (rollDice(1, 2, 0) != 1))
			continue;
		m->idleAnimState = (rollDice(1, 2, 0) << 4) | rollDice(1, 2, 0);
		checkSceneUpdateNeed(m->block);
	}
}

} // End of namespace Kyra

#endif // ENABLE_EOB
