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

#include "bladerunner/bladerunner.h"
#include "bladerunner/font.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/mouse.h"
#include "bladerunner/savefile.h"
#include "bladerunner/settings.h"
#include "bladerunner/shape.h"
#include "bladerunner/text_resource.h"

#include "common/debug.h"
#include "common/rect.h"
#include "common/util.h"

namespace BladeRunner {

DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) {
	_vm = vm;
	reset();
	_textResource = new TextResource(_vm);
	_shapes = new Shapes(_vm);

	_screenX = 0;
	_screenY = 0;
	_maxItemWidth = 0;
	_fadeInItemIndex = 0;
}

DialogueMenu::~DialogueMenu() {
	delete _textResource;
	delete _shapes;
}

bool DialogueMenu::loadResources() {
	bool r = _textResource->open("DLGMENU");
	if (!r) {
		error("Failed to load dialogue menu text");
	}
	r = _shapes->load("DIALOG.SHP");
	if (!r) {
		error("Failed to load dialogue menu shapes");
	}
	return r;
}

bool DialogueMenu::show() {
	int x, y;

	_vm->_mouse->getXY(&x, &y);

	return showAt(x, y);
}

bool DialogueMenu::showAt(int x, int y) {
	if (_isVisible) {
		return false;
	}

	_isVisible = true;
	_selectedItemIndex = 0;
	_centerX = x;
	_centerY = y;
	calculatePosition(x, y);

	return true;
}

bool DialogueMenu::hide() {
	_waitingForInput = false;
	if (!_isVisible) {
		return false;
	}

	_isVisible = false;
	return true;
}

bool DialogueMenu::clearList() {
	_selectedItemIndex = -1;
	_listSize = 0;
	return true;
}

bool DialogueMenu::addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly) {
	if (_listSize >= kMaxItems) {
		return false;
	}
	if (getAnswerIndex(answer) != -1) {
		return false;
	}

#if BLADERUNNER_ORIGINAL_BUGS
// Original uses incorrect spelling for entry id 1020: DRAGONFLY JEWERLY
	const Common::String &text = _textResource->getText(answer);
#else
	const char *answerTextCP = _textResource->getText(answer);
	if (_vm->_language == Common::EN_ANY && answer == 1020 && strcmp(answerTextCP, "DRAGONFLY JEWERLY") == 0) {
		// fix spelling or entry id 1020 to DRAGONFLY JEWELRY in English version
		answerTextCP = "DRAGONFLY JEWELRY";
	} else if (_vm->_language == Common::IT_ITA && answer == 180 && strcmp(answerTextCP, "AUTOMOBILI") == 0) {
		// fix bad dialogue entry id 180 for Italian version.
		// The entry is supposed to be Grigorian's organization name (which is CARS in English, but CCSR in Italian)
		// However, the original localization has it as "AUTOMOBILI" which is literally "cars" and results in confusion.
		// The other official localizations do not have this issue.
		answerTextCP = "C.C.S.R.";
	}
	const Common::String &text = answerTextCP;
#endif // BLADERUNNER_ORIGINAL_BUGS
	if (text.empty() || text.size() >= 50) {
		return false;
	}

	int index = _listSize++;
	_items[index].text = text;
	_items[index].answerValue = answer;
	_items[index].colorIntensity = 0;
	_items[index].isDone = done;
	_items[index].priorityPolite = priorityPolite;
	_items[index].priorityNormal = priorityNormal;
	_items[index].prioritySurly  = prioritySurly;

	// CHECK(madmoose): BLADE.EXE calls this needlessly
	// calculatePosition();

	return true;
}

/**
* Aux function - used in cut content mode to re-use some NeverRepeatOnceSelected dialogue options for different characters
*/
bool DialogueMenu::clearNeverRepeatWasSelectedFlag(int answer) {
	int foundIndex = -1;
	for (int i = 0; i != _neverRepeatListSize; ++i) {
		if (answer == _neverRepeatValues[i]) {
			foundIndex = i;
			break;
		}
	}

	if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
		_neverRepeatWasSelected[foundIndex] = false;
		return true;
	}
	return false;
}

bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly) {
	int foundIndex = -1;
	for (int i = 0; i != _neverRepeatListSize; ++i) {
		if (answer == _neverRepeatValues[i]) {
			foundIndex = i;
			break;
		}
	}

	if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
		return true;
	}

	if (foundIndex == -1) {
		_neverRepeatValues[_neverRepeatListSize] = answer;
		_neverRepeatWasSelected[_neverRepeatListSize] = false;
		++_neverRepeatListSize;

		assert(_neverRepeatListSize <= 100);
	}

	return addToList(answer, false, priorityPolite, priorityNormal, prioritySurly);
}

bool DialogueMenu::removeFromList(int answer) {
	int index = getAnswerIndex(answer);
	if (index < 0) {
		return false;
	}
	if (index < _listSize - 1) {
		for (int i = index; i < _listSize; ++i) {
			_items[index] = _items[index + 1];
		}
	}
	--_listSize;

	calculatePosition();
	return true;
}

int DialogueMenu::queryInput() {
	if (!_isVisible || _listSize == 0) {
		return -1;
	}

	int answer = -1;
	if (_listSize == 1) {
		_selectedItemIndex = 0;
		answer = _items[_selectedItemIndex].answerValue;
	} else if (_listSize == 2) {
#if BLADERUNNER_ORIGINAL_BUGS
		if (_items[0].isDone) {
			_selectedItemIndex = 1;
			answer = _items[_selectedItemIndex].answerValue;
		} else if (_items[1].isDone) {
			_selectedItemIndex = 0;
			answer = _items[_selectedItemIndex].answerValue;
		}
#else
		// In User Choice mode, avoid auto-select of last option
		// In this mode, player should still have agency to skip the last (non- "DONE")
		// question instead of automatically asking it because the other remaining option is "DONE"
		if (_vm->_settings->getPlayerAgenda() != kPlayerAgendaUserChoice) {
			if (_items[0].isDone) {
				_selectedItemIndex = 1;
				answer = _items[_selectedItemIndex].answerValue;
			} else if (_items[1].isDone) {
				_selectedItemIndex = 0;
				answer = _items[_selectedItemIndex].answerValue;
			}
		}
#endif // BLADERUNNER_ORIGINAL_BUGS
	}

	if (answer == -1) {
		int agenda = _vm->_settings->getPlayerAgenda();
		if (agenda == kPlayerAgendaUserChoice) {
			_waitingForInput = true;
			do {
				while (!_vm->playerHasControl()) {
					_vm->playerGainsControl();
				}

				while (_vm->_mouse->isDisabled()) {
					_vm->_mouse->enable();
				}

				_vm->gameTick();
			} while (_vm->_gameIsRunning && _waitingForInput);
		} else if (agenda == kPlayerAgendaErratic) {
			int tries = 0;
			bool searching = true;
			int i;
			do {
				i = _vm->_rnd.getRandomNumber(_listSize - 1);
				if (!_items[i].isDone) {
					searching = false;
				} else if (++tries > 1000) {
					searching = false;
					i = 0;
				}
			} while (searching);
			_selectedItemIndex = i;
		} else {
			int priority = -1;
			for (int i = 0; i < _listSize; ++i) {
				int priorityCompare = -1;
				if (agenda == kPlayerAgendaPolite) {
					priorityCompare = _items[i].priorityPolite;
				} else if (agenda == kPlayerAgendaNormal) {
					priorityCompare = _items[i].priorityNormal;
				} else if (agenda == kPlayerAgendaSurly) {
					priorityCompare = _items[i].prioritySurly;
				}
				if (priority < priorityCompare) {
					priority = priorityCompare;
					_selectedItemIndex = i;
				}
			}
		}
	}

	answer = _items[_selectedItemIndex].answerValue;
	for (int i = 0; i != _neverRepeatListSize; ++i) {
		if (answer == _neverRepeatValues[i]) {
			_neverRepeatWasSelected[i] = true;
			break;
		}
	}

	// debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text.c_str());

	return answer;
}

int DialogueMenu::listSize() const {
	return _listSize;
}

bool DialogueMenu::isVisible() const {
	return _isVisible;
}

bool DialogueMenu::isOpen() const {
	return _isVisible || _waitingForInput;
}

void DialogueMenu::tick(int x, int y) {
	if (!_isVisible || _listSize == 0) {
		return;
	}

	int line = (y - (_screenY + kBorderSize)) / kLineHeight;
	line = CLIP(line, 0, _listSize - 1);

	_selectedItemIndex = line;
}

