/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2020 Google
 *
 */
#include "common/file.h"
#include "hadesch/video.h"
#include "hadesch/pod_image.h"
#include "hadesch/tag_file.h"
#include "hadesch/hadesch.h"
#include "common/system.h"
#include "video/smk_decoder.h"
#include "audio/decoders/aiff.h"
#include "hadesch/pod_file.h"
#include "hadesch/baptr.h"

namespace Hadesch {

static const int kHeroBeltMinY = 378;
static const int kHeroBeltMaxY = 471;

static const TranscribedSound powerSounds[3][2] = {
	{
	    { "g0280nc0", _hs("That's where hero power of strength will be stored when you earn it") },
	    { "g0280ng0", _hs("The power of strength will let you overcome obstacles but you'll need a complete set of hero powers to use it") }
	},
	{
	    { "g0280nb0", _hs("That's where hero power of stealth will be stored when you earn it") },
	    { "g0280nf0", _hs("The power of stealth allows you to sneak past things but you'll need a complete set of hero powers to use it") }
	},
	{
	    { "g0280ne0", _hs("That's where hero power of wisdom will be stored when you earn it") },
	    { "g0280nh0", _hs("The power of wisdom will let you outwit and avoid deception but you'll need a complete set of hero powers to use it") }
	},
};

static Common::Array <PodImage> loadImageArray(const Common::String &name) {
	Common::SharedPtr<Common::SeekableReadStream> rs(g_vm->getWdPodFile()->getFileStream(name + ".pod"));
	PodFile pf2(name);
	pf2.openStore(rs);
	return pf2.loadImageArray();
}

static PodImage loadImage(const Common::String &name) {
	return loadImageArray(name)[0];
}

static const struct {
	const char *background;
	const char *iconNames;
	const char *icons;

	// TODO: figure out how the original handles
	// cursors.
	const char *iconCursors;
	const char *iconCursorsBright;

	const char *scrollBg;
	const char *scrollBgHades;
	const char *scrollTextCrete;
	const char *scrollTextTroyFemale;
	const char *scrollTextTroyMale;
	const char *scrollTextMedusa;
	const char *scrollTextHades;
	const char *powerImageWisdom;
	const char *powerImageStrength;
	const char *powerImageStealth;
} assetTable[3] = {
	// Warm
	{
		"g0010oa0",
		"g0091ba0",
		"g0091bb0",
		"g0091bc0",
		"g0093bc0",
		"g0015oy0",
		"g0015oy3",
		"g0015td0",
		"g0015te0",
		"g0015te3",
		"g0015tf0",
		"g0015tg0",
		"g0290oa0",
		"g0290ob0",
		"g0290oc0"
	},

	// Cold
	{
		"g0010ob0",
		"g0092ba0",
		"g0092bb0",
		"g0091bc0",
		"g0093bc0",
		"g0015oy2",
		"g0015oy5",
		"g0015td2",
		"g0015te2",
		"g0015te5",
		"g0015tf2",
		"g0015tg2",
		"g0092oa0",
		"g0092ob0",
		"g0092oc0"
	},

	// Cool
	{
		"g0010oc0",
		"g0093ba0",
		"g0093bb0",
		"g0091bc0",
		"g0093bc0",
		"g0015oy1",
		"g0015oy4",
		"g0015td1",
		"g0015te1",
		"g0015te4",
		"g0015tf1",
		"g0015tg1",
		"g0093oa0",
		"g0093ob0",
		"g0093oc0"
	}
};

HeroBelt::HeroBelt() {
	for (int i = 0; i < 3; i++) {
		_background[i] = loadImage(assetTable[i].background);
		_iconNames[i] = loadImageArray(assetTable[i].iconNames);
		_icons[i] = loadImageArray(assetTable[i].icons);
		_iconCursors[i] = loadImageArray(assetTable[i].iconCursors);
		_iconCursorsBright[i] = loadImageArray(assetTable[i].iconCursorsBright);
		_powerImages[0][i] = loadImageArray(assetTable[i].powerImageStrength);
		_powerImages[1][i] = loadImageArray(assetTable[i].powerImageStealth);
		_powerImages[2][i] = loadImageArray(assetTable[i].powerImageWisdom);

		_scrollBg[i] = loadImage(assetTable[i].scrollBg);
		_scrollBgHades[i] = loadImage(assetTable[i].scrollBgHades);
		_scrollTextCrete[i] = loadImage(assetTable[i].scrollTextCrete);
		_scrollTextTroyFemale[i] = loadImage(assetTable[i].scrollTextTroyFemale);
		_scrollTextTroyMale[i] = loadImage(assetTable[i].scrollTextTroyMale);
		_scrollTextMedusa[i] = loadImage(assetTable[i].scrollTextMedusa);
		_scrollTextHades[i] = loadImage(assetTable[i].scrollTextHades);
	}

	_branchOfLife = loadImageArray("v7150ba0");
	_overHeroBelt = false;
	_heroBeltY = kHeroBeltMaxY;
	_bottomEdge = false;
	_heroBeltSpeed = 0;
	_edgeStartTime = 0;
	_animateItem = kNone;
	_animateItemTargetSlot = -1;
	_hotZone = -1;
	_holdingItem = kNone;
	_holdingSlot = -1;
	_highlightTextIdx = -1;
	_showScroll = false;
	_hotZones.readHotzones(
		Common::SharedPtr<Common::SeekableReadStream>(g_vm->getWdPodFile()->getFileStream("HeroBelt.HOT")),
		true);
}

void HeroBelt::computeHotZone(int time, Common::Point mousePos, bool mouseEnabled) {
	bool wasBottomEdge = _bottomEdge;

	_overHeroBelt = false;
	_bottomEdge = false;

	_mousePos = mousePos;

	if (!mouseEnabled) {
		return;
	}

	_bottomEdge = mousePos.y > 460;
	_overHeroBelt = (_bottomEdge && _heroBeltSpeed < 0) || mousePos.y > _heroBeltY;

	if (!wasBottomEdge && _bottomEdge)
		_edgeStartTime = time;

	_currentTime = time;

	int wasHotZone = _hotZone;
	_hotZone = _hotZones.pointToIndex(mousePos);
	if (_hotZone >= 0 && wasHotZone < 0) {
		_startHotTime = time;
	}

	computeHighlight();
}

static bool isInFrieze() {
	Persistent *persistent = g_vm->getPersistent();
	return (persistent->_currentRoomId == kMinotaurPuzzle && persistent->_quest != kCreteQuest)
		|| (persistent->_currentRoomId == kTrojanHorsePuzzle && persistent->_quest != kTroyQuest)
		|| (persistent->_currentRoomId == kMedusaPuzzle && persistent->_quest != kMedusaQuest)
		|| (persistent->_currentRoomId == kFerrymanPuzzle && persistent->_quest != kRescuePhilQuest)
		|| (persistent->_currentRoomId == kMonsterPuzzle && persistent->_quest != kRescuePhilQuest);
}

void HeroBelt::render(Common::SharedPtr<GfxContext> context, int time, Common::Point viewPoint) {
	Persistent *persistent = g_vm->getPersistent();
	Common::Point beltPoint;

	// TODO: use beltopen hotzone instead?
	if (persistent->_currentRoomId == kMonsterPuzzle) {
		_heroBeltY = kHeroBeltMinY;
	} else {
		if (_bottomEdge && _heroBeltY == kHeroBeltMaxY
		    && (time > _edgeStartTime + 500)) {
			_heroBeltSpeed = -10;
		}

		if (!_overHeroBelt && _heroBeltY != kHeroBeltMaxY
		    && _animateItemTargetSlot == -1) {
			_showScroll = false;
			_heroBeltSpeed = +10;
		}

		if (_heroBeltSpeed != 0) {
			_heroBeltY += _heroBeltSpeed;
			if (_heroBeltY <= kHeroBeltMinY) {
				_heroBeltY = kHeroBeltMinY;
				_heroBeltSpeed = 0;
			}
			if (_heroBeltY >= kHeroBeltMaxY) {
				_heroBeltY = kHeroBeltMaxY;
				_heroBeltSpeed = 0;
			}
		}
	}

	_currentTime = time;

	beltPoint = Common::Point(0, _heroBeltY) + viewPoint;

	_background[_colour].render(context, beltPoint);

	if (_animateItem != kNone) {
		if (_currentTime > _animateItemStartTime + _animItemTime) {
			_animateItem = kNone;
			_animateItemTargetSlot = -1;
			_animItemCallbackEvent();
		}
	}

	if (_animateItem != kNone) {
		Common::Point target = computeSlotPoint(_animateItemTargetSlot, true);
		Common::Point delta1 = target - _animateItemStartPoint;
		int elapsed = _currentTime - _animateItemStartTime;
		double fraction = ((elapsed + 0.0) / _animItemTime);
		Common::Point deltaPartial = fraction * delta1;
		Common::Point cur = _animateItemStartPoint + deltaPartial;
		_iconCursors[_colour][_animateItem - 1].render(
			context, cur + viewPoint);
	}

	if (persistent->_currentRoomId == kMonsterPuzzle) {
		_icons[_colour][kNumberI].render(
				context,
				computeSlotPoint(0, false) + viewPoint
				+ Common::Point(0, 4));
		_icons[_colour][kNumberII].render(
				context,
				computeSlotPoint(1, false) + viewPoint);
		_icons[_colour][kNumberIII].render(
				context,
				computeSlotPoint(2, false) + viewPoint
				+ Common::Point(0, 4));
		for (unsigned i = 0; i < 3; i++) {
			_icons[_colour][_thunderboltFrame].render(
				context,
				computeSlotPoint(3 + i, false) + viewPoint);
		}

		_branchOfLife[_branchOfLifeFrame].render(
			context, beltPoint);

	} else {
		for (int i = 0; i < inventorySize; i++) {
			if (i == _animateItemTargetSlot || i == _holdingSlot
			    || persistent->_inventory[i] == kNone) {
				continue;
			}
			_icons[_colour][persistent->_inventory[i] - 1].render(
				context,
				computeSlotPoint(i, false) + viewPoint);
		}
	}

	_icons[_colour][persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints].render(
		context,
		computeSlotPoint(6, false) + viewPoint);

	if (isInFrieze())
		_icons[_colour][kReturnToWall].render(
			context,
			computeSlotPoint(7, false) + viewPoint);
	else
		_icons[_colour][kQuestScroll].render(
			context,
			computeSlotPoint(7, false) + viewPoint);

	_icons[_colour][kOptionsButton].render(
		context,
		computeSlotPoint(8, false) + viewPoint);

	if (_highlightTextIdx >= 0) {
		_iconNames[_colour][_highlightTextIdx].render(
			context, beltPoint);
	}

	for (unsigned i = 0; i < ARRAYSIZE(persistent->_powerLevel); i++)
		if (persistent->_powerLevel[i] > 0) {
			_powerImages[i][_colour][(int)i == _selectedPower].render(
				context, beltPoint);
		}

	if (_showScroll) {
		PodImage *bg = _scrollBg, *text = nullptr;
		switch (persistent->_quest) {
		case kCreteQuest:
			text = _scrollTextCrete;
			break;
		case kTroyQuest:
			if (persistent->_gender == kMale)
				text = _scrollTextTroyMale;
			else
				text = _scrollTextTroyFemale;
			break;
		case kMedusaQuest:
			text = _scrollTextMedusa;
			break;
		case kRescuePhilQuest:
			text = _scrollTextHades;
			bg = _scrollBgHades;
			break;
		case kEndGame:
		case kNoQuest:
		case kNumQuests:
			break;
		}
		bg[_colour].render(context, viewPoint);
		if (text != nullptr)
			text[_colour].render(
				context, viewPoint);
	}
}

bool HeroBelt::isPositionOverHeroBelt(Common::Point mousePos) const {
	return mousePos.y > _heroBeltY;
}

void HeroBelt::handleClick(Common::Point mousePos) {
	Persistent *persistent = g_vm->getPersistent();
	Common::String q = _hotZones.pointToName(mousePos);
	debug("handling belt click on <%s>", q.c_str());
	for (int i = 0; i < inventorySize; i++) {
		if (q == inventoryName(i)) {
			if (_holdingItem != kNone) {
				if (persistent->_inventory[i] != kNone &&
				    _holdingSlot != i) {
					g_vm->fallbackClick();
					return;
				}
				persistent->_inventory[_holdingSlot] = kNone;
				persistent->_inventory[i] = _holdingItem;
				_holdingItem = kNone;
				_holdingSlot = -1;
				return;
			}
			if (i == _animateItemTargetSlot
			    || persistent->_inventory[i] == kNone)
				return;
			_holdingItem = persistent->_inventory[i];
			_holdingSlot = i;
			return;
		}
	}

	if (q == "quest scroll") {
		if (isInFrieze())
			g_vm->moveToRoom(kWallOfFameRoom);
		else
			_showScroll = true;
	}

	if (q == "hints") {
		persistent->_hintsAreEnabled = !persistent->_hintsAreEnabled;
	}

	if (q == "options") {
		g_vm->enterOptions();
	}

	if (q == "strength")
		clickPower(kPowerStrength);
	if (q == "stealth")
		clickPower(kPowerStealth);
	if (q == "wisdom")
		clickPower(kPowerWisdom);
}

void HeroBelt::clickPower(HeroPower pwr) {
	Persistent *persistent = g_vm->getPersistent();
	Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();

	if (persistent->_currentRoomId == kMonsterPuzzle) {
		_selectedPower = pwr;
		return;
	}

	if (persistent->_quest == kRescuePhilQuest)
		return;

	room->playSpeech(powerSounds[pwr][!!persistent->_powerLevel[pwr]]);
}

void HeroBelt::computeHighlight() {
	Common::String hotZone = _hotZones.indexToName(_hotZone);
	Persistent *persistent = g_vm->getPersistent();
	for (int i = 0; i < inventorySize; i++) {
		if (hotZone == inventoryName(i)) {
			if (persistent->_currentRoomId == kMonsterPuzzle) {
				_highlightTextIdx = i < 3 ? kNumberI + i : kLightning1 + i - 3;
				return;
			} else {
				if (persistent->_inventory[i] != kNone &&
				    _holdingSlot != i) {
					_highlightTextIdx = persistent->_inventory[i] - 1;
					return;
				}
			}
		}
	}

	if (hotZone == "quest scroll") {
		// TODO: what's with 30 and 28?
		if (isInFrieze())
			_highlightTextIdx = kReturnToWall;
		else if (persistent->_quest == kRescuePhilQuest)
			_highlightTextIdx = kHadesScroll;
		else
			_highlightTextIdx = kQuestScroll;
		return;
	}

	if (hotZone == "stealth") {
		_highlightTextIdx = kPowerOfStealth;
		return;
	}

	if (hotZone == "strength") {
		_highlightTextIdx = kPowerOfStrength;
		return;
	}

	if (hotZone == "wisdom") {
		_highlightTextIdx = kPowerOfWisdom;
		return;
	}

	if (hotZone == "hints") {
		_highlightTextIdx = persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints;
		return;
	}

	if (hotZone == "options") {
		_highlightTextIdx = kOptionsButton;
		return;
	}

	_highlightTextIdx = -1;
}

void HeroBelt::placeToInventory(InventoryItem item, EventHandlerWrapper callbackEvent) {
	Persistent *persistent = g_vm->getPersistent();
	unsigned i;
	for (i = 0; i < inventorySize; i++) {
		if (persistent->_inventory[i] == kNone)
			break;
	}

	if (i == inventorySize) {
		debug("Out of inventory space");
		return;
	}

	persistent->_inventory[i] = item;
	_animateItem = item;
	_animItemCallbackEvent = callbackEvent;
	_animateItemStartPoint = _mousePos;
	_animateItemTargetSlot = i;
	_animateItemStartTime = _currentTime;
	_animItemTime = 2000;
	_heroBeltSpeed = -10;
}

void HeroBelt::removeFromInventory(InventoryItem item) {
	Persistent *persistent = g_vm->getPersistent();
	for (unsigned i = 0; i < inventorySize; i++) {
		if (persistent->_inventory[i] == item) {
			persistent->_inventory[i] = kNone;
		}
	}

	if (_holdingItem == item) {
		_holdingItem = kNone;
		_holdingSlot = -1;
	}

	if (_animateItem == item) {
		_animateItem = kNone;
		_animateItemTargetSlot = -1;
	}
}

void HeroBelt::clearHold() {
	_holdingItem = kNone;
	_holdingSlot = -1;
}

Common::Point HeroBelt::computeSlotPoint(int slot, bool fullyExtended) {
	Common::Point ret = Common::Point(19, 35);
	ret += Common::Point(0, (fullyExtended ? kHeroBeltMinY : _heroBeltY));
	ret += Common::Point(39 * slot, slot % 2 ? 4 : 0);
	if (slot >= 6) {
		ret += Common::Point(253, 0);
	}
	return ret;
}

int HeroBelt::getCursor(int time) {
	Common::String q = _hotZones.indexToName(_hotZone);
	Persistent *persistent = g_vm->getPersistent();
	if (q == "")
		return 0;
	for (unsigned i = 0; i < inventorySize; i++) {
		if (q == inventoryName(i)) {
			if ((int) i != _animateItemTargetSlot
			    && persistent->_inventory[i] != kNone)
				return (time - _startHotTime) / kDefaultSpeed % 3;
			return 0;
		}
	}

	return (time - _startHotTime) / kDefaultSpeed % 3;
}

Common::String HeroBelt::inventoryName(int slot) {
	return Common::String::format("inventory%d", slot);
}

bool HeroBelt::isHoldingItem() const {
	return _holdingItem != kNone;
}

const Graphics::Cursor *HeroBelt::getHoldingItemCursor(int cursorAnimationFrame) const {
	if ((cursorAnimationFrame / 2) % 2 == 1)
		return &_iconCursorsBright[_colour][_holdingItem - 1];
	else
		return &_iconCursors[_colour][_holdingItem - 1];
}

}
