/* 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 "common/scummsys.h"
#include "mads/mads.h"
#include "mads/action.h"
#include "mads/inventory.h"
#include "mads/resources.h"
#include "mads/scene.h"
#include "mads/staticres.h"

namespace MADS {

void ActionDetails::synchronize(Common::Serializer &s) {
	s.syncAsUint16LE(_verbId);
	s.syncAsUint16LE(_objectNameId);
	s.syncAsUint16LE(_indirectObjectId);
}

void ActionSavedFields::synchronize(Common::Serializer &s) {
	s.syncAsByte(_commandError);
	s.syncAsSint16LE(_commandSource);
	s.syncAsSint16LE(_command);
	s.syncAsSint16LE(_mainObject);
	s.syncAsSint16LE(_secondObject);
	s.syncAsSint16LE(_mainObjectSource);
	s.syncAsSint16LE(_secondObjectSource);
	s.syncAsSint16LE(_articleNumber);
	s.syncAsSint16LE(_lookFlag);
}

/*------------------------------------------------------------------------*/

MADSAction::MADSAction(MADSEngine *vm) : _vm(vm) {
	clear();
	_statusTextIndex = -1;
	_selectedAction = 0;
	_inProgress = false;
	_pickedWord = -1;

	_savedFields._commandSource = CAT_NONE;
	_savedFields._mainObjectSource = CAT_NONE;
	_savedFields._command = -1;
	_savedFields._mainObject = 0;
	_savedFields._secondObject = 0;
	_savedFields._secondObjectSource = CAT_NONE;
	_savedFields._articleNumber = PREP_NONE;
	_savedFields._lookFlag = false;

	_activeAction._verbId = VERB_NONE;
	_activeAction._objectNameId = -1;
	_activeAction._indirectObjectId = -1;
	_savedFields._commandError = false;
	_verbType = VERB_INIT;
	_prepType = PREP_NONE;
}

void MADSAction::clear() {
	_interAwaiting = AWAITING_COMMAND;
	_commandSource = CAT_NONE;
	_mainObjectSource = CAT_NONE;
	_secondObjectSource = CAT_NONE;
	_recentCommandSource = CAT_NONE;
	_articleNumber = 0;
	_lookFlag = false;
	_pointEstablished = 0;
	_statusText.clear();
	_selectedRow = -1;
	_hotspotId = -1;
	_secondObject = -1;
	_recentCommand = -1;
	_action._verbId = VERB_NONE;
	_action._objectNameId = -1;
	_action._indirectObjectId = -1;
	_textChanged = true;
}

void MADSAction::appendVocab(int vocabId, bool capitalize) {
	Common::String vocabStr = _vm->_game->_scene.getVocab(vocabId);
	if (capitalize)
		vocabStr.setChar(toupper(vocabStr[0]), 0);

	_statusText += vocabStr;
	_statusText += " ";
}

void MADSAction::startWalkingDirectly(int walkType) {
	Scene &scene = _vm->_game->_scene;
	Player &player = _vm->_game->_player;

	if (_pointEstablished && (walkType == -3 || _savedFields._command < 0)) {
		player._needToWalk = true;
		player._prepareWalkPos = scene._customDest;
	}
}

