/* 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 "titanic/room_flags.h"
#include "titanic/titanic.h"
#include "titanic/support/strings.h"

namespace Titanic {

#define ELEVATOR_SHIFT 18
#define ELEVATOR_MASK 3
#define PASSENGER_CLASS_SHIFT 16
#define PASSENGER_CLASS_MASK 3
#define FLOOR_SHIFT 8
#define FLOOR_MASK 0xFF
#define ROOM_SHIFT 1
#define ROOM_MASK 0x7F

struct TransportFlagsEntry {
	const char *const _roomName;
	uint _roomFlags;
};
struct SuccUBusFlagsEntry {
	const char *const _roomName;
	uint _roomFlags;
	PassengerClass _classNum;
};

#define TRANSPORT_ROOMS_SIZE 6
const TransportFlagsEntry TRANSPORT_ROOMS[TRANSPORT_ROOMS_SIZE] = {
	{ "TopOfWell", 0xDF4D1 },
	{ "Pellerator", 0xC95E9 },
	{ "Dome", 0xAD171 },
	{ "Lift", 0x96E45 },
	{ "SGTLeisure", 0x5D3AD },
	{ "ServiceElevator", 0x68797 }
};

#define SUCCUBUS_ROOMS_SIZE 17
const SuccUBusFlagsEntry SUCCUBUS_ROOMS[SUCCUBUS_ROOMS_SIZE] = {
	{ "ParrotLobby", 0x1D0D9, THIRD_CLASS },
	{ "SculptureChamber", 0x465FB, SECOND_CLASS },
	{ "Bar", 0x0B3D97, SECOND_CLASS },
	{ "EmbLobby", 0x0CC971, THIRD_CLASS },
	{ "MoonEmbLobby", 0x0CC971, THIRD_CLASS },
	{ "MusicRoom", 0x0F34DB, SECOND_CLASS },
	{ "MusicRoomLobby", 0x0F34DB, SECOND_CLASS },
	{ "Titania", 0x8A397, THIRD_CLASS },
	{ "BottomOfWell", 0x59FAD, THIRD_CLASS },
	{ "Arboretum", 0x4D6AF, FIRST_CLASS },
	{ "PromenadeDeck", 0x79C45, SECOND_CLASS },
	{ "1stClassRestaurant", 0x896B9, FIRST_CLASS },
	{ "CreatorsChamber", 0x2F86D, SECOND_CLASS },
	{ "CreatorsChamberOn", 0x2F86D, SECOND_CLASS },
	{ "BilgeRoom", 0x3D94B, THIRD_CLASS },
	{ "BilgeRoomWith", 0x3D94B, THIRD_CLASS },
	{ "Bridge", 0x39FCB, THIRD_CLASS }
};

int CRoomFlags::getConditionally() const {
	if (getRoomArea() != 5 || getRoomCategory())
		return _data;
	else
		return 5;
}

bool CRoomFlags::isTransportRoom() const {
	for (int idx = 0; idx < TRANSPORT_ROOMS_SIZE; ++idx) {
		if (TRANSPORT_ROOMS[idx]._roomFlags == _data)
			return true;
	}

	return false;
}

int CRoomFlags::getRoomCategory() const {
	if (getRoomNum() != 0)
		return false;

	CRoomFlags tempFlags = _data;
	tempFlags.setRoomBits(1);
	return tempFlags.getRoomArea() != 5;
}

int CRoomFlags::getRoomArea() const {
	if (isSuccUBusRoomFlags())
		return 4;

	if (!getBit0()) {
		uint v3 = getFloorNum();
		if (v3 <= 38) {
			uint v4 = getRoomNum();
			if (v4 <= 18) {
				uint v6 = getElevatorNum();

				if (v6 >= 1 && v6 <= 4) {
					uint v7 = (int)getPassengerClassNum() - 1;
					if (v7) {
						uint v8 = v7 - 1;
						if (v8) {
							if (v8 == 1 && is28To38(v3) && (v6 & 1) && v4 >= 1)
								return 3;
						} else if (is20To27(v3)) {
							if (v6 & 1) {
								if (v4 >= 1 && v4 <= 3)
									return 2;
							} else if (v4 >= 1 && v4 <= 4) {
								return 2;
							}
						}
					} else if (is2To19(v3) && v4 >= 1 && v4 <= 3) {
						return 1;
					}
				}
			}
		}
	}

	return 5;
}

CString CRoomFlags::getRoomDesc() const {
	Strings &str = g_vm->_strings;

	switch (getRoomArea()) {
	case 1:
	case 2:
	case 3: {
		CString result = getPassengerClassDesc();
		result += ", ";
		result += getFloorDesc();
		result += ", ";
		result += getElevatorDesc();
		result += ", ";
		result += getRoomNumDesc();
		return result;
	}

	case 4:
		switch (_data) {
		case 0x1D0D9:
			return str[THE_PARROT_LOBBY];
		case 0x2F86D:
			return str[THE_CREATORS_CHAMBER];
		case 0x39FCB:
			return str[THE_BRIDGE];
		case 0x3D94B:
			return str[THE_BILGE_ROOM];
		case 0x465FB:
			return str[THE_SCULPTURE_CHAMBER];
		case 0x4D6AF:
			return str[THE_ARBORETUM];
		case 0x59FAD:
			return str[THE_BOTTOM_OF_THE_WELL];
		case 0x79C45:
			return str[THE_PROMENADE_DECK];
		case 0x896B9:
			return str[RESTAURANT_1ST_CLASS];
		case 0x8A397:
			return str[TITANIAS_ROOM];
		case 0xB3D97:
			return str[THE_BAR];
		case 0xCC971:
			return str[THE_EMBARKATION_LOBBY];
		case 0xF34DB:
			return  str[THE_MUSIC_ROOM];
		default:
			break;
		}
		return str[UNKNOWN_ROOM];

	case 5:
		if (isTransportRoom()) {
			switch (_data) {
			case 0x68797:
				return str[THE_SERVICE_ELEVATOR];
			case 0x5D3AD:
				return str[SGT_LEISURE_LOUNGE];
			case 0x96E45:
				return str[THE_ELEVATOR];
			case 0xAD171:
				return str[THE_DOME];
			case 0xC95E9:
				return str[THE_PELLERATOR];
			case 0xDF4D1:
				return str[THE_TOP_OF_THE_WELL];
			default:
				break;
			}
		}

		if (getRoomCategory() == 0) {
			return str[NOWHERE_TO_GO];
		} else {
			CString result = getPassengerClassDesc();
			result += ", ";
			result += getFloorDesc();
			return result;
		}
		break;

	default:
		break;
	}

	return str[UNKNOWN_ROOM];
}

void CRoomFlags::setElevatorBits(uint val) {
	_data &= ~(ELEVATOR_MASK << ELEVATOR_SHIFT);
	_data |= (val & ELEVATOR_MASK) << ELEVATOR_SHIFT;
}

uint CRoomFlags::getElevatorBits() const {
	return (_data >> ELEVATOR_SHIFT) & ELEVATOR_MASK;
}

void CRoomFlags::setPassengerClassBits(uint val) {
	_data &= ~(PASSENGER_CLASS_MASK << PASSENGER_CLASS_SHIFT);
	_data |= (val & PASSENGER_CLASS_MASK) << PASSENGER_CLASS_SHIFT;
}

uint CRoomFlags::getPassengerClassBits() const {
	return (_data >> PASSENGER_CLASS_SHIFT) & PASSENGER_CLASS_MASK;
}

CString CRoomFlags::getPassengerClassDesc() const {
	PassengerClass classNum = getPassengerClassNum();
	Strings &str = g_vm->_strings;

	switch (classNum) {
	case FIRST_CLASS:
		return str[CLASS_1];
	case SECOND_CLASS:
		return str[CLASS_2];
	case THIRD_CLASS:
		return str[CLASS_3];
	default:
		return str[CLASS_NONE];
	}
}

void CRoomFlags::setFloorBits(uint val) {
	_data &= ~(FLOOR_MASK << FLOOR_SHIFT);
	_data |= (val & FLOOR_MASK) << FLOOR_SHIFT;
}

uint CRoomFlags::getFloorBits() const {
	return (_data >> FLOOR_SHIFT) & FLOOR_MASK;
}

uint CRoomFlags::decodeFloorBits(uint bits) const {
	int base = 0;
	int offset = bits & 0xF;

	switch ((bits >> 4) & 0xF) {
	case 9:
		base = 0;
		break;
	case 0xD:
		base = 10;
		break;
	case 0xE:
		base = 20;
		break;
	case 0xF:
		base = 30;
		break;
	default:
		base = 40;
		break;
	}

	return offset >= 10 ? 0 : base + offset;
}

void CRoomFlags::setFloorNum(uint floorNum) {
	uint base = 0;

	switch (floorNum / 10) {
	case 0:
		base = 0x90;
		break;
	case 1:
		base = 0xD0;
		break;
	case 2:
		base = 0xE0;
		break;
	case 3:
		base = 0xF0;
		break;
	default:
		break;
	}

	setFloorBits(base | (floorNum % 10));
}

uint CRoomFlags::getFloorNum() const {
	return decodeFloorBits(getFloorBits());
}

void CRoomFlags::setRoomBits(uint roomBits) {
	_data &= ~(ROOM_MASK << ROOM_SHIFT);
	_data |= (roomBits & ROOM_MASK) << ROOM_SHIFT;
}

uint CRoomFlags::getRoomBits() const {
	return (_data >> ROOM_SHIFT) & ROOM_MASK;
}

bool CRoomFlags::isSuccUBusRoomFlags() const {
	for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
		if (SUCCUBUS_ROOMS[idx]._roomFlags == _data)
			return true;
	}

	return false;
}

bool CRoomFlags::getBit0() const {
	return _data & 1;
}

uint CRoomFlags::getSpecialRoomFlags(const CString &roomName) {
	for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
		if (roomName == SUCCUBUS_ROOMS[idx]._roomName)
			return SUCCUBUS_ROOMS[idx]._roomFlags;
	}

	for (int idx = 0; idx < TRANSPORT_ROOMS_SIZE; ++idx) {
		if (roomName == TRANSPORT_ROOMS[idx]._roomName)
			return TRANSPORT_ROOMS[idx]._roomFlags;
	}

	return 0;
}

PassengerClass CRoomFlags::getSuccUBusClass(const CString &roomName) const {
	for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
		if (roomName == SUCCUBUS_ROOMS[idx]._roomName)
			return SUCCUBUS_ROOMS[idx]._classNum;
	}

	return NO_CLASS;
}

CString CRoomFlags::getSuccUBusRoomName() const {
	for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
		if (SUCCUBUS_ROOMS[idx]._roomFlags == _data)
			return SUCCUBUS_ROOMS[idx]._roomName;
	}

	return CString();
}

void CRoomFlags::changeClass(PassengerClass newClassNum) {
	uint floorNum = getFloorNum();
	uint roomNum = getRoomNum();
	uint elevatorNum = getElevatorNum();
	PassengerClass classNum = getPassengerClassNum();
	uint v10, v11, v12, v13;

	switch (classNum) {
	case FIRST_CLASS:
		v10 = 2;
		v11 = 19;
		v12 = 1;
		v13 = 3;
		break;

	case SECOND_CLASS:
		v10 = 20;
		v11 = 27;
		v12 = 1;
		v13 = (elevatorNum & 1) ? 3 : 4;
		break;

	case THIRD_CLASS:
		v10 = 28;
		v11 = 38;
		v12 = 1;
		v13 = 18;
		break;

	default:
		v10 = 0;
		v11 = 0;
		v12 = 0;
		v13 = 0;
		break;
	}

	// Perform action to change room or floor
	switch (newClassNum) {
	case FIRST_CLASS:
		if (--roomNum < v12)
			roomNum = v12;
		break;

	case SECOND_CLASS:
		if (++roomNum > v13)
			roomNum = v13;
		break;

	case THIRD_CLASS:
		if (--floorNum < v10)
			floorNum = v10;
		break;

	case UNCHECKED:
		if (++floorNum > v11)
			floorNum = v11;

	default:
		break;
	}

	// Set new floor and room
	setFloorNum(floorNum);
	setRoomBits(roomNum);
}

CString CRoomFlags::getElevatorDesc() const {
	return CString::format(g_vm->_strings[ELEVATOR_NUM].c_str(), getElevatorNum());
}

CString CRoomFlags::getFloorDesc() const {
	return CString::format(g_vm->_strings[FLOOR_NUM].c_str(), getFloorNum());
}

CString CRoomFlags::getRoomNumDesc() const {
	return CString::format(g_vm->_strings[ROOM_NUM].c_str(), getRoomNum());
}

bool CRoomFlags::compareClassElevator(uint flags1, uint flags2) {
	CRoomFlags f1(flags1);
	CRoomFlags f2(flags2);

	if (f1.getFloorNum() != f2.getFloorNum())
		return false;

	uint elev1 = f1.getElevatorNum();
	uint elev2 = f2.getElevatorNum();
	PassengerClass class1 = f1.getPassengerClassNum();
	PassengerClass class2 = f2.getPassengerClassNum();

	if (class1 == FIRST_CLASS || class1 == SECOND_CLASS) {
		if (elev1 == 2)
			elev1 = 1;
		else if (elev1 == 4)
			elev1 = 3;
	}
	if (class2 == FIRST_CLASS || class2 == SECOND_CLASS) {
		if (elev2 == 2)
			elev2 = 1;
		else if (elev2 == 4)
			elev2 = 3;
	}

	return elev1 == elev2;
}

bool CRoomFlags::compareLocation(uint flags1, uint flags2) {
	CRoomFlags f1(flags1);
	CRoomFlags f2(flags2);

	return f1.getElevatorNum() == f2.getElevatorNum() &&
		f1.getFloorNum() == f2.getFloorNum() &&
		f1.getRoomNum() == f2.getRoomNum();
}

bool CRoomFlags::isTitania(uint flags1, uint flags2) {
	return flags2 == 0x8A397;
}

void CRoomFlags::setRandomLocation(PassengerClass classNum, bool flag) {
	uint minRoom, elevNum, maxRoom, maxFloor, minFloor;

	do {
		switch (classNum) {
		case FIRST_CLASS:
			minFloor = 2;
			maxFloor = 19;
			minRoom = 1;
			maxRoom = 3;
			elevNum = g_vm->getRandomNumber(flag ? 2 : 3);
			break;

		case SECOND_CLASS:
			minFloor = 20;
			maxFloor = 27;
			elevNum = g_vm->getRandomNumber(flag ? 2 : 3);
			minRoom = 1;
			maxRoom = ((elevNum - 1) & 1) ? 3 : 4;
			break;

		case THIRD_CLASS:
			minRoom = 1;
			minFloor = 28;
			maxFloor = 38;
			maxRoom = 18;
			elevNum = g_vm->getRandomNumber(1) ? 2 : 0;
			break;

		default:
			return;
		}

		uint floorNum = minFloor + g_vm->getRandomNumber(maxFloor - minFloor);
		uint roomNum = minRoom + g_vm->getRandomNumber(maxRoom - minRoom);
		setElevatorBits(elevNum);
		setRoomBits(roomNum);
		setFloorNum(floorNum);
		setPassengerClassBits(classNum);
	} while (_data == 0x59706);
}

PassengerClass CRoomFlags::whatPassengerClass(int floorNum) {
	if (is2To19(floorNum))
		return FIRST_CLASS;

	return is20To27(floorNum) ? SECOND_CLASS : THIRD_CLASS;
}

} // End of namespace Titanic