/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1995-1997 Presto Studios, Inc.
 *
 * 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 "pegasus/gamestate.h"
#include "pegasus/pegasus.h"
#include "pegasus/ai/ai_area.h"
#include "pegasus/neighborhood/norad/constants.h"
#include "pegasus/neighborhood/norad/norad.h"
#include "pegasus/neighborhood/norad/pressuredoor.h"
#include "pegasus/neighborhood/norad/delta/noraddelta.h"

namespace Pegasus {

static const TimeValue kLevelsSplashStart = 0;
static const TimeValue kLevelsSplashStop = 1;
static const TimeValue kPressureBase = 1;

static const TimeValue kDoorSealedTime = 0;
static const TimeValue kEqualizeTime = 1;
static const TimeValue kMaxPressureLoopStart = 2;
static const TimeValue kMaxPressureLoopStop = 3;
static const TimeValue kOpeningDoorLoopStart = 3;
static const TimeValue kOpeningDoorLoopStop = 4;
static const TimeValue kIncreasingPressureTime = 4;
static const TimeValue kDecreasingPressureTime = 5;
static const TimeValue kCautionLoopStart = 6;
static const TimeValue kCautionLoopStop = 7;

static const NotificationFlags kSplashFinished = 1;
static const NotificationFlags kPressureDroppingFlag = kSplashFinished << 1;

static const NotificationFlags kPressureNotificationFlags = kSplashFinished |
														kPressureDroppingFlag;

static const NotificationFlags kDoorJumpsUpFlag = 1;
static const NotificationFlags kDoorJumpsBackFlag = kDoorJumpsUpFlag << 1;
static const NotificationFlags kDoorCrushedFlag = kDoorJumpsBackFlag << 1;

static const NotificationFlags kUtilityNotificationFlags = kDoorJumpsUpFlag |
														kDoorJumpsBackFlag |
														kDoorCrushedFlag;

enum {
	kPlayingRobotApproaching,
	kRobotPunching,
	kRobotComingThrough,
	kRobotDying,
	kRobotDead
};

const short kMaxPunches = 5;

enum {
	kPlayingSplash,
	kPlayingPressureMessage,
	kPlayingEqualizeMessage,
	kWaitingForPlayer,
	kPlayingDoneMessage,
	kGameOver
};

// Pressure values range from 0 to 11.
static const short kMinPressure = 0;
static const short kMaxPressure = 11;

static const TimeScale kNavTimeScale = 600;
static const TimeValue kNavFrameRate = 15;
static const TimeValue kNavTimePerFrame = kNavTimeScale / kNavFrameRate;

static const TimeValue kApproachPunchInTime = 122 * kNavTimePerFrame;
static const TimeValue kLoopPunchInTime = 38 * kNavTimePerFrame;
static const TimeValue kPunchThroughTime = 38 * kNavTimePerFrame;

// Pressure door PICTs:
static const ResIDType kUpperPressureUpOffPICTID = 400;
static const ResIDType kUpperPressureUpOnPICTID = 401;
static const ResIDType kUpperPressureDownOffPICTID = 402;
static const ResIDType kUpperPressureDownOnPICTID = 403;

static const ResIDType kLowerPressureUpOffPICTID = 404;
static const ResIDType kLowerPressureUpOnPICTID = 405;
static const ResIDType kLowerPressureDownOffPICTID = 406;
static const ResIDType kLowerPressureDownOnPICTID = 407;

PressureDoor::PressureDoor(Neighborhood *handler, bool isUpperDoor, const HotSpotID upSpotID, const HotSpotID downSpotID,
		const HotSpotID outSpotID, TimeValue pressureSoundIn, TimeValue pressureSoundOut, TimeValue equalizeSoundIn,
		TimeValue equalizeSoundOut) : GameInteraction(kNoradPressureDoorInteractionID, handler),
		_levelsMovie(kPressureDoorLevelsID), _typeMovie(kPressureDoorTypeID), _upButton(kPressureDoorUpButtonID),
		_downButton(kPressureDoorDownButtonID), _pressureNotification(kNoradPressureNotificationID, ((PegasusEngine *)g_engine)),
		_doorTracker(this), _utilityNotification(kNoradUtilityNotificationID, ((PegasusEngine *)g_engine)) {
	_neighborhoodNotification = handler->getNeighborhoodNotification();
	_upHotspotID = upSpotID;
	_downHotspotID = downSpotID;
	_outHotspotID = outSpotID;
	_pressureSoundIn = pressureSoundIn;
	_pressureSoundOut = pressureSoundOut;
	_equalizeSoundIn = equalizeSoundIn;
	_equalizeSoundOut = equalizeSoundOut;
	_playingAgainstRobot = false;
	_isUpperDoor = isUpperDoor;
}

void PressureDoor::openInteraction() {
	if (_isUpperDoor) {
		_levelsMovie.initFromMovieFile("Images/Norad Alpha/Upper Levels Movie");
		_levelsMovie.moveElementTo(kNoradUpperLevelsLeft, kNoradUpperLevelsTop);
	} else {
		_levelsMovie.initFromMovieFile("Images/Norad Alpha/Lower Levels Movie");
		_levelsMovie.moveElementTo(kNoradLowerLevelsLeft, kNoradLowerLevelsTop);
	}

	_levelsScale = _levelsMovie.getScale();
	_levelsMovie.setDisplayOrder(kPressureLevelsOrder);
	_levelsMovie.startDisplaying();
	_levelsMovie.setSegment(kLevelsSplashStart * _levelsScale, kLevelsSplashStop * _levelsScale);
	_levelsMovie.setTime(kLevelsSplashStart * _levelsScale);
	_levelsMovie.redrawMovieWorld();
	_levelsMovie.show();

	_pressureCallBack.setNotification(&_pressureNotification);
	_pressureCallBack.initCallBack(&_levelsMovie, kCallBackAtExtremes);
	_pressureCallBack.setCallBackFlag(kSplashFinished);
	_pressureCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);