void DialogueMenu::draw(Graphics::Surface &s) {
	if (!_isVisible || _listSize == 0) {
		return;
	}

	int fadeInItemIndex = _fadeInItemIndex;
	if (fadeInItemIndex < listSize()) {
		++_fadeInItemIndex;
	}

	for (int i = 0; i != _listSize; ++i) {
		int targetColorIntensity = 0;
		if (i == _selectedItemIndex) {
			targetColorIntensity = 31;
		} else {
			targetColorIntensity = 16;
		}
		if (i > fadeInItemIndex) {
			targetColorIntensity = 0;
		}

		if (_items[i].colorIntensity < targetColorIntensity) {
			_items[i].colorIntensity += 4;
			if (_items[i].colorIntensity > targetColorIntensity) {
				_items[i].colorIntensity = targetColorIntensity;
			}
		} else if (_items[i].colorIntensity > targetColorIntensity) {
			_items[i].colorIntensity -= 2;
			if (_items[i].colorIntensity < targetColorIntensity) {
				_items[i].colorIntensity = targetColorIntensity;
			}
		}
	}

	const int x1 = _screenX;
	const int y1 = _screenY;
	const int x2 = _screenX + kBorderSize + _maxItemWidth;
	const int y2 = _screenY + kBorderSize + _listSize * kLineHeight;

	darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2);

	int x = x1 + kBorderSize;
	int y = y1 + kBorderSize;

	Common::Point mouse = _vm->getMousePos();
	if (mouse.x >= x && mouse.x < x2) {
		s.vLine(mouse.x, y1 + 8, y2 + 2, s.format.RGBToColor(64, 64, 64));
	}
	if (mouse.y >= y && mouse.y < y2) {
		s.hLine(x1 + 8, mouse.y, x2 + 2, s.format.RGBToColor(64, 64, 64));
	}

	_shapes->get(0)->draw(s, x1, y1);
	_shapes->get(3)->draw(s, x2, y1);
	_shapes->get(2)->draw(s, x1, y2);
	_shapes->get(5)->draw(s, x2, y2);

	for (int i = 0; i != _listSize; ++i) {
		_shapes->get(1)->draw(s, x1, y);
		_shapes->get(4)->draw(s, x2, y);
		uint32 color = s.format.RGBToColor((_items[i].colorIntensity / 2) * (256 / 32), (_items[i].colorIntensity / 2) * (256 / 32), _items[i].colorIntensity * (256 / 32));
		_vm->_mainFont->drawString(&s, _items[i].text, x, y, s.w, color);
		y += kLineHeight;
	}
	for (; x != x2; ++x) {
		_shapes->get(6)->draw(s, x, y1);
		_shapes->get(7)->draw(s, x, y2);
	}
}

int DialogueMenu::getAnswerIndex(int answer) const {
	for (int i = 0; i != _listSize; ++i) {
		if (_items[i].answerValue == answer) {
			return i;
		}
	}

	return -1;
}

const char *DialogueMenu::getText(int id) const {
	return _textResource->getText((uint32)id);
}

void DialogueMenu::calculatePosition(int unusedX, int unusedY) {
	_maxItemWidth = 0;
	for (int i = 0; i != _listSize; ++i) {
		_maxItemWidth = MAX(_maxItemWidth, _vm->_mainFont->getStringWidth(_items[i].text));
	}
	_maxItemWidth += 2;

	int w = kBorderSize + _shapes->get(4)->getWidth() + _maxItemWidth;
	int h = kBorderSize + _shapes->get(7)->getHeight() + kLineHeight * _listSize;

	_screenX = _centerX - w / 2;
	_screenY = _centerY - h / 2;

	_screenX = CLIP(_screenX, 0, BladeRunnerEngine::kOriginalGameWidth  - w);
	_screenY = CLIP(_screenY, 0, BladeRunnerEngine::kOriginalGameHeight - h);

	_fadeInItemIndex = 0;
}

void DialogueMenu::mouseUp() {
	_waitingForInput = false;
}

bool DialogueMenu::waitingForInput() const {
	return _waitingForInput;
}