void MADSAction::set() {
	Scene &scene = _vm->_game->_scene;
	UserInterface &userInterface = scene._userInterface;
	_statusText = "";

	_action._verbId = VERB_NONE;
	_action._objectNameId = -1;
	_action._indirectObjectId = -1;

	if (_commandSource == CAT_TALK_ENTRY) {
		// Handle showing the conversation selection. Rex at least doesn't actually seem to use this
		if (_selectedRow >= 0) {
			_action._verbId = userInterface._talkIds[_selectedRow];
			Common::String desc = userInterface._talkStrings[_selectedRow];
			if (!desc.empty())
				_statusText = desc;
		}
	} else if (_lookFlag && (_selectedRow == 0)) {
		// Two 'look' actions in succession, so the action becomes 'Look around'
		_statusText = kLookAroundStr;
	} else {
		bool flag = false;
		if ((_commandSource == CAT_INV_VOCAB) && (_selectedRow >= 0)
				&& (_verbType == VERB_THAT) && (_prepType == PREP_NONE)) {
			// Use/to action
			int invIndex = userInterface._selectedInvIndex;
			InventoryObject &objEntry = _vm->_game->_objects.getItem(invIndex);

			_action._objectNameId = objEntry._descId;
			_action._verbId = objEntry._vocabList[_selectedRow]._vocabId;

			// Set up the status text string
			_statusText = kUseStr;
			appendVocab(_action._objectNameId);
			_statusText += kToStr;
			appendVocab(_action._verbId);
		} else {
			// Handling for if an action has been selected
			if (_selectedRow >= 0) {
				if (_commandSource == CAT_COMMAND) {
					// Standard verb action
					_action._verbId = scene._verbList[_selectedRow]._id;
				} else {
					// Selected action on an inventory object
					int invIndex = userInterface._selectedInvIndex;
					InventoryObject &objEntry = _vm->_game->_objects.getItem(invIndex);

					_action._verbId = objEntry._vocabList[_selectedRow]._vocabId;
				}

				appendVocab(_action._verbId, true);

				if (_action._verbId == VERB_LOOK) {
					// Add in the word 'add'
					_statusText += kArticleList[PREP_AT];
					_statusText += " ";
				}
			}

			// Add in any necessary article if necessary
			if ((_hotspotId >= 0) && (_selectedRow >= 0) && (_articleNumber > 0) && (_verbType == VERB_THAT)) {
				flag = true;

				_statusText += kArticleList[_articleNumber];
				_statusText += " ";
			}

			// Handling for hotspot
			if (_hotspotId >= 0) {
				if (_selectedRow < 0) {
					int verbId;

					if (_hotspotId < (int)scene._hotspots.size()) {
						// Get the verb Id from the hotspot
						verbId = scene._hotspots[_hotspotId]._verbId;
					} else {
						// Get the verb Id from the scene object
						verbId = scene._dynamicHotspots.get(_hotspotId - scene._hotspots.size())._verbId;
					}

					if (verbId > 0) {
						// Set the specified action
						_action._verbId = verbId;
						appendVocab(_action._verbId, true);
					} else {
						// Default to a standard 'walk to'
						_action._verbId = VERB_WALKTO;
						_statusText += kWalkToStr;
					}
				}

				if ((_mainObjectSource == CAT_INV_LIST) || (_mainObjectSource == CAT_INV_ANIM)) {
					// Get name from given inventory object
					InventoryObject &invObject = _vm->_game->_objects.getItem(_hotspotId);
					_action._objectNameId = invObject._descId;
				} else if (_hotspotId < (int)scene._hotspots.size()) {
					// Get name from scene hotspot
					_action._objectNameId = scene._hotspots[_hotspotId]._vocabId;
				} else {
					// Get name from temporary scene hotspot
					_action._objectNameId = scene._dynamicHotspots.get(_hotspotId - scene._hotspots.size())._descId;
				}
				appendVocab(_action._objectNameId);
			}
		}

		if (_secondObject >= 0) {
			if (_secondObjectSource == CAT_INV_LIST || _secondObjectSource == CAT_INV_ANIM) {
				InventoryObject &invObject = _vm->_game->_objects.getItem(_secondObject);
				_action._indirectObjectId = invObject._descId;
			} else if (_secondObject < (int)scene._hotspots.size()) {
				_action._indirectObjectId = scene._hotspots[_secondObject]._vocabId;
			} else {
				_action._indirectObjectId = scene._dynamicHotspots.get(_secondObject - scene._hotspots.size())._descId;
			}
		}

		if ((_hotspotId >= 0) && (_articleNumber > 0) && !flag) {
			if (_articleNumber == PREP_RELATIONAL) {
				if (_secondObject >= 0) {
					int articleNum = 0;

					if ((_secondObjectSource == 2) || (_secondObjectSource == 5)) {
						InventoryObject &invObject = _vm->_game->_objects.getItem(_secondObject);
						articleNum = invObject._article;
					} else if (_secondObject < (int)scene._hotspots.size()) {
						articleNum = scene._hotspots[_secondObject]._articleNumber;
					} else {
						articleNum = scene._dynamicHotspots.get(_secondObject - scene._hotspots.size())._articleNumber;
					}

					_statusText += kArticleList[articleNum];
				}
			} else if ((_articleNumber != VERB_LOOK) || (_vm->getGameID() != GType_RexNebular) ||
				(_action._indirectObjectId >= 0 && scene.getVocab(_action._indirectObjectId) != kFenceStr)) {
				// Write out the article
				_statusText += kArticleList[_articleNumber];
			} else {
				// Special case for a 'fence' entry in Rex Nebular
				_statusText += kOverStr;
			}

			_statusText += " ";
		}

		// Append object description if necessary
		if (_secondObject >= 0)
			appendVocab(_action._indirectObjectId);

		// Remove any trailing space character
		if (_statusText.hasSuffix(" "))
			_statusText.deleteLastChar();
	}

	_textChanged = true;
}

