/* 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 "bladerunner/scene.h"

#include "bladerunner/actor.h"
#include "bladerunner/actor_dialogue_queue.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/chapters.h"
#include "bladerunner/game_info.h"
#include "bladerunner/items.h"
#include "bladerunner/overlays.h"
#include "bladerunner/regions.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/screen_effects.h"
#include "bladerunner/set.h"
#include "bladerunner/settings.h"
#include "bladerunner/slice_renderer.h"
#include "bladerunner/script/police_maze.h"
#include "bladerunner/script/scene_script.h"
#include "bladerunner/ui/spinner.h"
#include "bladerunner/vqa_player.h"
#include "bladerunner/zbuffer.h"

#include "common/str.h"

namespace BladeRunner {

Scene::Scene(BladeRunnerEngine *vm)
	: _vm(vm),
	_setId(-1),
	_sceneId(-1),
	_vqaPlayer(nullptr),
	_defaultLoop(0),
	_defaultLoopSet(false),
	_specialLoopMode(kSceneLoopModeLoseControl),
	_specialLoop(0),
	_defaultLoopPreloadedSet(false),
	// _introFinished(false),
	_nextSetId(-1),
	_nextSceneId(-1),
	_frame(0),
	_actorStartFacing(0),
	_playerWalkedIn(false),
	_set(new Set(vm)),
	_regions(new Regions()),
	_exits(new Regions()) {
}

Scene::~Scene() {
	delete _set;
	delete _regions;
	delete _exits;
	delete _vqaPlayer;
}

bool Scene::open(int setId, int sceneId, bool isLoadingGame) {
	if (!isLoadingGame) {
		_vm->_actorDialogueQueue->flush(1, false);
	}

	_vm->walkingReset();

	_setId = setId;
	_sceneId = sceneId;

	const Common::String sceneName = _vm->_gameInfo->getSceneName(_sceneId);

	if (isLoadingGame) {
		_vm->_overlays->resume(true);
	} else {
		_regions->clear();
		_exits->clear();
#if BLADERUNNER_ORIGINAL_BUGS
#else
		_vm->_screenEffects->toggleEntry(-1, false); // clear the skip list
#endif
		_vm->_screenEffects->_entries.clear();
		_vm->_overlays->removeAll();
		_defaultLoop = 0;
		_defaultLoopSet = false;
		_defaultLoopPreloadedSet = false;
		_specialLoopMode = kSceneLoopModeNone;
		_specialLoop = -1;
		_frame = -1;
	}

	Common::String vqaName;
	int currentResourceId = _vm->_chapters->currentResourceId();
	if (currentResourceId == 1) {
		vqaName = Common::String::format("%s.VQA", sceneName.c_str());
	} else {
		vqaName = Common::String::format("%s_%d.VQA", sceneName.c_str(), MIN(currentResourceId, 3));
	}

	if (_vqaPlayer != nullptr) {
		delete _vqaPlayer;
	}

	_vqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceBack, vqaName);

	if (!_vm->_sceneScript->open(sceneName)) {
		return false;
	}

	if (!isLoadingGame) {
		_vm->_sceneScript->initializeScene();
	}

	Common::String setResourceName = Common::String::format("%s-MIN.SET", sceneName.c_str());
	if (!_set->open(setResourceName)) {
		return false;
	}

	_vm->_sliceRenderer->setView(_vm->_view);

	if ((setId == kSetMA02_MA04 || setId == kSetMA04)
	    && sceneId == kSceneMA04) {
		_vm->setExtraCNotify(0);
	}

	if (isLoadingGame) {
		resume(true);
		if (sceneId == kScenePS10    // police maze
		    || sceneId == kScenePS11 // police maze
		    || sceneId == kScenePS12 // police maze
		    || sceneId == kScenePS13 // police maze
#if BLADERUNNER_ORIGINAL_BUGS
#else
		    || sceneId == kSceneUG01 // Steam room
#endif // BLADERUNNER_ORIGINAL_BUGS
		) {
			_vm->_sceneScript->sceneLoaded();
		}
		return true;
	}

	if (!_vqaPlayer->open()) {
		return false;
	}

	if (_specialLoopMode == kSceneLoopModeNone) {
		startDefaultLoop();
	}

	// This frame advancement (frame skip) may be required here
	// It is in the original code and possible initializes some variables
	// (or perhaps z-buffering related stuff)
	// It may cause issues when in a scene we need to trigger some action
	// based on the first frame of the loop when entering the scene (using SceneFrameAdvanced())
	// (eg. it is contributing to the barrel flame glitch in pan from DR04 to DR01)
	// However, better to resolve those issues with a workaround (eg. using InitializeScene())
	advanceFrame();

	_vm->_playerActor->setAtXYZ(_actorStartPosition, _actorStartFacing);
	_vm->_playerActor->setSetId(setId);

	_vm->_sceneScript->sceneLoaded();

	_vm->_sceneObjects->clear();

	// Init click map
	int actorCount = _vm->_gameInfo->getActorCount();
	for (int i = 0; i != actorCount; ++i) {
		Actor *actor = _vm->_actors[i];
		if (actor->getSetId() == setId) {
			//debug("Actor added: %d", i);
#if !BLADERUNNER_ORIGINAL_BUGS
			// ensure that actors' "hotspot" areas from previous scene are cleared up
			actor->resetScreenRectangleAndBbox();
#endif
			_vm->_sceneObjects->addActor(
				i + kSceneObjectOffsetActors,
				actor->getBoundingBox(),
				actor->getScreenRectangle(),
				true,
				false,
				actor->isTarget(),
				actor->isRetired()
			);
		}
	}

	_set->addObjectsToScene(_vm->_sceneObjects);
	_vm->_items->addToSet(setId);
	_vm->_sceneObjects->updateObstacles();

	if (_specialLoopMode != kSceneLoopModeLoseControl) {
		_vm->_sceneScript->playerWalkedIn();
	}

	return true;
}

bool Scene::close(bool isLoadingGame) {
	bool result = true;
	if (getSetId() == -1) {
		return true;
	}

	_vm->_policeMaze->clear(!isLoadingGame);

	if (isLoadingGame) {
		_vm->_sceneScript->playerWalkedOut();
	}

	//	if (SceneScript_isLoaded() && !SceneScript_unload()) {
	//		result = false;
	//	}
	if (_vqaPlayer != nullptr) {
		//_vqaPlayer->stop();
		delete _vqaPlayer;
		_vqaPlayer = nullptr;
	}
	_sceneId = -1;
	_setId = -1;

	return result;
}

int Scene::advanceFrame(bool useTime) {
	int frame = _vqaPlayer->update(false, true, useTime);
	if (frame >= 0) {
		blit(_vm->_surfaceBack, _vm->_surfaceFront);
		_vqaPlayer->updateZBuffer(_vm->_zbuffer);
		_vqaPlayer->updateView(_vm->_view);
		_vqaPlayer->updateScreenEffects(_vm->_screenEffects);
		_vqaPlayer->updateLights(_vm->_lights);
	}

	if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeOnce || _specialLoopMode == kSceneLoopModeOnceNStay || _specialLoopMode == kSceneLoopModeSpinner) {
		if (!_defaultLoopSet) {
			_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
			_defaultLoopSet = true;
			if (_specialLoopMode == kSceneLoopModeLoseControl) {
				_vm->playerLosesControl();
			}
		}
	} else if (_specialLoopMode == kSceneLoopModeChangeSet) {
		if (frame == -3) { // EOF
			_vm->_settings->setNewSetAndScene(_nextSetId, _nextSceneId);
			_vm->playerGainsControl();
		}
	} else if (_specialLoopMode == kSceneLoopModeNone) {
		if (!_defaultLoopPreloadedSet) {
			_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, &Scene::loopEndedStatic, this);
			_defaultLoopPreloadedSet = true;
		}
	}

	if (frame >= 0) {
		_frame = frame;
	}

	return frame;
}

void Scene::resume(bool isLoadingGame) {
	if (!_vqaPlayer) {
		return;
	}

	int targetFrame = _frame;

	if (isLoadingGame) {
		_vqaPlayer->open();
	} else {
		_vm->_zbuffer->disable();
	}

	if (_specialLoopMode == kSceneLoopModeNone) {
		startDefaultLoop();
	} else {
		if (_specialLoopMode == kSceneLoopModeChangeSet) {
			_vm->_settings->setNewSetAndScene(_setId, _sceneId);
		}
		if (_defaultLoopPreloadedSet) {
			_specialLoopMode = kSceneLoopModeNone;
			startDefaultLoop();
			advanceFrame(false);
			loopStartSpecial(_specialLoopMode, _specialLoop, false);
		} else {
			_defaultLoopPreloadedSet = true;
			loopStartSpecial(_specialLoopMode, _specialLoop, true);
			if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeChangeSet) {
				_vm->playerGainsControl();
			}
		}
		if (_specialLoopMode == kSceneLoopModeChangeSet) {
			_vm->_settings->clearNewSetAndScene();
		}
	}

	int frame, frameStart, frameEnd;
	do {
		// fast forward to targetFrame but do, at the very least, one advanceFrame() (with time=false)
		frame = advanceFrame(false);
		// check if active loop has changed, and we need to adjust targetFrame
		if (_vqaPlayer->getCurrentBeginAndEndFrame(frame, &frameStart, &frameEnd)
		    && targetFrame >= 0
		    && (targetFrame < frameStart || targetFrame > frameEnd)
		) {
			// active loop has changed, and targetFrame has a remnant frame value from the previous loop's frameset,
			// so set it to the current loop's start
			targetFrame = frameStart;
		}
	} while (frame >= 0 && frame != targetFrame);

	if (!isLoadingGame) {
		_vm->_zbuffer->enable();
	}
}

void Scene::startDefaultLoop() {
	_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeImmediate, nullptr, nullptr);
	_defaultLoopSet = true;
	_defaultLoopPreloadedSet = false;
}

void Scene::setActorStart(Vector3 position, int facing) {
	_actorStartPosition = position;
	_actorStartFacing = facing;
}

void Scene::loopSetDefault(int loopId) {
	_defaultLoop = loopId;
}

void Scene::loopStartSpecial(int specialLoopMode, int loopId, bool immediately) {
	_specialLoopMode = specialLoopMode;
	_specialLoop = loopId;

	int repeats = -1;
	if (_specialLoopMode == kSceneLoopModeChangeSet || _specialLoopMode == kSceneLoopModeOnceNStay) {
		repeats = 0;
	}

	int loopMode = kLoopSetModeEnqueue;
	if (immediately) {
		loopMode = kLoopSetModeImmediate;
	}

	_vqaPlayer->setLoop(_specialLoop, repeats, loopMode, &Scene::loopEndedStatic, this);
	if (_specialLoopMode == kSceneLoopModeChangeSet) {
		_nextSetId = _vm->_settings->getNewSet();
		_nextSceneId = _vm->_settings->getNewScene();
	}
	if (immediately) {
		_defaultLoopPreloadedSet = true;
		loopEnded(0, _specialLoop);
	}
}

int Scene::findObject(const Common::String &objectName) {
	return _set->findObject(objectName);
}

bool Scene::objectSetHotMouse(int objectId) {
	return _set->objectSetHotMouse(objectId);
}

bool Scene::objectGetBoundingBox(int objectId, BoundingBox *boundingBox) {
	return _set->objectGetBoundingBox(objectId, boundingBox);
}

void Scene::objectSetIsClickable(int objectId, bool isClickable, bool sceneLoaded) {
	_set->objectSetIsClickable(objectId, isClickable);
	if (sceneLoaded) {
		_vm->_sceneObjects->setIsClickable(objectId + kSceneObjectOffsetObjects, isClickable);
	}
}

void Scene::objectSetIsObstacle(int objectId, bool isObstacle, bool sceneLoaded, bool updateWalkpath) {
	_set->objectSetIsObstacle(objectId, isObstacle);
	if (sceneLoaded) {
		_vm->_sceneObjects->setIsObstacle(objectId + kSceneObjectOffsetObjects, isObstacle);
		if (updateWalkpath) {
			_vm->_sceneObjects->updateObstacles();
		}
	}
}

void Scene::objectSetIsObstacleAll(bool isObstacle, bool sceneLoaded) {
	int i;
	for (i = 0; i < (int)_set->getObjectCount(); ++i) {
		_set->objectSetIsObstacle(i, isObstacle);
		if (sceneLoaded) {
			_vm->_sceneObjects->setIsObstacle(i + kSceneObjectOffsetObjects, isObstacle);
		}
	}
}

void Scene::objectSetIsTarget(int objectId, bool isTarget, bool sceneLoaded) {
	_set->objectSetIsTarget(objectId, isTarget);
	if (sceneLoaded) {
		_vm->_sceneObjects->setIsTarget(objectId + kSceneObjectOffsetObjects, isTarget);
	}
}

const Common::String &Scene::objectGetName(int objectId) {
	return _set->objectGetName(objectId);
}

void Scene::loopEnded(int frame, int loopId) {
	if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeOnce || _specialLoopMode == kSceneLoopModeSpinner) {
		if (_defaultLoopPreloadedSet) {
			_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
			_defaultLoopSet = true;
			_defaultLoopPreloadedSet = false;
			if (_specialLoopMode == kSceneLoopModeLoseControl) {
				_vm->playerLosesControl();
			}
		} else {
			if (_specialLoopMode == kSceneLoopModeLoseControl) {
				_vm->playerGainsControl();
				_playerWalkedIn = true;
			}
			if (_specialLoopMode == kSceneLoopModeSpinner) {
				_vm->_spinner->open();
			}
			_specialLoopMode = kSceneLoopModeNone;
			_specialLoop = -1;
			_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, nullptr, nullptr);
			_defaultLoopPreloadedSet = true;
		}
	} else if (_specialLoopMode == kSceneLoopModeChangeSet) {
		_defaultLoopSet = true;
		_defaultLoopPreloadedSet = false;
		_vm->playerLosesControl();
	}
}

void Scene::loopEndedStatic(void *data, int frame, int loopId) {
	((Scene *)data)->loopEnded(frame, loopId);
}

void Scene::save(SaveFileWriteStream &f) {
	f.writeInt(_setId);
	f.writeInt(_sceneId);
	f.writeInt(_defaultLoop);
	f.writeBool(_defaultLoopSet);
	f.writeBool(_defaultLoopPreloadedSet);
	f.writeInt(_specialLoopMode);
	f.writeInt(_specialLoop);
	f.writeInt(_nextSetId);
	f.writeInt(_nextSceneId);
	f.writeInt(_frame);
	f.writeVector3(_actorStartPosition);
	f.writeInt(_actorStartFacing);
	f.writeBool(_playerWalkedIn);
}

void Scene::load(SaveFileReadStream &f) {
	_setId = f.readInt();
	_sceneId = f.readInt();
	_defaultLoop = f.readInt();
	_defaultLoopSet = f.readBool();
	_defaultLoopPreloadedSet = f.readBool();
	_specialLoopMode = f.readInt();
	_specialLoop = f.readInt();
	_nextSetId = f.readInt();
	_nextSceneId = f.readInt();
	_frame = f.readInt();
	_actorStartPosition = f.readVector3();
	_actorStartFacing = f.readInt();
	_playerWalkedIn = f.readBool();
}

} // End of namespace BladeRunner