	_pressureNotification.notifyMe(this, kPressureNotificationFlags, kPressureNotificationFlags);

	if (_isUpperDoor) {
		_typeMovie.initFromMovieFile("Images/Norad Alpha/Upper Type Movie");
		_typeMovie.moveElementTo(kNoradUpperTypeLeft, kNoradUpperTypeTop);
	} else {
		_typeMovie.initFromMovieFile("Images/Norad Alpha/Lower Type Movie");
		_typeMovie.moveElementTo(kNoradLowerTypeLeft, kNoradLowerTypeTop);
	}

	_typeScale = _typeMovie.getScale();
	_typeMovie.setDisplayOrder(kPressureTypeOrder);
	_typeMovie.startDisplaying();
	_typeMovie.setTime(kDoorSealedTime * _typeScale);
	_typeMovie.redrawMovieWorld();

	SpriteFrame *frame = new SpriteFrame();
	if (_isUpperDoor)
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureUpOffPICTID);
	else
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureUpOffPICTID);
	_upButton.addFrame(frame, 0, 0);

	frame = new SpriteFrame();
	if (_isUpperDoor)
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureUpOnPICTID);
	else
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureUpOnPICTID);
	_upButton.addFrame(frame, 0, 0);

	_upButton.setCurrentFrameIndex(0);
	_upButton.setDisplayOrder(kPressureUpOrder);

	Common::Rect r;
	frame->getSurfaceBounds(r);
	if (_isUpperDoor)
		r.moveTo(kNoradUpperUpLeft, kNoradUpperUpTop);
	else
		r.moveTo(kNoradLowerUpLeft, kNoradLowerUpTop);

	_upButton.setBounds(r);
	_upButton.startDisplaying();
	_upButton.show();

	frame = new SpriteFrame();
	if (_isUpperDoor)
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureDownOffPICTID);
	else
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureDownOffPICTID);
	_downButton.addFrame(frame, 0, 0);

	frame = new SpriteFrame();
	if (_isUpperDoor)
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kLowerPressureDownOnPICTID);
	else
		frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kUpperPressureDownOnPICTID);
	_downButton.addFrame(frame, 0, 0);

	_downButton.setCurrentFrameIndex(0);
	_downButton.setDisplayOrder(kPressureDownOrder);

	frame->getSurfaceBounds(r);
	if (_isUpperDoor)
		r.moveTo(kNoradUpperDownLeft, kNoradUpperDownTop);
	else
		r.moveTo(kNoradLowerDownLeft, kNoradLowerDownTop);

	_downButton.setBounds(r);
	_downButton.startDisplaying();
	_downButton.show();

	_utilityCallBack.setNotification(&_utilityNotification);
	_utilityCallBack.initCallBack(&_utilityTimer, kCallBackAtTime);
	_utilityNotification.notifyMe(this, kUtilityNotificationFlags, kUtilityNotificationFlags);
	_utilityTimer.setMasterTimeBase(getOwner()->getNavMovie());

	if (_playingAgainstRobot)
		_neighborhoodNotification->notifyMe(this, kExtraCompletedFlag | kDelayCompletedFlag |
				kSpotSoundCompletedFlag, kExtraCompletedFlag | kDelayCompletedFlag | kSpotSoundCompletedFlag);
	else
		_neighborhoodNotification->notifyMe(this, kDelayCompletedFlag | kSpotSoundCompletedFlag,
				kDelayCompletedFlag | kSpotSoundCompletedFlag);

	_gameState = kPlayingSplash;
}