void MADSAction::refresh() {
	Scene &scene = _vm->_game->_scene;

	// Exit immediately if nothing has changed
	if (!_textChanged)
		return;

	// Remove any old copy of the status text
	if (_statusTextIndex >= 0) {
		scene._textDisplay.expire(_statusTextIndex);
		_statusTextIndex = -1;
	}

	if (!_statusText.empty()) {
		if ((_vm->_game->_screenObjects._inputMode == kInputBuildingSentences) ||
				(_vm->_game->_screenObjects._inputMode == kInputLimitedSentences)) {
			Font *font = _vm->_font->getFont(FONT_MAIN);
			int textSpacing = -1;

			int strWidth = font->getWidth(_statusText);
			if (strWidth > MADS_SCREEN_WIDTH) {
				// Too large to fit, so fall back on interface font
				font = _vm->_font->getFont(FONT_INTERFACE);
				strWidth = font->getWidth(_statusText, 0);
				textSpacing = 0;
			}

			// Add a new text display entry to display the status text at the bottom of the screen area
			_statusTextIndex = scene._textDisplay.add(160 - (strWidth / 2),
				MADS_SCENE_HEIGHT + scene._posAdjust.y - 13, 3, textSpacing, _statusText, font);
		}
	}

	_textChanged = false;
}

void MADSAction::startAction() {
	Game &game = *_vm->_game;
	Player &player = game._player;
	Scene &scene = _vm->_game->_scene;
	DynamicHotspots &dynHotspots = scene._dynamicHotspots;
	Hotspots &hotspots = scene._hotspots;

	player.cancelCommand();

	_inProgress = true;
	_savedFields._commandError = false;
	_savedFields._command = _selectedRow;
	_savedFields._mainObject = _hotspotId;
	_savedFields._secondObject = _secondObject;
	_savedFields._articleNumber = _articleNumber;
	_savedFields._commandSource = _commandSource;
	_savedFields._mainObjectSource = _mainObjectSource;
	_savedFields._secondObjectSource = _secondObjectSource;
	_savedFields._lookFlag = _lookFlag;
	_activeAction = _action;

	// Copy the action to be active
	_activeAction = _action;
	_sentence = _statusText;

	if ((_mainObjectSource == CAT_HOTSPOT) && (_secondObjectSource == 4))
		_savedFields._commandError = true;

	player._needToWalk = false;
	int hotspotId = -1;

	if (!_savedFields._lookFlag && (_vm->_game->_screenObjects._inputMode != kInputConversation)) {
		if (_savedFields._mainObjectSource == CAT_HOTSPOT)
			hotspotId = _savedFields._mainObject;
		else if (_secondObjectSource == 4)
			hotspotId = _savedFields._secondObject;

		if (hotspotId >= (int)hotspots.size()) {
			DynamicHotspot &hs = dynHotspots.get(hotspotId - hotspots.size());
			if ((hs._feetPos.x == -1) || (hs._feetPos.x == -3)) {
				startWalkingDirectly(hs._feetPos.x);
			} else if (hs._feetPos.x < 0) {
				player._prepareWalkFacing = hs._facing;
			} else if (_savedFields._commandSource == CAT_NONE || hs._cursor < CURSOR_WAIT) {
				player._needToWalk = true;
				player._prepareWalkPos = hs._feetPos;
			}

			player._prepareWalkFacing = hs._facing;
			hotspotId = -1;
		}
	}

	if (hotspotId >= 0) {
		Hotspot &hs = hotspots[hotspotId];

		if (hs._feetPos.x == -1 || hs._feetPos.x == -3) {
			startWalkingDirectly(hs._feetPos.x);
		} else if (hs._feetPos.x >= 0) {
			if (_savedFields._commandSource == CAT_NONE || hs._cursor < CURSOR_WAIT) {
				player._needToWalk = true;
				player._prepareWalkPos = hs._feetPos;
			}
		}

		player._prepareWalkFacing = hs._facing;
	}

	player._readyToWalk = player._needToWalk;
}

