/* 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 "mohawk/riven_stacks/tspit.h"

#include "mohawk/cursors.h"
#include "mohawk/riven.h"
#include "mohawk/riven_card.h"
#include "mohawk/riven_graphics.h"
#include "mohawk/riven_inventory.h"
#include "mohawk/riven_video.h"

#include "common/events.h"

namespace Mohawk {
namespace RivenStacks {

TSpit::TSpit(MohawkEngine_Riven *vm) :
		DomeSpit(vm, kStackTspit, "tsliders.190", "tsliderbg.190") {

	REGISTER_COMMAND(TSpit, xtexterior300_telescopedown);
	REGISTER_COMMAND(TSpit, xtexterior300_telescopeup);
	REGISTER_COMMAND(TSpit, xtisland390_covercombo);
	REGISTER_COMMAND(TSpit, xtatrusgivesbooks);
	REGISTER_COMMAND(TSpit, xtchotakesbook);
	REGISTER_COMMAND(TSpit, xthideinventory);
	REGISTER_COMMAND(TSpit, xt7500_checkmarbles);
	REGISTER_COMMAND(TSpit, xt7600_setupmarbles);
	REGISTER_COMMAND(TSpit, xt7800_setup);
	REGISTER_COMMAND(TSpit, xdrawmarbles);
	REGISTER_COMMAND(TSpit, xtakeit);
	REGISTER_COMMAND(TSpit, xtscpbtn);
	REGISTER_COMMAND(TSpit, xtisland4990_domecheck);
	REGISTER_COMMAND(TSpit, xtisland5056_opencard);
	REGISTER_COMMAND(TSpit, xtisland5056_resetsliders);
	REGISTER_COMMAND(TSpit, xtisland5056_slidermd);
	REGISTER_COMMAND(TSpit, xtisland5056_slidermw);
	REGISTER_COMMAND(TSpit, xtatboundary);
}

void TSpit::xtexterior300_telescopedown(const ArgumentArray &args) {
	// First, show the button movie
	RivenVideo *buttonVideo = _vm->_video->openSlot(3);
	buttonVideo->seek(0);
	buttonVideo->enable();
	buttonVideo->playBlocking();

	// Don't do anything else if the telescope power is off
	if (_vm->_vars["ttelevalve"] == 0)
		return;

	uint32 &telescopePos = _vm->_vars["ttelescope"];
	uint32 &telescopeCover = _vm->_vars["ttelecover"];

	if (telescopePos != 1) {
		// We're not at the bottom, and we can move down again

		// Play a piece of the moving down movie
		static const uint32 timeIntervals[] = { 4320, 3440, 2660, 1760, 880, 0 };
		uint16 movieCode = telescopeCover ? 1 : 2;
		RivenVideo *video = _vm->_video->openSlot(movieCode);
		video->enable();
		video->seek(timeIntervals[telescopePos]);
		_vm->_sound->playCardSound("tTeleMove"); // Play the moving sound
		video->playBlocking(timeIntervals[telescopePos - 1]);
		video->stop();

		// Now move the telescope down a position and refresh
		telescopePos--;
		_vm->getCard()->enter(false);
		return;
	}

	// We're at the bottom, which means one of two things can happen...
	if (telescopeCover == 1 && _vm->_vars["ttelepin"] == 1) {
		// ...if the cover is open and the pin is up, the game is now over.
		xtopenfissure();
	} else {
		// ...the telescope can't move down anymore.
		// Play the sound of not being able to move
		_vm->_sound->playCardSound("tTelDnMore");
	}
}

void TSpit::xtexterior300_telescopeup(const ArgumentArray &args) {
	// First, show the button movie
	RivenVideo *buttonVideo = _vm->_video->openSlot(3);
	buttonVideo->seek(0);
	buttonVideo->enable();
	buttonVideo->playBlocking();

	// Don't do anything else if the telescope power is off
	if (_vm->_vars["ttelevalve"] == 0)
		return;

	uint32 &telescopePos = _vm->_vars["ttelescope"];

	// Check if we can't move up anymore
	if (telescopePos == 5) {
		// Play the sound of not being able to move
		_vm->_sound->playCardSound("tTelDnMore");
		return;
	}

	// Play a piece of the moving up movie
	static const uint32 timeIntervals[] = { 0, 800, 1680, 2560, 3440, 4320 };
	uint16 movieCode = _vm->_vars["ttelecover"] ? 4 : 5;
	RivenVideo *video = _vm->_video->openSlot(movieCode);
	video->enable();
	video->seek(timeIntervals[telescopePos - 1]);
	_vm->_sound->playCardSound("tTeleMove"); // Play the moving sound
	video->playBlocking(timeIntervals[telescopePos]);
	video->stop();

	// Now move the telescope up a position and refresh
	telescopePos++;
	_vm->getCard()->enter(false);
}

void TSpit::xtopenfissure() {
	if (_vm->_vars["pcage"] == 2) {
		// The best ending: Catherine is free, Gehn is trapped, Atrus comes to rescue you.
		// And now we fall back to Earth... all the way...
		_vm->getCard()->playMovie(8);
		runEndGame(8, 5000);
	} else if (_vm->_vars["agehn"] == 4) {
		// The ok ending: Catherine is still trapped, Gehn is trapped, Atrus comes to rescue you.
		// Nice going! Catherine and the islanders are all dead now! Just go back to your home...
		_vm->getCard()->playMovie(9);
		runEndGame(9, 5000);
	} else if (_vm->_vars["atrapbook"] == 1) {
		// The bad ending: Catherine is trapped, Gehn is free, Atrus gets shot by Gehn,
		// And then you get shot by Cho. Nice going! Catherine and the islanders are dead
		// and you have just set Gehn free from Riven, not to mention you're dead.
		_vm->getCard()->playMovie(10);
		runEndGame(10, 5000);
	} else {
		// The impossible ending: You don't have Catherine's journal and yet you were somehow
		// able to open the hatch on the telescope. The game provides an ending for those who
		// cheat, load a saved game with the combo, or just guess the telescope combo. Atrus
		// doesn't come and you just fall into the fissure.
		_vm->getCard()->playMovie(11);
		runEndGame(11, 5000);
	}
}

void TSpit::xtisland390_covercombo(const ArgumentArray &args) {
	// Called when clicking the telescope cover buttons. args[0] is the button number (1...5).
	uint32 &correctDigits = _vm->_vars["tcovercombo"];

	if (correctDigits < 5 && args[0] == getComboDigit(_vm->_vars["tcorrectorder"], correctDigits))
		correctDigits++;
	else
		correctDigits = 0;

	// If we have hit the correct 5 buttons in a row, activate the hotspot to open up the
	// telescope cover.
	RivenHotspot *openCover = _vm->getCard()->getHotspotByName("openCover");
	openCover->enable(correctDigits == 5);
}

// Atrus' Journal and Trap Book are added to inventory
void TSpit::xtatrusgivesbooks(const ArgumentArray &args) {
	// Give the player Atrus' Journal and the Trap book
}

// Trap Book is removed from inventory
void TSpit::xtchotakesbook(const ArgumentArray &args) {
	// And now Cho takes the trap book
}

void TSpit::xthideinventory(const ArgumentArray &args) {
}

// Marble Puzzle related constants
static const uint32 kMarbleCount = 6;
static const int kSmallMarbleWidth = 4;
static const int kSmallMarbleHeight = 2;
//static const int kLargeMarbleSize = 8;
static const int kMarbleHotspotSize = 13;
static const char *s_marbleNames[] = { "tred", "torange", "tyellow", "tgreen", "tblue", "tviolet" };

// Marble Puzzle helper functions
// The y portion takes the upper 16 bits, while the x portion takes the lower 16 bits
static void setMarbleX(uint32 &var, byte x) {
	var = (var & 0xff00) | (x + 1);
}

static void setMarbleY(uint32 &var, byte y) {
	var = ((y + 1) << 16) | (var & 0xff);
}

static byte getMarbleX(uint32 var) {
	return (var & 0xff) - 1;
}

static byte getMarbleY(uint32 var) { // Give that that Y you old hag! </bad Seinfeld reference>
	return ((var >> 16) & 0xff) - 1;
}

static Common::Rect generateMarbleGridRect(uint16 x, uint16 y) {
	// x/y in terms of 0!
	static const int marbleGridOffsetX[] = { 134, 202, 270, 338, 406 };
	static const int marbleGridOffsetY[] = {  24,  92, 159, 227, 295 };

	uint16 offsetX = marbleGridOffsetX[x / 5] + (x % 5) * kMarbleHotspotSize;
	uint16 offsetY = marbleGridOffsetY[y / 5] + (y % 5) * kMarbleHotspotSize;
	return Common::Rect(offsetX, offsetY, offsetX + kMarbleHotspotSize, offsetY + kMarbleHotspotSize);
}

void TSpit::xt7500_checkmarbles(const ArgumentArray &args) {
	// Set apower if the marbles are in their correct spot.

	bool valid = true;
	static const uint32 marbleFinalValues[] = { 1114121, 1441798, 0, 65552, 65558, 262146 };

	for (uint16 i = 0; i < kMarbleCount; i++)
		if (_vm->_vars[s_marbleNames[i]] != marbleFinalValues[i]) {
			valid = false;
			break;
		}

	// If we have the correct combo, activate the power and reset the marble positions
	// Otherwise, make sure the power is off
	if (valid) {
		_vm->_vars["apower"] = 1;
		for (uint16 i = 0; i < kMarbleCount; i++)
			_vm->_vars[s_marbleNames[i]] = 0;
	} else
		_vm->_vars["apower"] = 0;
}

void TSpit::xt7600_setupmarbles(const ArgumentArray &args) {
	// Draw the small marbles when we're a step away from the waffle

	// Convert from marble X coordinate to screen X coordinate
	static const uint16 xPosOffsets[] = {
			246, 245, 244, 243, 243, 241, 240, 240, 239, 238, 237, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 226, 225
	};

	// Convert from marble Y coordinate to screen Y coordinate
	static const uint16 yPosOffsets[] = {
			261, 263, 265, 267, 268, 270, 272, 274, 276, 278, 281, 284, 285, 288, 290, 293, 295, 298, 300, 303, 306, 309, 311, 314, 316
	};

	// Handle spacing for y coordinates due to the angle
	static const double yAdjusts[] = {
			4.56, 4.68, 4.76, 4.84, 4.84, 4.96, 5.04, 5.04, 5.12, 5.2, 5.28, 5.28, 5.36, 5.44, 5.4, 5.6, 5.72, 5.8, 5.88, 5.96, 6.04, 6.12, 6.2, 6.2, 6.28
	};

	// Waffle state of 0 is up, 1 down
	bool waffleDown = _vm->_vars["twaffle"] != 0;

	// Note that each of the small marble images is exactly 4x2
	// The original seems to scale the marble images from extras.mhk, but
	// we're using the pre-scaled images in the stack.
	uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, buildCardResourceName("tsmallred"));

	for (uint16 i = 0; i < kMarbleCount; i++) {
		uint32 var = _vm->_vars[s_marbleNames[i]];

		if (var == 0) {
			// The marble is still in its initial place
			// (Note that this is still drawn even if the waffle is down)
			static const uint16 defaultX[] = { 375, 377, 379, 381, 383, 385 };
			static const uint16 defaultY[] = { 253, 257, 261, 265, 268, 273 };
			_vm->_gfx->copyImageToScreen(baseBitmapId + i, defaultX[i], defaultY[i], defaultX[i] + kSmallMarbleWidth, defaultY[i] + kSmallMarbleHeight);
		} else if (waffleDown) {
			// The marble is on the grid and the waffle is down
			// (Nothing to draw here)
		} else {
			// The marble is on the grid and the waffle is up
			int marbleX = (int)floor(getMarbleX(var) * yAdjusts[getMarbleY(var)] + xPosOffsets[getMarbleY(var)] + 0.5);
			int marbleY = yPosOffsets[getMarbleY(var)];
			_vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight);
		}
	}
}

void TSpit::setMarbleHotspots() {
	// Set the hotspots
	for (uint16 i = 0; i < kMarbleCount; i++) {
		uint32 marblePos = _vm->_vars[s_marbleNames[i]];
		RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);

		if (marblePos == 0) // In the receptacle
			marbleHotspot->setRect(_marbleBaseHotspots[i]);
		else                 // On the grid
			marbleHotspot->setRect(generateMarbleGridRect(getMarbleX(marblePos), getMarbleY(marblePos)));
	}
}

void TSpit::xt7800_setup(const ArgumentArray &args) {
	// First, let's store the base receptacle hotspots for the marbles
	if (_marbleBaseHotspots.empty())
		for (uint16 i = 0; i < kMarbleCount; i++) {
			RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
			_marbleBaseHotspots.push_back(marbleHotspot->getRect());
		}

	// Move the marble hotspots based on their position variables
	setMarbleHotspots();
	_vm->_vars["themarble"] = 0;
}

void TSpit::drawMarbles() {
	_vm->_gfx->beginScreenUpdate();
	for (uint32 i = 0; i < kMarbleCount; i++) {
		// Don't draw the marble if we're holding it
		if (_vm->_vars["themarble"] - 1 == i)
			continue;

		RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);

		Common::Rect rect = marbleHotspot->getRect();
		// Trim the rect down a bit
		rect.left += 3;
		rect.top += 3;
		rect.right -= 2;
		rect.bottom -= 2;
		_vm->_gfx->drawExtrasImage(i + 200, rect);
	}
	_vm->_gfx->applyScreenUpdate();
}

void TSpit::xdrawmarbles(const ArgumentArray &args) {
	// Draw marbles in the closeup
	drawMarbles();
}

void TSpit::xtakeit(const ArgumentArray &args) {
	// Pick up and move a marble

	// First, let's figure out what marble we're now holding
	uint32 &marble = _vm->_vars["themarble"];
	marble = 0;

	for (uint32 i = 0; i < kMarbleCount; i++) {
		RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
		if (marbleHotspot->containsPoint(getMousePosition())) {
			marble = i + 1;
			break;
		}
	}

	// xtakeit() shouldn't be called if we're not on a marble hotspot
	assert(marble != 0);

	// Redraw the background
	_vm->getCard()->drawPicture(1);

	// Loop until the player lets go (or quits)
	while (mouseIsDown() && !_vm->hasGameEnded()) {
		_vm->doFrame();
	}

	// Check if we landed in a valid location and no other marble has that location
	uint32 &marblePos = _vm->_vars[s_marbleNames[marble - 1]];

	bool foundMatch = false;
	for (int y = 0; y < 25 && !foundMatch; y++) {
		for (int x = 0; x < 25 && !foundMatch; x++) {
			Common::Rect testHotspot = generateMarbleGridRect(x, y);

			// Let's try to place the marble!
			if (testHotspot.contains(getMousePosition())) {
				// Set this as the position
				setMarbleX(marblePos, x);
				setMarbleY(marblePos, y);

				// Let's make sure no other marble is in this spot...
				for (uint16 i = 0; i < kMarbleCount; i++)
					if (i != marble - 1 && _vm->_vars[s_marbleNames[i]] == marblePos)
						marblePos = 0;

				// We have a match
				foundMatch = true;
			}
		}
	}

	// If we still don't have a match, reset it to the original location
	if (!foundMatch)
		marblePos = 0;

	// Check the new hotspots and refresh everything
	marble = 0;
	setMarbleHotspots();
	drawMarbles();
}

void TSpit::xtscpbtn(const ArgumentArray &args) {
	runDomeButtonMovie();
}

void TSpit::xtisland4990_domecheck(const ArgumentArray &args) {
	runDomeCheck();
}

void TSpit::xtisland5056_opencard(const ArgumentArray &args) {
	checkDomeSliders();
}

void TSpit::xtisland5056_resetsliders(const ArgumentArray &args) {
	resetDomeSliders(24);
}

void TSpit::xtisland5056_slidermd(const ArgumentArray &args) {
	dragDomeSlider(24);
}

void TSpit::xtisland5056_slidermw(const ArgumentArray &args) {
	checkSliderCursorChange(24);
}

void TSpit::xtatboundary(const ArgumentArray &args) {
	runDemoBoundaryDialog();
}

} // End of namespace RivenStacks
} // End of namespace Mohawk