void PressureDoor::initInteraction() {
	_levelsMovie.start();

	if (_playingAgainstRobot) {
		ExtraTable::Entry entry;
		_owner->getExtraEntry(kN59RobotApproaches, entry);
		_utilityTimer.setSegment(entry.movieStart, entry.movieEnd);
		_utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag);
		_punchInTime = kApproachPunchInTime + entry.movieStart;
		_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale);
		_utilityTimer.setTime(entry.movieStart);
		_owner->startExtraSequence(kN59RobotApproaches, kExtraCompletedFlag, kFilterAllInput);
		_utilityTimer.start();
		_robotState = kPlayingRobotApproaching;
	}

	_levelsMovie.redrawMovieWorld();
}

void PressureDoor::closeInteraction() {
	_pressureNotification.cancelNotification(this);
	_pressureCallBack.releaseCallBack();
	_utilityNotification.cancelNotification(this);
	_utilityCallBack.releaseCallBack();
	_neighborhoodNotification->cancelNotification(this);
}

void PressureDoor::playAgainstRobot() {
	_playingAgainstRobot = true;
}

void PressureDoor::receiveNotification(Notification *notification, const NotificationFlags flags) {
	Neighborhood *owner = getOwner();

	if (notification == _neighborhoodNotification) {
		if (_playingAgainstRobot && (flags & kExtraCompletedFlag) != 0) {
			ExtraTable::Entry entry;

			switch (_robotState) {
			case kPlayingRobotApproaching:
				_utilityTimer.stop();
				if (GameState.getNoradSubRoomPressure() == kMaxPressure) {
					owner->getExtraEntry(kN59PlayerWins1, entry);
					_utilityTimer.setSegment(entry.movieStart, entry.movieEnd);
					_utilityTimer.setTime(entry.movieStart);
					_utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag);
					_punchInTime = kLoopPunchInTime + entry.movieStart;
					_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale);
					owner->startExtraSequence(kN59PlayerWins1, kExtraCompletedFlag, kFilterNoInput);
					_utilityTimer.start();
					_robotState = kRobotDying;
				} else {
					owner->getExtraEntry(kN59RobotPunchLoop, entry);
					_utilityTimer.setSegment(entry.movieStart, entry.movieEnd);
					_utilityTimer.setTime(entry.movieStart);
					_utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag);
					_punchInTime = kLoopPunchInTime + entry.movieStart;
					_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale);
					owner->startSpotLoop(entry.movieStart, entry.movieEnd, kExtraCompletedFlag);
					_utilityTimer.start();
					_robotState = kRobotPunching;
					_punchCount = 1;
				}
				break;
			case kRobotPunching:
				if (GameState.getNoradSubRoomPressure() == kMaxPressure) {
					owner->startExtraSequence(kN59PlayerWins1, kExtraCompletedFlag, kFilterNoInput);
					_robotState = kRobotDying;
				} else if (++_punchCount >= kMaxPunches) {
					_robotState = kRobotComingThrough;
					owner->getExtraEntry(kN59RobotWins, entry);
					_utilityTimer.stop();
					_utilityTimer.setSegment(entry.movieStart, entry.movieEnd);
					_utilityTimer.setTime(entry.movieStart);
					_utilityCallBack.cancelCallBack();
					_utilityCallBack.setCallBackFlag(kDoorCrushedFlag);
					_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, kPunchThroughTime + entry.movieStart, kNavTimeScale);
					owner->startExtraSequence(kN59RobotWins, kExtraCompletedFlag, kFilterNoInput);
					_utilityTimer.start();
				} else {
					_utilityCallBack.setCallBackFlag(kDoorJumpsUpFlag);
					_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime, kNavTimeScale);
					owner->scheduleNavCallBack(kExtraCompletedFlag);
				}
				break;
			case kRobotComingThrough:
				g_system->delayMillis(2 * 1000);
				((PegasusEngine *)g_engine)->die(kDeathRobotThroughNoradDoor);
				break;
			case kRobotDying:
				_robotState = kRobotDead;
				_levelsMovie.stop();
				_levelsMovie.setSegment((kNormalSubRoomPressure + kPressureBase) * _levelsScale,
						(GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale + 1);
				_levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
				_pressureCallBack.setCallBackFlag(kPressureDroppingFlag);
				_pressureCallBack.scheduleCallBack(kTriggerAtStart, 0, 0);
				_typeMovie.stop();
				_typeMovie.setSegment(0, _typeMovie.getDuration());
				_typeMovie.setTime(kDecreasingPressureTime * _typeScale);
				_typeMovie.redrawMovieWorld();
				_typeMovie.show();
				_downButton.show();
				_downButton.setCurrentFrameIndex(1);
				_gameState = kGameOver;
				allowInput(false);
				_levelsMovie.setRate(Common::Rational(-4, 3)); // Should match door tracker.
				break;
			case kRobotDead:
				allowInput(true);
				((NoradDelta *)owner)->playerBeatRobotWithDoor();
				owner->requestDeleteCurrentInteraction();
				break;
			}
		}

		if ((flags & (kDelayCompletedFlag | kSpotSoundCompletedFlag)) != 0) {
			switch (_gameState) {
			case kPlayingPressureMessage:
				_typeMovie.setTime(kEqualizeTime * _typeScale);
				_typeMovie.redrawMovieWorld();
				owner->requestDelay(1, 5, kFilterNoInput, 0);
				owner->requestSpotSound(_equalizeSoundIn, _equalizeSoundOut, kFilterNoInput, 0);
				owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag);
				_gameState = kPlayingEqualizeMessage;
				break;
			case kPlayingEqualizeMessage:
				_gameState = kWaitingForPlayer;
				stopChangingPressure();
				break;
			case kPlayingDoneMessage:
				_gameState = kWaitingForPlayer;
				_typeMovie.stop();
				_typeMovie.setFlags(0);
				_typeMovie.hide();
				if (!_playingAgainstRobot)
					((Norad *)_owner)->doneWithPressureDoor();
				break;
			}
		}
	} else if (notification == &_pressureNotification) {
		switch (flags) {
		case kSplashFinished:
			_levelsMovie.stop();
			_levelsMovie.setSegment(0, _levelsMovie.getDuration());
			_levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
			_levelsMovie.redrawMovieWorld();

			if (GameState.getNoradSubRoomPressure() != kNormalSubRoomPressure) {
				_typeMovie.show();
				owner->requestDelay(1, 5, kFilterNoInput, 0);
				owner->requestSpotSound(_pressureSoundIn, _pressureSoundOut, kFilterNoInput, 0);
				owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag);
				_gameState = kPlayingPressureMessage;
			} else {
				_gameState = kWaitingForPlayer;
			}
			break;
		case kPressureDroppingFlag:
			_levelsMovie.stop();
			_levelsMovie.hide();
			_typeMovie.stop();
			_typeMovie.hide();
			_upButton.hide();
			_downButton.hide();
			owner->startExtraSequence(kN59PlayerWins2, kExtraCompletedFlag, kFilterNoInput);
			break;
		}
	} else if (notification == &_utilityNotification) {
		switch (flags) {
		case kDoorJumpsUpFlag:
			_utilityCallBack.setCallBackFlag(kDoorJumpsBackFlag);
			_utilityCallBack.scheduleCallBack(kTriggerTimeFwd, _punchInTime + kNavTimePerFrame, kNavTimeScale);
			_levelsMovie.hide();
			_typePunched = _typeMovie.isVisible();
			if (_typePunched == true)
				_typeMovie.hide();
			_upButton.hide();
			_downButton.hide();
			break;
		case kDoorJumpsBackFlag:
			_levelsMovie.show();
			_upButton.show();
			_downButton.show();
			if (_typePunched)
				_typeMovie.show();
			break;
		case kDoorCrushedFlag:
			_levelsMovie.hide();
			_typeMovie.hide();
			_upButton.hide();
			_downButton.hide();
			break;
		}
	}
}