void MADSAction::checkAction() {
	if (isAction(VERB_LOOK) || isAction(VERB_THROW))
		_vm->_game->_player._needToWalk = false;
}

bool MADSAction::isAction(int verbId, int objectNameId, int indirectObjectId) {
	if (_activeAction._verbId != verbId)
		return false;
	if ((objectNameId != 0) && (_activeAction._objectNameId != objectNameId))
		return false;
	if ((indirectObjectId != 0) && (_activeAction._indirectObjectId != indirectObjectId))
		return false;

	return true;
}

bool MADSAction::isObject(int objectNameId) {
	return _activeAction._objectNameId == objectNameId;
}

bool MADSAction::isTarget(int objectNameId) {
	return _activeAction._indirectObjectId == objectNameId;
}

void MADSAction::checkActionAtMousePos() {
	Scene &scene = _vm->_game->_scene;
	UserInterface &userInterface = scene._userInterface;

	if ((userInterface._category == CAT_COMMAND || userInterface._category == CAT_INV_VOCAB) &&
			_interAwaiting != AWAITING_COMMAND && _pickedWord >= 0) {
		if (_recentCommandSource == userInterface._category || _recentCommand != _pickedWord ||
			(_interAwaiting != AWAITING_THIS && _interAwaiting != 3))
			clear();
		else if (_selectedRow != 0 || userInterface._category != CAT_COMMAND)
			scene._lookFlag = false;
		else
			scene._lookFlag = true;
	}

	if (_vm->_events->_rightMousePressed && _vm->_events->_mouseButtons) {
		switch (userInterface._category) {
		case CAT_COMMAND:
		case CAT_INV_VOCAB:
			return;

		case CAT_INV_LIST:
		case CAT_HOTSPOT:
		case CAT_INV_ANIM:
			if (_interAwaiting != AWAITING_THAT) {
				if (userInterface._selectedActionIndex >= 0) {
					_commandSource = CAT_COMMAND;
					_selectedRow = userInterface._selectedActionIndex;
					_verbType = scene._verbList[_selectedRow]._verbType;
					_prepType = scene._verbList[_selectedRow]._prepType;
					_interAwaiting = AWAITING_THIS;
				} else if (userInterface._selectedItemVocabIdx >= 0) {
					_commandSource = CAT_INV_VOCAB;
					_selectedRow = userInterface._selectedItemVocabIdx;
					int objectId = _vm->_game->_objects._inventoryList[_selectedRow];
					InventoryObject &invObject = _vm->_game->_objects[objectId];

					_verbType = invObject._vocabList[_selectedRow - 1]._verbType;
					_prepType = invObject._vocabList[_selectedRow - 1]._prepType;
					_mainObjectSource = CAT_INV_LIST;
					_hotspotId = userInterface._selectedInvIndex;
					_articleNumber = _prepType;

					if ((_verbType == VERB_THIS && _prepType == PREP_NONE) ||
							(_verbType == VERB_THAT && _prepType != PREP_NONE))
						_interAwaiting = AWAITING_RIGHT_MOUSE;
					else
						_interAwaiting = AWAITING_THAT;
				}
			}
			break;

		default:
			break;
		}
	}

	switch (_interAwaiting) {
	case AWAITING_COMMAND:
		_articleNumber = 0;
		switch (userInterface._category) {
		case CAT_COMMAND:
			_commandSource = CAT_COMMAND;
			_selectedRow = _pickedWord;
			if (_selectedRow >= 0) {
				_verbType = scene._verbList[_selectedRow]._verbType;
				_prepType = scene._verbList[_selectedRow]._prepType;
			}
			break;

		case CAT_INV_VOCAB:
			_commandSource = CAT_INV_VOCAB;
			_selectedRow = _pickedWord;
			if (_selectedRow < 0) {
				_hotspotId = -1;
				_mainObjectSource = CAT_NONE;
			} else {
				InventoryObject &invObject = _vm->_game->_objects.getItem(userInterface._selectedInvIndex);
				_verbType = invObject._vocabList[_selectedRow]._verbType;
				_prepType = invObject._vocabList[_selectedRow]._prepType;
				_hotspotId = userInterface._selectedInvIndex;
				_mainObjectSource = CAT_INV_LIST;

				if (_verbType == VERB_THAT)
					_articleNumber = _prepType;
			}
			break;

		case CAT_HOTSPOT:
			_selectedRow = -1;
			_commandSource = CAT_NONE;
			_mainObjectSource = CAT_HOTSPOT;
			_hotspotId = _pickedWord;
			break;

		case CAT_TALK_ENTRY:
			_commandSource = CAT_TALK_ENTRY;
			_selectedRow = _pickedWord;
			break;

		default:
			break;
		}
		break;

	case AWAITING_THIS:
		_articleNumber = 0;
		switch (userInterface._category) {
		case CAT_INV_LIST:
		case CAT_HOTSPOT:
		case CAT_INV_ANIM:
			_mainObjectSource = userInterface._category;
			_hotspotId = _pickedWord;
			break;
		default:
			break;
		}
		break;

	case AWAITING_THAT:
		switch (userInterface._category) {
		case CAT_INV_LIST:
		case CAT_HOTSPOT:
		case CAT_INV_ANIM:
			_secondObjectSource = userInterface._category;
			_secondObject = _pickedWord;
			break;
		default:
			break;
		}
		break;

	default:
		break;
	}
}

