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

#include "engines/nancy/util.h"
#include "engines/nancy/nancy.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/resource.h"
#include "engines/nancy/sound.h"
#include "engines/nancy/input.h"
#include "engines/nancy/puzzledata.h"
#include "engines/nancy/state/scene.h"

#include "engines/nancy/action/puzzle/towerpuzzle.h"

namespace Nancy {
namespace Action {

void TowerPuzzle::init() {
	Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
	_drawSurface.clear(g_nancy->_graphics->getTransColor());
	setTransparent(true);
	setVisible(true);
	moveTo(screenBounds);

	g_nancy->_resource->loadImage(_imageName, _image);
	_image.setTransparentColor(_drawSurface.getTransparentColor());
}

void TowerPuzzle::registerGraphics() {
	_heldRing.registerGraphics();
	RenderObject::registerGraphics();
}

void TowerPuzzle::readData(Common::SeekableReadStream &stream) {
	_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
	assert(_puzzleState);

	readFilename(stream, _imageName);

	_numRingsByDifficulty.resize(3);
	for (uint i = 0; i < 3; ++i) {
		_numRingsByDifficulty[i] = stream.readUint16LE();
	}

	stream.skip(2);

	readRectArray(stream, _droppedRingSrcs, 6);
	readRectArray(stream, _heldRingSrcs, 6);

	readRectArray(stream, _hotspots, 3);

	_destRects.resize(6);
	for (uint ringID = 0; ringID < 6; ++ringID) {
		_destRects[ringID].resize(3);
		for (uint poleID = 0; poleID < 3; ++poleID) {
			// Biggest ring can only be in bottom position,
			// so it only has one rect per pole; second-biggest can
			// be in bottom-most and one position above it, so it has
			// two rects per pole, etc. Skipped data is array of 0xFF.
			readRectArray(stream, _destRects[ringID][poleID], ringID + 1, 6);
		}
	}

	_takeSound.readNormal(stream);
	_dropSound.readNormal(stream);

	_solveExitScene._sceneChange.readData(stream);
	stream.skip(2);
	_solveSound.readNormal(stream);
	_solveExitScene._flag.label = stream.readSint16LE();
	_solveExitScene._flag.flag = stream.readByte();

	_exitScene.readData(stream);
	readRect(stream, _exitHotspot);
}

void TowerPuzzle::execute() {
	switch (_state) {
	case kBegin:
		_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
		assert(_puzzleState);

		init();
		registerGraphics();
		_numRings = _numRingsByDifficulty[NancySceneState.getDifficulty()];

		if (!_puzzleState->playerHasTriedPuzzle) {
			_puzzleState->order.clear();
			_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
			for (uint i = 0; i < _numRings; ++i) {
				_puzzleState->order[0][i] = i;
			}
			_puzzleState->playerHasTriedPuzzle = true;
		}

		for (uint poleID = 0; poleID < 3; ++poleID) {
			for (uint pos = 0; pos < _numRings; ++pos) {
				if (_puzzleState->order[poleID][pos] == -1) {
					continue;
				}

				drawRing(poleID, pos, _puzzleState->order[poleID][pos]);
			}
		}

		NancySceneState.setNoHeldItem();

		g_nancy->_sound->loadSound(_takeSound);
		g_nancy->_sound->loadSound(_dropSound);

		_state = kRun;
		// fall through
	case kRun:
		switch (_solveState) {
		case kNotSolved :
			for (uint i = 0; i < _numRings; ++i) {
				// Win condition is valid for both middle and right pole
				if (_puzzleState->order[1][i] != (int8)i && _puzzleState->order[2][i] != (int8)i) {
					return;
				}
			}

			g_nancy->_sound->loadSound(_solveSound);
			g_nancy->_sound->playSound(_solveSound);
			_solveState = kWaitForSound;
			break;
		case kWaitForSound :
			if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
				g_nancy->_sound->stopSound(_solveSound);
				_state = kActionTrigger;
			}

			break;
		}

		break;
	case kActionTrigger :
		switch (_solveState) {
		case kNotSolved:
			_exitScene.execute();
			break;
		case kWaitForSound:
			_solveExitScene.execute();
			_puzzleState->playerHasTriedPuzzle = false;
			_puzzleState->order.clear();
			_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
			break;
		}

		g_nancy->_sound->stopSound(_takeSound);
		g_nancy->_sound->stopSound(_dropSound);
		g_nancy->_sound->stopSound(_solveSound);
		finishExecution();
	}
}

void TowerPuzzle::handleInput(NancyInput &input) {
	if (_state != kRun && _solveState != kNotSolved) {
		return;
	}

	// Note: this is a click-and-drag puzzle

	// Check if mouse is above a pole hotspot
	// and change the cursor if needed
	int hoveredPoleID = -1;
	for (uint poleID = 0; poleID < 3; ++poleID) {
		if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[poleID]).contains(input.mousePos)) {
			g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
			hoveredPoleID = poleID;
			break;
		}
	}

	if (_heldRingID == -1) {
		// Not holding a ring

		// First, check the exit hotspot
		if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
			g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);

			if (input.input & NancyInput::kLeftMouseButtonUp) {
				// Player has clicked, exit
				_state = kActionTrigger;
			}

			return;
		}

		// Check if we need to pick up a ring
		// Behavior is the same as original engine, where clicking outside a hotspot
		// and dragging the mouse inside while holding the click still triggers
		if (hoveredPoleID != -1 && (input.input & NancyInput::kLeftMouseButtonHeld)) {
			// Find the position of the topmost ring
			int ringPos;
			for (ringPos = 5; ringPos > -1; --ringPos) {
				if (_puzzleState->order[hoveredPoleID][ringPos] != -1) {
					break;
				}
			}

			if (ringPos == -1) {
				// Pole contains no rings, do nothing
				return;
			}

			// Redraw so the ring isn't visible anymore
			drawRing(hoveredPoleID, ringPos, _puzzleState->order[hoveredPoleID][ringPos], true);

			if (ringPos > 0) {
				drawRing(hoveredPoleID, ringPos - 1, _puzzleState->order[hoveredPoleID][ringPos - 1]);
			}

			// Change the data
			SWAP<int8>(_heldRingID, _puzzleState->order[hoveredPoleID][ringPos]);
			_heldRingPoleID = hoveredPoleID;

			// Show the held ring
			_heldRing._drawSurface.create(_image, _heldRingSrcs[_heldRingID]);
			_heldRing.setVisible(true);
			_heldRing.setTransparent(true);
			_heldRing.pickUp();

			g_nancy->_sound->playSound(_takeSound);
		}
	}

	if (_heldRingID != -1) {
		_heldRing.handleInput(input);

		// Holding a ring, check if it has just been dropped
		if ((input.input & NancyInput::kLeftMouseButtonUp) || !(input.input & NancyInput::kLeftMouseButtonHeld)) {
			// Check if dropped over a pole hotspot
			// If not, return to old pole; if yes, move to new one
			uint returnToPole = hoveredPoleID == -1 ? _heldRingPoleID : hoveredPoleID;

			// Find the new position of the ring
			uint newPos;
			for (newPos = 0; newPos < 6; ++newPos) {
				if (_puzzleState->order[returnToPole][newPos] == -1) {
					break;
				}
			}

			// Make sure the player can't place a larger ring on top of a smaller one
			if (newPos > 0 && _puzzleState->order[returnToPole][newPos - 1] > _heldRingID) {
				returnToPole = _heldRingPoleID;

				for (newPos = 0; newPos < 6; ++newPos) {
					if (_puzzleState->order[returnToPole][newPos] == -1) {
						break;
					}
				}
			}

			// Draw the new ring in its place
			drawRing(returnToPole, newPos, _heldRingID);

			// Change the data
			SWAP<int8>(_heldRingID, _puzzleState->order[returnToPole][newPos]);
			_heldRingPoleID = -1;

			g_nancy->_sound->playSound(_dropSound);

			// Hide the held ring
			_heldRing.setVisible(false);
			_heldRing.putDown();
		}
	}
}

void TowerPuzzle::drawRing(uint poleID, uint position, uint ringID, bool clear) {
	_needsRedraw = true;

	if (clear) {
		// Just clear the ring, leaving a hole in the surface
		// that needs to be filled by redrawing the ring below
		_drawSurface.fillRect(_destRects[ringID][poleID][position], _drawSurface.getTransparentColor());
		return;
	}

	_drawSurface.blitFrom(_image, _droppedRingSrcs[ringID], _destRects[ringID][poleID][position]);
}

} // End of namespace Action
} // End of namespace Nancy