void DialogueMenu::save(SaveFileWriteStream &f) {
	f.writeBool(_isVisible);
	f.writeBool(_waitingForInput);
	f.writeInt(_selectedItemIndex);
	f.writeInt(_listSize);

	f.writeInt(_neverRepeatListSize);
	for (int i = 0; i < 100; ++i) {
		f.writeInt(_neverRepeatValues[i]);
	}
	for (int i = 0; i < 100; ++i) {
		f.writeBool(_neverRepeatWasSelected[i]);
	}
	for (int i = 0; i < 10; ++i) {
		f.writeStringSz(_items[i].text, 50);
		f.writeInt(_items[i].answerValue);
		f.writeInt(_items[i].colorIntensity);
		f.writeInt(_items[i].priorityPolite);
		f.writeInt(_items[i].priorityNormal);
		f.writeInt(_items[i].prioritySurly);
		f.writeInt(_items[i].isDone);
	}
}

void DialogueMenu::load(SaveFileReadStream &f) {
	_isVisible = f.readBool();
	_waitingForInput = f.readBool();
	_selectedItemIndex = f.readInt();
	_listSize = f.readInt();

#if 0
	/* fix for duplicated non-repeated entries in the save game */
	f.readInt();
	_neverRepeatListSize = 0;
	int answer[100];
	bool selected[100];
	for (int i = 0; i < 100; ++i) {
		_neverRepeatValues[i] = -1;
		answer[i] = f.readInt();
	}
	for (int i = 0; i < 100; ++i) {
		_neverRepeatWasSelected[i] = false;
		selected[i] = f.readBool();
	}
	for (int i = 0; i < 100; ++i) {
		int found = false;
		bool value = false;

		for (int j = 0; j < 100; ++j) {
			if (_neverRepeatValues[j] == answer[i]) {
				found = true;
			}
			if (answer[j] == answer[i]) {
				value |= selected[j];
			}
		}

		if (!found) {
			_neverRepeatValues[_neverRepeatListSize] = answer[i];
			_neverRepeatWasSelected[_neverRepeatListSize] = value;
			++_neverRepeatListSize;
		}
	}
#else
	_neverRepeatListSize = f.readInt();
	for (int i = 0; i < 100; ++i) {
		_neverRepeatValues[i] = f.readInt();
	}
	for (int i = 0; i < 100; ++i) {
		_neverRepeatWasSelected[i] = f.readBool();
	}
#endif

	for (int i = 0; i < 10; ++i) {
		_items[i].text = f.readStringSz(50);
		_items[i].answerValue = f.readInt();
		_items[i].colorIntensity = f.readInt();
		_items[i].priorityPolite = f.readInt();
		_items[i].priorityNormal = f.readInt();
		_items[i].prioritySurly = f.readInt();
		_items[i].isDone = f.readInt();
	}
}

void DialogueMenu::clear() {
	_isVisible = false;
	_waitingForInput = false;
	_selectedItemIndex = 0;
	_listSize = 0;
	for (int i = 0; i != kMaxItems; ++i) {
		_items[i].text.clear();
		_items[i].answerValue = -1;
		_items[i].isDone = 0;
		_items[i].priorityPolite = -1;
		_items[i].priorityNormal = -1;
		_items[i].prioritySurly = -1;
		_items[i].colorIntensity = 0;
	}
	_neverRepeatListSize = 0;
	for (int i = 0; i != kMaxRepeatHistory; ++i) {
		_neverRepeatValues[i]      = -1;
		_neverRepeatWasSelected[i] = false;
	}
	_centerX = 0;
	_centerY = 0;
}

void DialogueMenu::reset() {
	clear();
	_textResource = nullptr;
}

void DialogueMenu::darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2) {
	x1 = MAX(x1, 0);
	y1 = MAX(y1, 0);
	x2 = MIN<int32>(x2, BladeRunnerEngine::kOriginalGameWidth);
	y2 = MIN<int32>(y2, BladeRunnerEngine::kOriginalGameHeight);

	if (x1 < x2 && y1 < y2) {
		for (int y = y1; y != y2; ++y) {
			for (int x = x1; x != x2; ++x) {
				void *p = s.getBasePtr(CLIP(x, 0, s.w - 1), CLIP(y, 0, s.h - 1));
				uint8 r, g, b;
				s.format.colorToRGB(READ_UINT32(p), r, g, b);
				r /= 4;
				g /= 4;
				b /= 4;
				drawPixel(s, p, s.format.RGBToColor(r, g, b));
			}
		}
	}
}

} // End of namespace BladeRunner