void MADSAction::leftClick() {
	Scene &scene = _vm->_game->_scene;
	UserInterface &userInterface = scene._userInterface;
	bool abortFlag = false;

	if ((userInterface._category == CAT_COMMAND || userInterface._category == CAT_INV_VOCAB) &&
		_interAwaiting != 1 && _pickedWord >= 0 &&
			_recentCommandSource == userInterface._category && _recentCommand == _pickedWord &&
			(_interAwaiting == 2 || userInterface._category == CAT_INV_VOCAB)) {
		abortFlag = true;
		if (_selectedRow == 0 && userInterface._category == CAT_COMMAND) {
			_selectedAction = CAT_COMMAND;
			scene._lookFlag = true;
		} else {
			_selectedAction = CAT_NONE;
			scene._lookFlag = false;
			clear();
		}
	}

	if (abortFlag || (_vm->_events->_rightMousePressed && (userInterface._category == CAT_COMMAND ||
			userInterface._category == CAT_INV_VOCAB)))
		return;

	switch (_interAwaiting) {
	case AWAITING_COMMAND:
		switch (userInterface._category) {
		case CAT_COMMAND:
			if (_selectedRow >= 0) {
				if (_verbType == VERB_ONLY)
					_selectedAction = -1;
				else {
					_recentCommand = _selectedRow;
					_recentCommandSource = _commandSource;
					_interAwaiting = AWAITING_THIS;
				}
			}
			break;

		case CAT_INV_LIST:
			if (_pickedWord >= 0) {
				userInterface.selectObject(_pickedWord);
			}
			break;

		case CAT_INV_VOCAB:
			if (_selectedRow >= 0) {
				if (_verbType != VERB_THIS || _prepType != PREP_NONE) {
					if (_verbType != VERB_THAT || _prepType == PREP_NONE) {
						_interAwaiting = AWAITING_THAT;
						_articleNumber = _prepType;
					} else {
						_articleNumber = _prepType;
						_selectedAction = -1;
					}
				} else {
					_selectedAction = -1;
				}

				_recentCommand = _selectedRow;
				_recentCommandSource = _commandSource;
			}
			break;

		case CAT_HOTSPOT:
			_recentCommand = -1;
			_recentCommandSource = CAT_NONE;

			if (_vm->_events->currentPos().y < MADS_SCENE_HEIGHT) {
				scene._customDest = _vm->_events->currentPos() + scene._posAdjust;
				_selectedAction = -1;
				_pointEstablished = true;
			}
			break;

		case CAT_TALK_ENTRY:
			if (_selectedRow >= 0)
				_selectedAction = -1;
			break;

		default:
			break;
		}
		break;

	case AWAITING_THIS:
		switch (userInterface._category) {
		case CAT_INV_LIST:
		case CAT_HOTSPOT:
		case CAT_INV_ANIM:
			if (_hotspotId >= 0) {
				if (_prepType) {
					_articleNumber = _prepType;
					_interAwaiting = AWAITING_THAT;
				} else {
					_selectedAction = -1;
				}

				if (userInterface._category == CAT_HOTSPOT) {
					scene._customDest = _vm->_events->mousePos() + scene._posAdjust;
					_pointEstablished = true;
				}
			}
			break;
		default:
			break;
		}
		break;

	case AWAITING_THAT:
		switch (userInterface._category) {
		case CAT_INV_LIST:
		case CAT_HOTSPOT:
		case CAT_INV_ANIM:
			if (_secondObject >= 0) {
				_selectedAction = -1;

				if (userInterface._category == CAT_HOTSPOT) {
					if (!_pointEstablished) {
						scene._customDest = _vm->_events->mousePos() + scene._posAdjust;
						_pointEstablished = true;
					}
				}
			}
			break;
		default:
			break;
		}
		break;

	default:
		break;
	}
}

