/* 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 "common/algorithm.h"
#include "mm/mm1/data/character.h"
#include "mm/shared/utils/strings.h"
#include "mm/mm1/mm1.h"

namespace MM {
namespace MM1 {

static const int CLASS_HP_PER_LEVEL[6] = {
	12, 10, 10, 8, 6, 8
};

Resistances::Resistances() {
	for (int i = 0; i < 8; ++i)
		_arr[i].clear();
}

void Resistances::synchronize(Common::Serializer &s) {
	for (int i = 0; i < 8; ++i)
		_arr[i].synchronize(s);
}

size_t Resistances::getPerformanceTotal() const {
	size_t total = 0;
	for (int i = 0; i < 8; ++i)
		total += _arr[i].getPerformanceTotal();
	return total;
}

void Inventory::clear() {
	_items.clear();
	_items.resize(INVENTORY_COUNT);
}

void Inventory::synchronize(Common::Serializer &s, bool ids) {
	for (int i = 0; i < INVENTORY_COUNT; ++i)
		s.syncAsByte(ids ? _items[i]._id : _items[i]._charges);
}

bool Inventory::empty() const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (_items[i])
			return false;
	}
	return true;
}

bool Inventory::full() const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (!_items[i])
			return false;
	}
	return true;
}

uint Inventory::size() const {
	for (int i = INVENTORY_COUNT - 1; i >= 0; --i) {
		if (_items[i])
			return i + 1;
	}
	return 0;
}

uint Inventory::add(byte id, byte charges) {
	uint idx = getFreeSlot();
	_items[idx]._id = id;
	_items[idx]._charges = charges;

	return idx;
}

int Inventory::getFreeSlot() const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (!_items[i])
			return i;
	}

	error("Inventory is full");
	return -1;
}


void Inventory::removeAt(uint idx) {
	_items.remove_at(idx);
	_items.push_back(Entry());
}

void Inventory::remove(Entry *e) {
	int index = indexOf(e);
	assert(index >= 0);
	removeAt(index);
}

int Inventory::indexOf(Entry *e) const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (e == &_items[i])
			return i;
	}

	return -1;
}

int Inventory::indexOf(byte itemId) const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (_items[i]._id == itemId)
			return i;
	}

	return -1;
}

bool Inventory::hasCategory(CategoryFn fn) const {
	for (uint i = 0; i < INVENTORY_COUNT; ++i) {
		if (fn(_items[i]._id))
			return true;
	}
	return false;
}

void Inventory::removeCharge(Entry *e) {
	if (e->_charges) {
		if (--e->_charges == 0)
			remove(e);
	}
}

size_t Inventory::getPerformanceTotal() const {
	size_t total = 0;
	for (uint i = 0; i < size(); ++i)
		total += (size_t)(*this)[i]._id + (size_t)(*this)[i]._charges;

	return total;
}

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

Character::Character() : PrimaryAttributes() {
	Common::fill(&_flags[0], &_flags[14], 0);
}

void Character::synchronize(Common::Serializer &s, int portraitNum) {
	char name[16];
	if (s.isSaving()) {
		// Save the name in uppercase to match original
		Common::strlcpy(name, uppercase(_name).c_str(), 16);
		s.syncBytes((byte *)name, 16);
	} else {
		s.syncBytes((byte *)name, 16);
		name[15] = '\0';

		if (g_engine->isEnhanced())
			Common::strlcpy(_name, camelCase(name).c_str(), 16);
		else
			Common::strlcpy(_name, uppercase(name).c_str(), 16);
	}

	s.syncAsByte(_sex);
	s.syncAsByte(_alignmentInitial);
	s.syncAsByte(_alignment);
	s.syncAsByte(_race);
	s.syncAsByte(_class);

	_intelligence.synchronize(s);
	_might.synchronize(s);
	_personality.synchronize(s);
	_endurance.synchronize(s);
	_speed.synchronize(s);
	_accuracy.synchronize(s);
	_luck.synchronize(s);
	_level.synchronize(s);
	s.syncAsByte(_age);
	s.syncAsByte(_ageDayCtr);

	s.syncAsUint32LE(_exp);
	s.syncAsUint16LE(_sp._current);
	s.syncAsUint16LE(_sp._base);
	_spellLevel.synchronize(s);

	s.syncAsUint16LE(_gems);
	s.syncAsUint16LE(_hpCurrent);
	s.syncAsUint16LE(_hp);
	s.syncAsUint16LE(_hpMax);

	// Gold field is annoying by being 3 bytes
	uint goldLo = _gold & 0xffff;
	uint goldHi = _gold >> 16;
	s.syncAsUint16LE(goldLo);
	s.syncAsByte(goldHi);
	if (s.isLoading())
		_gold = goldLo | (goldHi << 16);

	_ac.synchronize(s);
	s.syncAsByte(_food);
	s.syncAsByte(_condition);

	_equipped.synchronize(s, true);
	_backpack.synchronize(s, true);
	_equipped.synchronize(s, false);
	_backpack.synchronize(s, false);

	_resistances.synchronize(s);
	_physicalAttr.synchronize(s);
	_missileAttr.synchronize(s);

	s.syncAsByte(_trapCtr);
	s.syncAsByte(_quest);
	s.syncAsByte(_worthiness);
	s.syncAsByte(_alignmentCtr);
	s.syncBytes(_flags, 14);

	s.syncAsByte(_portrait);
	if (s.isLoading()) {
		if (portraitNum != -1)
			_portrait = portraitNum;
		if (_portrait >= NUM_PORTRAITS)
			// Ensure portrait number is valid
			_portrait = 0;
	}

	if (s.isLoading())
		loadFaceSprites();
}

void Character::loadFaceSprites() {
	if (_portrait != 0xff && g_engine->isEnhanced()) {
		Common::Path cname(Common::String::format("char%02d.fac",
			_portrait * 2 + (_sex == MALE ? 0 : 1) + 1));
		_faceSprites.load(cname);
	}
}

void Character::clear() {
	Common::fill(_name, _name + 16, 0);
	_sex = (Sex)0;
	_alignmentInitial = (Alignment)0;
	_alignment = (Alignment)0;
	_race = (Race)0;
	_class = (CharacterClass)0;
	_intelligence = _might = _personality = _endurance = 0;
	_speed = _accuracy = _luck = 0;
	_level = 1;
	_age = _ageDayCtr = 0;
	_exp = 0;
	_sp = 0;
	_spellLevel = 0;
	_gems = 0;
	_hpCurrent = _hp = _hpMax = 0;
	_gold = 0;
	_ac = 0;
	_food = 0;
	_condition = 0;
	_quest = 0;
	_equipped.clear();
	_backpack.clear();

	_alignmentInitial = GOOD;
	_alignment = GOOD;
	_resistances._s._magic.clear();
	_resistances._s._fear.clear();
	_resistances._s._poison.clear();
	_resistances._s._psychic.clear();
	_trapCtr = _alignmentCtr = 0;

	Common::fill(&_flags[0], &_flags[8], 0);
}


void Character::gatherGold() {
	uint total = 0;
	for (uint i = 0; i < g_globals->_party.size(); ++i) {
		total += g_globals->_party[i]._gold;
		g_globals->_party[i]._gold = 0;
	}

	_gold = total;
}

Character::TradeResult Character::trade(int whoTo, int itemIndex) {
	Character &dest = g_globals->_party[whoTo];
	if (&dest == this)
		return TRADE_SUCCESS;

	if (dest._backpack.full())
		return TRADE_FULL;
	if (!_backpack[itemIndex])
		return TRADE_NO_ITEM;

	Inventory::Entry e = _backpack[itemIndex];
	_backpack.removeAt(itemIndex);
	dest._backpack.add(e._id, e._charges);

	return TRADE_SUCCESS;
}

Character::LevelIncrease Character::increaseLevel() {
	++_level;
	++_age;
	if (_age > 220)
		_age = 220;
	_trapCtr += 2;

	int classNum = _class == NONE ? ROBBER : _class;
	int newHP = g_engine->getRandomNumber(CLASS_HP_PER_LEVEL[classNum - 1]);

	if (_endurance._base >= 40)
		newHP += 10;
	else if (_endurance._base >= 35)
		newHP += 9;
	else if (_endurance._base >= 30)
		newHP += 8;
	else if (_endurance._base >= 27)
		newHP += 7;
	else if (_endurance._base >= 24)
		newHP += 6;
	else if (_endurance._base >= 21)
		newHP += 5;
	else if (_endurance._base >= 19)
		newHP += 4;
	else if (_endurance._base >= 17)
		newHP += 3;
	else if (_endurance._base >= 15)
		newHP += 2;
	else if (_endurance._base >= 13)
		newHP += 1;
	else if (_endurance._base >= 9)
		newHP += 0;
	else if (_endurance._base >= 7)
		newHP = MAX(newHP - 1, 1);
	else if (_endurance._base >= 5)
		newHP = MAX(newHP - 2, 1);
	else
		newHP = MAX(newHP - 3, 1);

	_hpCurrent += newHP;
	_hp = _hpMax = _hpCurrent;

	int gainedSpells = 0;
	if (classNum < ARCHER) {
		if (_level._base < 7)
			gainedSpells = 0;
		else if (_level._base == 7)
			gainedSpells = 1;
		else if (_level._base == 9)
			gainedSpells = 2;
		else if (_level._base == 11)
			gainedSpells = 3;
		else if (_level._base == 13)
			gainedSpells = 4;
	} else if (classNum < SORCERER) {
		if (_level._base == 3)
			gainedSpells = 2;
		else if (_level._base == 5)
			gainedSpells = 3;
		else if (_level._base == 7)
			gainedSpells = 4;
		else if (_level._base == 9)
			gainedSpells = 5;
		else if (_level._base == 11)
			gainedSpells = 6;
		else if (_level._base == 13)
			gainedSpells = 7;
	}

	LevelIncrease result;
	result._numHP = newHP;
	result._numSpells = gainedSpells;
	return result;
}

Character::BuyResult Character::buyItem(byte itemId) {
	// Check if backpack is full
	if (_backpack.full())
		return BUY_BACKPACK_FULL;

	// Check character has enough gold
	g_globals->_items.getItem(itemId);
	Item &item = g_globals->_currItem;
	if (_gold < item._cost)
		return BUY_NOT_ENOUGH_GOLD;

	// Add the item
	_gold -= item._cost;
	_backpack.add(itemId, item._maxCharges);

	return BUY_SUCCESS;
}

void Character::updateAttributes() {
	_intelligence.reset();
	_might.reset();
	_personality.reset();
	_endurance.reset();
	_speed.reset();
	_personality.reset();
	_endurance.reset();
	_speed.reset();
	_accuracy.reset();
	_luck.reset();
	_level.reset();
	_spellLevel.reset();
}

void Character::updateAC() {
	int ac = _ac._base;

	if (_speed >= 40)
		ac += 9;
	else if (_speed >= 35)
		ac += 8;
	else if (_speed >= 30)
		ac += 7;
	else if (_speed >= 25)
		ac += 6;
	else if (_speed >= 21)
		ac += 5;
	else if (_speed >= 19)
		ac += 4;
	else if (_speed >= 17)
		ac += 3;
	else if (_speed >= 15)
		ac += 2;
	else if (_speed >= 13)
		ac += 1;
	else if (_speed >= 9)
		ac += 0;
	else if (_speed >= 7)
		ac = MAX(ac - 1, 0);
	else if (_speed >= 5)
		ac = MAX(ac - 2, 0);
	else
		ac = MAX(ac - 3, 0);

	_ac._current = ac;
}

void Character::updateSP() {
	int intelligence = _intelligence._current;
	int personality = _personality._current;
	int level = _level._current;
	int index = 3;
	AttributePair newSP;

	// Spell points only relevant for spell casters
	if (_spellLevel._current) {
		int threshold = -1;
		if (_class == CLERIC)
			threshold = personality;
		else if (_class == SORCERER)
			threshold = intelligence;
		else if (level < 7)
			threshold = -1;
		else {
			level -= 6;
			threshold = (_class == PALADIN) ?
				personality : intelligence;
		}

		if (threshold >= 40)
			index += 10;
		else if (threshold >= 35)
			index += 9;
		else if (threshold >= 30)
			index += 8;
		else if (threshold >= 27)
			index += 7;
		else if (threshold >= 24)
			index += 6;
		else if (threshold >= 21)
			index += 5;
		else if (threshold >= 19)
			index += 4;
		else if (threshold >= 17)
			index += 3;
		else if (threshold >= 15)
			index += 2;
		else if (threshold >= 13)
			index += 1;
		else if (threshold < 5)
			index -= 3;
		else if (threshold < 7)
			index -= 2;
		else if (threshold < 9)
			index -= 1;

		// Calculate the SP
		newSP._base += index * level;
		newSP._current = newSP._base;
	}

	// Set the character's new SP
	_sp = newSP;
}

void Character::updateResistances() {
	for (int i = 0; i < 8; ++i)
		_resistances._arr[i]._current = _resistances._arr[i]._base;
}

Common::String Character::getConditionString() const {
	Common::String result;
	int cond = _condition;

	if (cond == 0) {
		result += STRING["stats.conditions.good"];
	} else if (cond == ERADICATED) {
		result += STRING["stats.conditions.eradicated"];
	} else {
		if (cond & BAD_CONDITION) {
			// Fatal conditions
			if (cond & DEAD)
				result += STRING["stats.conditions.dead"] + ",";
			if (cond & STONE)
				result += STRING["stats.conditions.stone"] + ",";
		} else {
			if (cond & UNCONSCIOUS)
				result += STRING["stats.conditions.unconscious"] + ",";
			if (cond & PARALYZED)
				result += STRING["stats.conditions.paralyzed"] + ",";
			if (cond & POISONED)
				result += STRING["stats.conditions.poisoned"] + ",";
			if (cond & DISEASED)
				result += STRING["stats.conditions.diseased"] + ",";
			if (cond & SILENCED)
				result += STRING["stats.conditions.silenced"] + ",";
			if (cond & BLINDED)
				result += STRING["stats.conditions.blinded"] + ",";
			if (cond & ASLEEP)
				result += STRING["stats.conditions.asleep"] + ",";
		}

		result.deleteLastChar();
	}

	return result;
}

void Character::rest() {
	// Characters with a bad condition like
	// being stoned can't rest
	if (_condition & BAD_CONDITION)
		return;

	updateSP();
	updateAttributes();
	updateAC();
	updateResistances();

	_condition &= ~(ASLEEP | BLINDED | SILENCED |
		PARALYZED | UNCONSCIOUS);

	if (_hpCurrent == 0)
		_hpCurrent = 1;

	// Increment the day counter. When it overflows,
	// it's time to increment the character's age by a year
	if (_ageDayCtr++ > 255) {
		_ageDayCtr = 0;
		if (_age < 255)
			++_age;
	}

	if ((g_engine->getRandomNumber(70) + 80) < _age) {
		// Older characters have a chance of falling unconscious
		_condition = UNCONSCIOUS | BAD_CONDITION;
		return;
	}

	// Fun fact: in the original if any of the attributes reach zero,
	// then it jumps to an instruction that jumps to itself, freezing the game.
	// For ScummVM, I just limit the minimum to 1 instead
	if (_age >= 60) {
		_might._current = MAX(_might._current - 1, 1);
		_endurance._current = MAX(_endurance._current - 1, 1);
		_speed._current = MAX(_speed._current - 1, 1);
	}
	if (_age >= 70) {
		_might._current = MAX(_might._current - 1, 1);
		_endurance._current = MAX(_endurance._current - 1, 1);
		_speed._current = MAX(_speed._current - 1, 1);
	}

	if (_age >= 80) {
		_might._current = MAX((int)_might._current - 2, 1);
	}

	if (_food > 0) {
		--_food;

		if (_condition & POISONED) {
			_hpMax /= 2;
		} else {
			_hpMax = _hp;
		}

		if (!(_condition & DISEASED)) {
			_hpCurrent = _hpMax;
			_sp._current = _sp._base;
		}
	}
}

bool Character::hasItem(byte itemId) const {
	return _backpack.indexOf(itemId) != -1 ||
		_equipped.indexOf(itemId) != -1;
}

#define PERF16(x) ((x & 0xff) + ((x >> 8) & 0xff))
#define PERF32(x) ((x & 0xff) + ((x >> 8) & 0xff) + \
	((x >> 16) & 0xff) + ((x >> 24) & 0xff))

size_t Character::getPerformanceTotal() const {
	size_t totalFlags = 0;
	for (int i = 0; i < 14; ++i)
		totalFlags += _flags[i];

	return (int)_sex
		+ _alignmentInitial
		+ _alignment
		+ _race
		+ _class
		+ _intelligence.getPerformanceTotal()
		+ _might.getPerformanceTotal()
		+ _personality.getPerformanceTotal()
		+ _endurance.getPerformanceTotal()
		+ _speed.getPerformanceTotal()
		+ _accuracy.getPerformanceTotal()
		+ _luck.getPerformanceTotal()
		+ _level.getPerformanceTotal()
		+ (int)_age + (int)_ageDayCtr
		+ PERF32(_exp)
		+ _sp.getPerformanceTotal()
		+ _spellLevel.getPerformanceTotal()
		+ PERF16(_gems)
		+ PERF16(_hpCurrent)
		+ PERF16(_hp)
		+ PERF16(_hpMax)
		+ PERF32(_gold)
		+ _ac
		+ _food
		+ _condition
		+ _equipped.getPerformanceTotal()
		+ _backpack.getPerformanceTotal()
		+ _resistances.getPerformanceTotal()
		+ _physicalAttr.getPerformanceTotal()
		+ _missileAttr.getPerformanceTotal()
		+ _trapCtr
		+ _quest
		+ _worthiness
		+ _alignmentCtr
		+ totalFlags;
}

byte Character::statColor(int amount, int threshold) const {
	if (amount < 1)
		return 6;
	else if (amount > threshold)
		return 2;
	else if (amount == threshold)
		return 15;
	else if (amount >= (threshold / 4))
		return 9;
	else
		return 32;
}

byte Character::conditionColor() const {
	if (_condition == ERADICATED)
		return 32;
	else if (_condition == FINE)
		return 15;
	else if (_condition & BAD_CONDITION)
		return 6;
	else
		return 9;
}

ConditionEnum Character::worstCondition() const {
	if (_condition == ERADICATED) {
		return C_ERADICATED;
	} else if (_condition & BAD_CONDITION) {
		if (_condition & DEAD)
			return C_DEAD;
		if (_condition & STONE)
			return C_STONE;
		if (_condition & UNCONSCIOUS)
			return C_UNCONSCIOUS;
	} else {
		if (_condition & PARALYZED)
			return C_PARALYZED;
		if (_condition & POISONED)
			return C_POISONED;
		if (_condition & DISEASED)
			return C_DISEASED;
		if (_condition & SILENCED)
			return C_SILENCED;
		if (_condition & BLINDED)
			return C_BLINDED;
		if (_condition & ASLEEP)
			return C_ASLEEP;
	}

	return C_GOOD;
}

Common::String Character::getConditionString(ConditionEnum cond) {
	switch (cond) {
	case C_ERADICATED: return STRING["stats.conditions.eradicated"];
	case C_DEAD: return STRING["stats.conditions.dead"];
	case C_STONE: return STRING["stats.conditions.stone"];
	case C_UNCONSCIOUS: return STRING["stats.conditions.unconscious"];
	case C_PARALYZED: return STRING["stats.conditions.paralyzed"];
	case C_POISONED: return STRING["stats.conditions.poisoned"];
	case C_DISEASED: return STRING["stats.conditions.diseased"];
	case C_SILENCED: return STRING["stats.conditions.silenced"];
	case C_BLINDED: return STRING["stats.conditions.blinded"];
	case C_ASLEEP: return STRING["stats.conditions.asleep"];
	default: return STRING["stats.conditions.good"];
	}
}

int Character::spellNumber() const {
	return g_events->isInCombat() ? _combatSpell : _nonCombatSpell;
}

void Character::setSpellNumber(int spellNum) {
	if (g_events->isInCombat())
		_combatSpell = spellNum;
	else
		_nonCombatSpell = spellNum;
}

} // namespace MM1
} // namespace MM