void PressureDoor::activateHotspots() {
	GameInteraction::activateHotspots();

	switch (_gameState) {
	case kWaitingForPlayer:
		g_allHotspots.activateOneHotspot(_upHotspotID);
		g_allHotspots.activateOneHotspot(_downHotspotID);
		if (!_playingAgainstRobot)
			g_allHotspots.activateOneHotspot(_outHotspotID);
		break;
	default:
		break;
	}
}

void PressureDoor::clickInHotspot(const Input &input, const Hotspot *spot) {
	HotSpotID id = spot->getObjectID();

	if (id == _upHotspotID || id == _downHotspotID) {
		if (id == _upHotspotID)
			_doorTracker.setTrackParameters(spot, &_upButton);
		else
			_doorTracker.setTrackParameters(spot, &_downButton);

		_doorTracker.startTracking(input);
	} else {
		GameInteraction::clickInHotspot(input, spot);
	}
}

void PressureDoor::incrementPressure(const HotSpotID id) {
	_typeMovie.stop();
	_typeMovie.setSegment(0, _typeMovie.getDuration());
	_typeMovie.setFlags(0);

	if (id == _upHotspotID) {
		if (GameState.getNoradSubRoomPressure() < kMaxPressure) {
			GameState.setNoradSubRoomPressure(GameState.getNoradSubRoomPressure() + 1);
			_levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
			_levelsMovie.redrawMovieWorld();
			_typeMovie.setTime(kIncreasingPressureTime * _typeScale);
			_typeMovie.redrawMovieWorld();
			_typeMovie.show();
			g_AIArea->checkMiddleArea();
		} else {
			_typeMovie.hide();
		}
	} else if (id == _downHotspotID) {
		if (GameState.getNoradSubRoomPressure() > kMinPressure) {
			GameState.setNoradSubRoomPressure(GameState.getNoradSubRoomPressure() - 1);
			_levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
			_levelsMovie.redrawMovieWorld();
			_typeMovie.setTime(kDecreasingPressureTime * _typeScale);
			_typeMovie.redrawMovieWorld();
			_typeMovie.show();
			g_AIArea->checkMiddleArea();
		} else {
			_typeMovie.hide();
		}
	}
}