void MADSAction::synchronize(Common::Serializer &s) {
	_action.synchronize(s);
	_activeAction.synchronize(s);
	s.syncAsSint16LE(_articleNumber);
	s.syncAsByte(_lookFlag);
	s.syncAsByte(_textChanged);
	s.syncAsSint16LE(_selectedRow);
	s.syncAsSint16LE(_selectedAction);
	s.syncAsSint16LE(_statusTextIndex);
	s.syncAsSint16LE(_hotspotId);
	_savedFields.synchronize(s);

	// TODO: When saving in Rex Village Hut, _senetence size() doesn't match
	// string length. Find out why not
	_sentence = Common::String(_sentence.c_str());
	s.syncString(_sentence);

	s.syncAsSint16LE(_verbType);
	s.syncAsSint16LE(_prepType);
	s.syncAsSint16LE(_commandSource);
	s.syncAsSint16LE(_mainObjectSource);
	s.syncAsSint16LE(_secondObject);
	s.syncAsSint16LE(_secondObjectSource);
	s.syncAsSint16LE(_recentCommandSource);
	s.syncAsSint16LE(_recentCommand);
	s.syncAsSint16LE(_interAwaiting);
	s.syncAsSint16LE(_pickedWord);
	s.syncAsByte(_pointEstablished);
	s.syncAsByte(_inProgress);
}

} // End of namespace MADS