/* 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 "access/inventory.h"
#include "access/access.h"
#include "access/resources.h"
#include "access/amazon/amazon_resources.h"
#include "access/martian/martian_resources.h"

namespace Access {

void InventoryEntry::load(const Common::String &name, const int *data) {
	_value = ITEM_NOT_FOUND;
	_name = name;
	if (data) {
		_otherItem1 = *data++;
		_newItem1 = *data++;
		_otherItem2 = *data++;
		_newItem2 = *data;
	} else {
		_otherItem1 = -1;
		_newItem1 = -1;
		_otherItem2 = -1;
		_newItem2 = -1;
	}
}

int InventoryEntry::checkItem(int itemId) {
	if (_otherItem1 == itemId)
		return _newItem1;
	else if (_otherItem2 == itemId)
		return _newItem2;
	else
		return -1;
}

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

InventoryManager::InventoryManager(AccessEngine *vm) : Manager(vm) {
	_startInvItem = 0;
	_startInvBox = 0;
	_invChangeFlag = true;
	_invRefreshFlag = false;
	_invModeFlag = false;
	_startAboutItem = 0;
	_startTravelItem = 0;
	_iconDisplayFlag = true;
	_boxNum = 0;

	_inv.resize(_vm->_res->INVENTORY.size());
	for (uint idx = 0; idx < _inv.size(); ++idx)
		_inv[idx].load(_vm->_res->INVENTORY[idx]._desc, _vm->_res->INVENTORY[idx]._combo);

	for (uint i = 0; i < 26; ++i) {
		const int *r = INVCOORDS[i];
		_invCoords.push_back(Common::Rect(r[0], r[2], r[1], r[3]));
	}
}

int &InventoryManager::operator[](int idx) {
	// WORKAROUND: At least in Amazon, some game scripts accidentally do reads
	// beyond the length of the inventory array
	static int invalid = 0;
	return (idx >= (int)_inv.size()) ? invalid : _inv[idx]._value;
}

int InventoryManager::useItem() {
	return _vm->_useItem;
}

void InventoryManager::setUseItem(int itemId) {
	_vm->_useItem = itemId;
}

void InventoryManager::refreshInventory() {
	// The original version was using pre-rendering for the inventory to spare some time.
	// This is not needed on modern hardware, and it breaks a couple of things.
	// Therefore it was removed in order to keep the same logic than for the CD version
	// if (_vm->_screen->_vesaMode) {
	//		_invRefreshFlag = true;
	//		newDisplayInv();
	// }
}

int InventoryManager::newDisplayInv() {
	Screen &screen = *_vm->_screen;
	EventsManager &events = *_vm->_events;
	Room &room = *_vm->_room;
	FileManager &files = *_vm->_files;

	_invModeFlag = true;
	_vm->_timers.saveTimers();

	if (!room._tile && !_invRefreshFlag) {
		saveScreens();
	}

	savedFields();
	screen.setPanel(1);
	events._cursorExitFlag = false;
	getList();
	initFields();

	files._setPaletteFlag = false;
	files.loadScreen(&_vm->_buffer1, 99, 0);
	_vm->_buffer1.copyTo(&_vm->_buffer2);
	_vm->copyBF2Vid();

	// Set cells
	Common::Array<CellIdent> cells;
	cells.push_back(CellIdent(99, 99, 1));
	_vm->loadCells(cells);

	showAllItems();

	if (!_invRefreshFlag) {
		chooseItem();
		if (_vm->_useItem != -1) {
			int savedScale = _vm->_scale;
			_vm->_scale = 153;
			_vm->_screen->setScaleTable(_vm->_scale);
			_vm->_buffer1.clearBuffer();

			SpriteResource *spr = _vm->_objectsTable[99];
			SpriteFrame *frame = spr->getFrame(_vm->_useItem);

			int w = screen._scaleTable1[46];
			int h = screen._scaleTable1[35];
			_vm->_buffer1.sPlotF(frame, Common::Rect(0, 0, w, h));
			events.setCursorData(&_vm->_buffer1, Common::Rect(0, 0, w, h));

			_vm->_scale = savedScale;
			screen.setScaleTable(_vm->_scale);
		}
	}

	freeInvCells();
	screen.setPanel(0);
	events.debounceLeft();

	restoreFields();
	screen.restorePalette();
	// The original was testing the vesa mode too.
	// We removed this check as we don't use pre-rendering
	if (!_invRefreshFlag) {
		screen.clearScreen();
		screen.setPalette();
	}

	if (!room._tile && !_invRefreshFlag) {
		restoreScreens();
	} else {
		screen.setBufferScan();
		room.buildScreen();

		// The original was doing a check on the vesa mode at this point.
		// We don't need it as we don't do inventory pre-rendering
		screen.fadeOut();
		_vm->copyBF2Vid();
	}

	events._cursorExitFlag = false;
	screen._screenChangeFlag = false;
	_invModeFlag = false;
	events.debounceLeft();
	_vm->_timers.restoreTimers();
	_vm->_startup = 1;

	int result = 0;
	if (!_invRefreshFlag) {
		if (_vm->_useItem == -1) {
			result = 2;
			events.forceSetCursor(CURSOR_CROSSHAIRS);
		} else
			events.forceSetCursor(CURSOR_INVENTORY);
	}

	_invRefreshFlag = false;
	_invChangeFlag = false;
	return result;
}

int InventoryManager::displayInv() {
	int *inv = (int *) malloc(_vm->_res->INVENTORY.size() * sizeof(int));
	const char **names = (const char **)malloc(_vm->_res->INVENTORY.size() * sizeof(const char *));

	for (uint i = 0; i < _vm->_res->INVENTORY.size(); i++) {
		inv[i] = _inv[i]._value;
		names[i] = _inv[i]._name.c_str();
	}
	_vm->_events->forceSetCursor(CURSOR_CROSSHAIRS);
	_vm->_invBox->getList(names, inv);

	int btnSelected = 0;
	int boxX = _vm->_invBox->doBox_v1(_startInvItem, _startInvBox, btnSelected);
	_startInvItem = _vm->_boxDataStart;
	_startInvBox = _vm->_boxSelectY;

	if (boxX == -1)
		btnSelected = 2;

	if (btnSelected != 2)
		_vm->_useItem = _vm->_invBox->_tempListIdx[boxX];
	else
		_vm->_useItem = -1;

	free(names);
	free(inv);
	return 0;
}

void InventoryManager::savedFields() {
	Screen &screen = *_vm->_screen;
	Room &room = *_vm->_room;

	_fields._vWindowHeight = screen._vWindowHeight;
	_fields._vWindowLinesTall = screen._vWindowLinesTall;
	_fields._vWindowWidth = screen._vWindowWidth;
	_fields._vWindowBytesWide = screen._vWindowBytesWide;
	_fields._playFieldHeight = room._playFieldHeight;
	_fields._playFieldWidth = room._playFieldWidth;
	_fields._windowXAdd = screen._windowXAdd;
	_fields._windowYAdd = screen._windowYAdd;
	_fields._screenYOff = screen._screenYOff;
	_fields._scrollX = _vm->_scrollX;
	_fields._scrollY = _vm->_scrollY;
	_fields._clipWidth = screen._clipWidth;
	_fields._clipHeight = screen._clipHeight;
	_fields._bufferStart = screen._bufferStart;
	_fields._scrollCol = _vm->_scrollCol;
	_fields._scrollRow = _vm->_scrollRow;
}

void InventoryManager::restoreFields() {
	Screen &screen = *_vm->_screen;
	Room &room = *_vm->_room;

	screen._vWindowHeight = _fields._vWindowHeight;
	screen._vWindowLinesTall = _fields._vWindowLinesTall;
	screen._vWindowWidth = _fields._vWindowWidth;
	screen._vWindowBytesWide = _fields._vWindowBytesWide;
	room._playFieldHeight = _fields._playFieldHeight;
	room._playFieldWidth = _fields._playFieldWidth;
	screen._windowXAdd = _fields._windowXAdd;
	screen._windowYAdd = _fields._windowYAdd;
	screen._screenYOff = _fields._screenYOff;
	_vm->_scrollX = _fields._scrollX;
	_vm->_scrollY = _fields._scrollY;
	screen._clipWidth = _fields._clipWidth;
	screen._clipHeight = _fields._clipHeight;
	screen._bufferStart = _fields._bufferStart;
	_vm->_scrollCol = _fields._scrollCol;
	_vm->_scrollRow = _fields._scrollRow;
}

void InventoryManager::initFields() {
	Screen &screen = *_vm->_screen;
	Room &room = *_vm->_room;

	screen._vWindowHeight = screen.h;
	room._playFieldHeight = screen.h;
	screen._vWindowLinesTall = screen.h;
	screen._clipHeight = screen.h;
	room._playFieldWidth = screen.w;
	screen._vWindowWidth = screen.w;
	screen._vWindowBytesWide = screen.w;
	screen._clipWidth = screen.w;

	screen._windowXAdd = 0;
	screen._windowYAdd = 0;
	screen._screenYOff = 0;
	screen._bufferStart.x = 0;
	screen._bufferStart.y = 0;
	_vm->_scrollX = _vm->_scrollY = 0;

	_vm->_buffer1.clearBuffer();
	_vm->_buffer2.clearBuffer();
	// The original was doing at this point a check on vesa mode
	// We don't need it as we don't do inventory pre-rendering
	if (!_invRefreshFlag)
		screen.clearBuffer();

	screen.savePalette();
}

void InventoryManager::getList() {
	_items.clear();
	_tempLOff.clear();

	for (uint i = 0; i < _inv.size(); ++i) {
		if (_inv[i]._value == ITEM_IN_INVENTORY) {
			_items.push_back(i);
			_tempLOff.push_back(_inv[i]._name);
		}
	}
}

void InventoryManager::showAllItems() {
	_iconDisplayFlag = true;

	for (uint i = 0; i < _items.size(); ++i)
		putInvIcon(i, _items[i]);
}

void InventoryManager::putInvIcon(int itemIndex, int itemId) {
	SpriteResource *spr = _vm->_objectsTable[99];
	assert(spr);
	Common::Point pt((itemIndex % 6) * 46 + 23, (itemIndex / 6) * 35 + 15);
	_vm->_buffer2.plotImage(spr, itemId, pt);

	if (_iconDisplayFlag) {
		_vm->_screen->copyBlock(&_vm->_buffer2, Common::Rect(pt.x, pt.y, pt.x + 46, pt.y + 35));
	}
}

void InventoryManager::chooseItem() {
	EventsManager &events = *_vm->_events;
	_vm->_useItem = -1;

	while (!_vm->shouldQuit()) {
		// Check for events
		events.pollEventsAndWait();

		int selIndex;
		// Poll events and wait for a click on a known area
		if (!events._leftButton || ((selIndex = coordIndexOf()) == -1))
			continue;

		if (selIndex > 23) {
			if (selIndex == 25)
				_vm->_useItem = -1;
			break;
		} else if (selIndex < (int)_items.size() && _items[selIndex] != -1) {
			_boxNum = selIndex;
			_vm->copyBF2Vid();
			combineItems();
			_vm->copyBF2Vid();
			outlineIcon(_boxNum);
			_vm->_useItem = _items[_boxNum];
		}
	}
}

void InventoryManager::freeInvCells() {
	delete _vm->_objectsTable[99];
	_vm->_objectsTable[99] = nullptr;
}

int InventoryManager::coordIndexOf() {
	const Common::Point pt = _vm->_events->_mousePos;

	for (int i = 0; i < (int)_invCoords.size(); ++i) {
		if (_invCoords[i].contains(pt))
			return i;
	}

	return -1;
}

void InventoryManager::saveScreens() {
	_vm->_buffer1.copyTo(&_savedBuffer1);
	_vm->_screen->copyTo(&_savedScreen);
	_vm->_newRects.push_back(Common::Rect(0, 0, _savedScreen.w, _savedScreen.h));

}

void InventoryManager::restoreScreens() {
	_vm->_buffer1.w = _vm->_buffer1.pitch;
	_savedBuffer1.copyTo(&_vm->_buffer1);
	_savedScreen.copyTo(_vm->_screen);

	_savedBuffer1.free();
	_savedScreen.free();
}

void InventoryManager::outlineIcon(int itemIndex) {
	Screen &screen = *_vm->_screen;
	screen.frameRect(_invCoords[itemIndex], 7);

	Common::String s = _tempLOff[itemIndex];
	Font &font = _vm->_fonts._font2;
	int strWidth = font.stringWidth(s);

	font._fontColors[0] = 0;
	font._fontColors[1] = 10;
	font._fontColors[2] = 11;
	font._fontColors[3] = 12;
	font.drawString(&screen, s, Common::Point((screen.w - strWidth) / 2, 184));
}

void InventoryManager::combineItems() {
	Screen &screen = *_vm->_screen;
	EventsManager &events = *_vm->_events;
	screen._leftSkip = screen._rightSkip = 0;
	screen._topSkip = screen._bottomSkip = 0;
	screen._screenYOff = 0;

	Common::Point tempMouse = events._mousePos;
	Common::Point lastMouse = events._mousePos;

	Common::Rect &inv = _invCoords[_boxNum];
	Common::Rect r(inv.left, inv.top, inv.left + 46, inv.top + 35);
	Common::Point tempBox(inv.left, inv.top);
	Common::Point lastBox(inv.left, inv.top);

	_vm->_buffer2.copyBlock(&_vm->_buffer1, r);
	SpriteResource *sprites = _vm->_objectsTable[99];
	int invItem = _items[_boxNum];
	events.pollEvents();

	// Item drag handling loop if left button is held down
	while (!_vm->shouldQuit() && events._leftButton) {
		// Poll for events
		events.pollEventsAndWait();

		// Check positioning
		if (lastMouse == events._mousePos)
			continue;

		lastMouse = events._mousePos;
		Common::Rect lastRect(lastBox.x, lastBox.y, lastBox.x + 46, lastBox.y + 35);
		screen.copyBlock(&_vm->_buffer2, lastRect);

		Common::Point newPt;
		newPt.x = MAX(events._mousePos.x - tempMouse.x + tempBox.x, 0);
		newPt.y = MAX(events._mousePos.y - tempMouse.y + tempBox.y, 0);

		screen.plotImage(sprites, invItem, newPt);
		lastBox = newPt;
	}

	int destBox = events.checkMouseBox1(_invCoords);
	if (destBox >= 0 && destBox != _boxNum && destBox < (int)_items.size()
			&& _items[destBox] != -1) {
		int itemA = invItem;
		int itemB = _items[destBox];

		// Check whether the items can be combined
		int combinedItem = _inv[itemA].checkItem(itemB);
		if (combinedItem != -1) {
			_inv[combinedItem]._value = 1;
			_inv[itemA]._value = 2;
			_inv[itemB]._value = 2;
			_items[_boxNum] = -1;
			_items[destBox] = combinedItem;
			_tempLOff[destBox] = _inv[combinedItem]._name;
			events.hideCursor();

			// Shrink down the first item on top of the second item
			zoomIcon(itemA, itemB, destBox, true);

			// Shrink down the second item
			Common::Rect destRect(_invCoords[destBox].left, _invCoords[destBox].top,
				_invCoords[destBox].left + 46, _invCoords[destBox].top + 35);
			_vm->_buffer2.copyBlock(&_vm->_buffer1, destRect);
			screen._screenYOff = 0;
			zoomIcon(itemB, -1, destBox, true);

			// Exand up the new combined item from nothing to full size
			zoomIcon(combinedItem, -1, destBox, false);

			_boxNum = destBox;
			events.showCursor();
			return;
		}
	}

	_iconDisplayFlag = true;
	putInvIcon(_boxNum, invItem);
}

void InventoryManager::zoomIcon(int zoomItem, int backItem, int zoomBox, bool shrink) {
	Screen &screen = *_vm->_screen;
	screen._screenYOff = 0;
	SpriteResource *sprites = _vm->_objectsTable[99];

	int oldScale = _vm->_scale;
	int zoomScale = shrink ? 255 : 1;
	int zoomInc = shrink ? -1 : 1;
	Common::Rect boxRect(_invCoords[zoomBox].left, _invCoords[zoomBox].top,
		_invCoords[zoomBox].left + 46, _invCoords[zoomBox].top + 35);

	while (!_vm->shouldQuit() && zoomScale != 0 && zoomScale != 256) {
		_vm->_events->pollEventsAndWait();

		_vm->_buffer2.copyBlock(&_vm->_buffer1, boxRect);
		if (backItem != -1) {
			_iconDisplayFlag = false;
			putInvIcon(zoomBox, backItem);
		}

		_vm->_scale = zoomScale;
		screen.setScaleTable(zoomScale);

		int xv = screen._scaleTable1[boxRect.width() + 1];
		if (xv) {
			int yv = screen._scaleTable1[boxRect.height() + 1];
			if (yv) {
				// The zoomed size is positive in both directions, so show zoomed item
				Common::Rect scaledBox(xv, yv);
				scaledBox.moveTo(boxRect.left + (boxRect.width() - xv + 1) / 2,
					boxRect.top + (boxRect.height() - yv + 1) / 2);

				_vm->_buffer2.sPlotF(sprites->getFrame(zoomItem), scaledBox);
			}
		}

		screen.copyBlock(&_vm->_buffer2, boxRect);

		zoomScale += zoomInc;
	}

	if (!shrink) {
		// Handle the final full-size version
		_vm->_buffer2.copyBlock(&_vm->_buffer1, boxRect);
		_vm->_buffer2.plotImage(sprites, zoomItem,
			Common::Point(boxRect.left, boxRect.top));
		screen.copyBlock(&_vm->_buffer2, boxRect);
	}

	_vm->_scale = oldScale;
	screen.setScaleTable(oldScale);
}

void InventoryManager::synchronize(Common::Serializer &s) {
	int count = _inv.size();
	s.syncAsUint16LE(count);

	if (!s.isSaving())
		_inv.resize(count);

	for (int i = 0; i < count; ++i)
		s.syncAsUint16LE(_inv[i]._value);
}

} // End of namespace Access