void PressureDoor::stopChangingPressure() {
	Neighborhood *owner;

	switch (GameState.getNoradSubRoomPressure()) {
	case 11:
		_typeMovie.setSegment(kMaxPressureLoopStart * _typeScale, kMaxPressureLoopStop * _typeScale);
		_typeMovie.setFlags(kLoopTimeBase);
		_typeMovie.show();
		_typeMovie.start();
		break;
	case 10:
		_typeMovie.setSegment(kCautionLoopStart * _typeScale, kCautionLoopStop * _typeScale);
		_typeMovie.setFlags(kLoopTimeBase);
		_typeMovie.show();
		_typeMovie.start();
		break;
	case kNormalSubRoomPressure:
		owner = getOwner();
		_typeMovie.setSegment(kOpeningDoorLoopStart * _typeScale, kOpeningDoorLoopStop * _typeScale);
		_typeMovie.setFlags(kLoopTimeBase);
		_typeMovie.show();
		_gameState = kPlayingDoneMessage;
		owner->requestDelay(2, 1, kFilterNoInput, kDelayCompletedFlag);
		_typeMovie.start();
		break;
	default:
		_typeMovie.hide();
		break;
	}
}

bool PressureDoor::canSolve() {
	if (_playingAgainstRobot)
		return GameState.getNoradSubRoomPressure() < 11;

	return GameState.getNoradSubRoomPressure() != kNormalSubRoomPressure;
}

void PressureDoor::doSolve() {
	if (_playingAgainstRobot) {
		GameState.setNoradSubRoomPressure(11);
		_levelsMovie.setTime((11 + kPressureBase) * _levelsScale);
		_levelsMovie.redrawMovieWorld();
		_typeMovie.setSegment(kMaxPressureLoopStart * _typeScale, kMaxPressureLoopStop * _typeScale);
		_typeMovie.setFlags(kLoopTimeBase);
		_typeMovie.show();
		_typeMovie.start();
		g_AIArea->checkMiddleArea();
	} else {
		GameState.setNoradSubRoomPressure(kNormalSubRoomPressure);
		_levelsMovie.setTime((kNormalSubRoomPressure + kPressureBase) * _levelsScale);
		_levelsMovie.redrawMovieWorld();
		_typeMovie.setSegment(kOpeningDoorLoopStart * _typeScale, kOpeningDoorLoopStop * _typeScale);
		_typeMovie.setFlags(kLoopTimeBase);
		_typeMovie.show();
		Neighborhood *owner = getOwner();
		owner->requestDelay(2, 1, kFilterNoInput, kDelayCompletedFlag);
		_gameState = kPlayingDoneMessage;
		_typeMovie.start();
		g_AIArea->checkMiddleArea();
	}
}

} // End of namespace Pegasus
