/**
 * Tibia GIMUD Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
 *
 * 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 "otpch.h"

#include "items.h"
#include "spells.h"
#include "movement.h"
#include "script.h"

#include "pugicast.h"

extern MoveEvents* g_moveEvents;

Items::Items()
{
	items.reserve(6000);
	nameToItems.reserve(6000);
}

void Items::clear()
{
	items.clear();
	nameToItems.clear();
}

bool Items::reload()
{
	clear();
	if (!loadItems()) {
		return false;
	}

	g_moveEvents->reload();
	return true;
}

bool Items::loadItems()
{
	ScriptReader script;
	if (!script.open("data/items/items.srv")) {
		return false;
	}

	std::string identifier;
	uint16_t id = 0;
	while (true) {
		script.nextToken();
		if (script.Token == ENDOFFILE) {
			break;
		}

		if (script.Token != IDENTIFIER) {
			script.error("Identifier expected");
			return false;
		}

		identifier = script.getIdentifier();
		script.readSymbol('=');

		if (identifier == "typeid") {
			id = script.readNumber();
			if (id >= items.size()) {
				items.resize(id + 1);
			}

			if (items[id].id) {
				script.error("item type already defined");
				return false;
			}

			items[id].id = id;
		} else if (identifier == "name") {
			items[id].name = script.readString();
		} else if (identifier == "description") {
			items[id].description = script.readString();
		} else if (identifier == "flags") {
			script.readSymbol('{');
			while (true) {
				while (true) {
					script.nextToken();
					if (script.Token == SPECIAL) {
						break;
					}

					identifier = script.getIdentifier();
					
					if (identifier == "bank") {
						items[id].group = ITEM_GROUP_GROUND;
					} else if (identifier == "clip") {
						items[id].alwaysOnTop = true;
						items[id].alwaysOnTopOrder = 1;
					} else if (identifier == "bottom") {
						items[id].alwaysOnTop = true;
						items[id].alwaysOnTopOrder = 2;
					} else if (identifier == "top") {
						items[id].alwaysOnTop = true;
						items[id].alwaysOnTopOrder = 3;
					} else if (identifier == "container") {
						items[id].type = ITEM_TYPE_CONTAINER;
					} else if (identifier == "chest") {
						items[id].type = ITEM_TYPE_CHEST;
					} else if (identifier == "cumulative") {
						items[id].stackable = true;
					} else if (identifier == "changeuse") {
						items[id].changeUse = true;
					} else if (identifier == "forceuse") {
						items[id].forceUse = true;
					} else if (identifier == "key") {
						items[id].type = ITEM_TYPE_KEY;
						items[id].group = ITEM_GROUP_KEY;
					} else if (identifier == "door") {
						items[id].type = ITEM_TYPE_DOOR;
					} else if (identifier == "bed") {
						items[id].type = ITEM_TYPE_BED;
					} else if (identifier == "rune") {
						items[id].type = ITEM_TYPE_RUNE;
					} else if (identifier == "depot") {
						items[id].type = ITEM_TYPE_DEPOT;
					} else if (identifier == "mailbox") {
						items[id].type = ITEM_TYPE_MAILBOX;
					} else if (identifier == "allowdistread") {
						items[id].allowDistRead = true;
					} else if (identifier == "text") {
						items[id].canReadText = true;
					} else if (identifier == "write") {
						items[id].canWriteText = true;
					} else if (identifier == "writeonce") {
						items[id].canWriteText = true;
						items[id].writeOnceItemId = id;
					} else if (identifier == "fluidcontainer") {
						items[id].group = ITEM_GROUP_FLUID;
					} else if (identifier == "splash") {
						items[id].group = ITEM_GROUP_SPLASH;
					} else if (identifier == "unpass") {
						items[id].blockSolid = true;
					} else if (identifier == "unmove") {
						items[id].moveable = false;
					} else if (identifier == "unthrow") {
						items[id].blockProjectile = true;
					} else if (identifier == "unlay") {
						items[id].allowPickupable = false;
					} else if (identifier == "avoid") {
						items[id].blockPathFind = true;
					} else if (identifier == "magicfield") {
						items[id].type = ITEM_TYPE_MAGICFIELD;
						items[id].group = ITEM_GROUP_MAGICFIELD;
					} else if (identifier == "take") {
						items[id].pickupable = true;
					} else if (identifier == "hang") {
						items[id].isHangable = true;
					} else if (identifier == "hooksouth") {
						items[id].isHorizontal = true;
					} else if (identifier == "hookeast") {
						items[id].isVertical = true;
					} else if (identifier == "rotate") {
						items[id].rotatable = true;
					} else if (identifier == "destroy") {
						items[id].destroy = true;
					} else if (identifier == "corpse") {
						items[id].corpse = true;
					} else if (identifier == "expire") {
						items[id].stopTime = false;
					} else if (identifier == "expirestop") {
						items[id].stopTime = true;
					} else if (identifier == "weapon") {
						items[id].group = ITEM_GROUP_WEAPON;
					} else if (identifier == "shield") {
						items[id].weaponType = WEAPON_SHIELD;
					} else if (identifier == "distance") {
						items[id].weaponType = WEAPON_DISTANCE;
					} else if (identifier == "wand") {
						items[id].weaponType = WEAPON_WAND;
					} else if (identifier == "ammo") {
						items[id].weaponType = WEAPON_AMMO;
					} else if (identifier == "armor") {
						items[id].group = ITEM_GROUP_ARMOR;
					} else if (identifier == "height") {
						items[id].hasHeight = true;
					} else if (identifier == "disguise") {
						items[id].disguise = true;
					} else if (identifier == "showdetail") {
						items[id].showDuration = true;
					} else if (identifier == "noreplace") {
						items[id].replaceable = false;
					} else if (identifier == "collisionevent") {
						items[id].collisionEvent = true;
					} else if (identifier == "separationevent") {
						items[id].separationEvent = true;
					} else if (identifier == "useevent") {
						items[id].useEvent = true;
					} else if (identifier == "distuse") {
						items[id].distUse = true;
					} else if (identifier == "multiuse") {
						items[id].multiUseEvent = true;
					} else {
						script.error("Unknown flag");
						return false;
					}
				}

				if (script.getSpecial() == '}') {
					break;
				}

				if (script.Token != SPECIAL || script.getSpecial() != ',') {
					continue;
				}
			}
		} else if (identifier == "attributes") {
			script.readSymbol('{');
			while (true) {
				while (true) {
					script.nextToken();
					if (script.Token == SPECIAL) {
						break;
					}

					identifier = script.getIdentifier();
					script.readSymbol('=');

					if (identifier == "waypoints") {
						items[id].speed = script.readNumber();
					} else if (identifier == "capacity") {
						items[id].maxItems = script.readNumber();
					} else if (identifier == "changetarget") {
						items[id].transformToOnUse = script.readNumber();
					} else if (identifier == "nutrition") {
						items[id].nutrition = script.readNumber();
					} else if (identifier == "maxlength") {
						items[id].maxTextLen = script.readNumber();
					} else if (identifier == "fluidsource") {
						items[id].fluidSource = getFluidType(script.readIdentifier());
					} else if (identifier == "avoiddamagetypes") {
						items[id].combatType = getCombatType(script.readIdentifier());
					} else if (identifier == "damagetype") {
						items[id].damageType = getCombatType(script.readIdentifier());
					} else if (identifier == "attackstrength") {
						items[id].attackStrength = script.readNumber();
					} else if (identifier == "attackvariation") {
						items[id].attackVariation = script.readNumber();
					} else if (identifier == "manaconsumption") {
						items[id].manaConsumption = script.readNumber();
					} else if (identifier == "minimumlevel") {
						items[id].minReqLevel = script.readNumber();
						items[id].wieldInfo |= WIELDINFO_LEVEL;
					} else if (identifier == "vocations") {
						int32_t vocations = script.readNumber();
						items[id].vocations = vocations;

						std::list<std::string> vocationStringList;

						if (hasBitSet(VOCATION_SORCERER, vocations)) {
							vocationStringList.push_back("sorcerer");
						}

						if (hasBitSet(VOCATION_DRUID, vocations)) {
							vocationStringList.push_back("druid");
						}

						if (hasBitSet(VOCATION_PALADIN, vocations)) {
							vocationStringList.push_back("paladin");
						}

						if (hasBitSet(VOCATION_KNIGHT, vocations)) {
							vocationStringList.push_back("knight");
						}

						std::string vocationString;
						for (const std::string& str : vocationStringList) {
							if (!vocationString.empty()) {
								if (str != vocationStringList.back()) {
									vocationString.push_back(',');
									vocationString.push_back(' ');
								} else {
									vocationString += " and ";
								}
							}

							vocationString += str;
							vocationString.push_back('s');
						}

						items[id].wieldInfo |= WIELDINFO_VOCREQ;
						items[id].vocationString = vocationString;
					} else if (identifier == "weaponspecialeffect") {
						items[id].weaponSpecialEffect = script.readNumber();
					} else if (identifier == "beddirection") {
						items[id].bedPartnerDir = getDirection(script.readIdentifier());
					} else if (identifier == "bedtarget") {
						items[id].transformToOnUse = script.readNumber();
					} else if (identifier == "bedfree") {
						items[id].transformToFree = script.readNumber();
					} else if (identifier == "weight") {
						items[id].weight = script.readNumber();
					} else if (identifier == "rotatetarget") {
						items[id].rotateTo = script.readNumber();
					} else if (identifier == "destroytarget") {
						items[id].destroyTarget = script.readNumber();
					} else if (identifier == "slottype") {
						identifier = asLowerCaseString(script.readIdentifier());
						if (identifier == "head") {
							items[id].slotPosition |= SLOTP_HEAD;
						} else if (identifier == "body") {
							items[id].slotPosition |= SLOTP_ARMOR;
						} else if (identifier == "legs") {
							items[id].slotPosition |= SLOTP_LEGS;
						} else if (identifier == "feet") {
							items[id].slotPosition |= SLOTP_FEET;
						} else if (identifier == "backpack") {
							items[id].slotPosition |= SLOTP_BACKPACK;
						} else if (identifier == "twohanded") {
							items[id].slotPosition |= SLOTP_TWO_HAND;
						} else if (identifier == "righthand") {
							items[id].slotPosition &= ~SLOTP_LEFT;
						} else if (identifier == "lefthand") {
							items[id].slotPosition &= ~SLOTP_RIGHT;
						} else if (identifier == "necklace") {
							items[id].slotPosition |= SLOTP_NECKLACE;
						} else if (identifier == "ring") {
							items[id].slotPosition |= SLOTP_RING;
						} else if (identifier == "ammo") {
							items[id].slotPosition |= SLOTP_AMMO;
						} else if (identifier == "hand") {
							items[id].slotPosition |= SLOTP_HAND;
						} else {
							script.error("Unknown slot position");
							return false;
						}
					} else if (identifier == "speedboost") {
						items[id].getAbilities().speed = script.readNumber();
					} else if (identifier == "fistboost") {
						items[id].getAbilities().skills[SKILL_FIST] = script.readNumber();
					} else if (identifier == "swordboost") {
						items[id].getAbilities().skills[SKILL_SWORD] = script.readNumber();
					} else if (identifier == "clubboost") {
						items[id].getAbilities().skills[SKILL_CLUB] = script.readNumber();
					} else if (identifier == "axeboost") {
						items[id].getAbilities().skills[SKILL_AXE] = script.readNumber();
					} else if (identifier == "shieldboost") {
						items[id].getAbilities().skills[SKILL_SHIELD] = script.readNumber();
					} else if (identifier == "distanceboost") {
						items[id].getAbilities().skills[SKILL_DISTANCE] = script.readNumber();
					} else if (identifier == "magicboost") {
						items[id].getAbilities().stats[STAT_MAGICPOINTS] = script.readNumber();
					} else if (identifier == "percenthp") {
						items[id].getAbilities().statsPercent[STAT_MAXHITPOINTS] = script.readNumber();
					} else if (identifier == "percentmp") {
						items[id].getAbilities().statsPercent[STAT_MAXMANAPOINTS] = script.readNumber();
					} else if (identifier == "suppressdrunk") {
						if (script.readNumber()) {
							items[id].getAbilities().conditionSuppressions |= CONDITION_DRUNK;
						}
					} else if (identifier == "invisible") {
						if (script.readNumber()) {
							items[id].getAbilities().invisible = true;
						}
					} else if (identifier == "manashield") {
						if (script.readNumber()) {
							items[id].getAbilities().manaShield = true;
						}
					} else if (identifier == "healthticks") {
						Abilities& abilities = items[id].getAbilities();
						abilities.regeneration = true;
						abilities.healthTicks = script.readNumber();
					} else if (identifier == "healthgain") {
						Abilities& abilities = items[id].getAbilities();
						abilities.regeneration = true;
						abilities.healthGain = script.readNumber();
					} else if (identifier == "manaticks") {
						Abilities& abilities = items[id].getAbilities();
						abilities.regeneration = true;
						abilities.manaTicks = script.readNumber();
					} else if (identifier == "managain") {
						Abilities& abilities = items[id].getAbilities();
						abilities.regeneration = true;
						abilities.manaGain = script.readNumber();
					} else if (identifier == "absorbmagic") {
						int32_t percent = script.readNumber();
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += percent;
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += percent;
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += percent;
					} else if (identifier == "absorbenergy") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += script.readNumber();
					} else if (identifier == "absorbfire") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += script.readNumber();
					} else if (identifier == "absorbpoison") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += script.readNumber();
					} else if (identifier == "absorblifedrain") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += script.readNumber();
					} else if (identifier == "absorbmanadrain") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += script.readNumber();
					} else if (identifier == "absorbphysical") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += script.readNumber();
					} else if (identifier == "absorbhealing") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += script.readNumber();
					} else if (identifier == "absorbundefined") {
						items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += script.readNumber();
					} else if (identifier == "absorbfirefield") {
						items[id].getAbilities().fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += static_cast<int16_t>(script.readNumber());
					} else if (identifier == "brightness") {
						items[id].lightLevel = script.readNumber();
					} else if (identifier == "lightcolor") {
						items[id].lightColor = script.readNumber();
					} else if (identifier == "totalexpiretime") {
						items[id].decayTime = script.readNumber();
					} else if (identifier == "expiretarget") {
						items[id].decayTo = script.readNumber();
					} else if (identifier == "totaluses") {
						items[id].charges = script.readNumber();
					} else if (identifier == "weapontype") {
						identifier = script.readIdentifier();
						if (identifier == "sword") {
							items[id].weaponType = WEAPON_SWORD;
						} else if (identifier == "club") {
							items[id].weaponType = WEAPON_CLUB;
						} else if (identifier == "axe") {
							items[id].weaponType = WEAPON_AXE;
						} else if (identifier == "shield") {
							items[id].weaponType = WEAPON_SHIELD;
						} else if (identifier == "distance") {
							items[id].weaponType = WEAPON_DISTANCE;
						} else if (identifier == "wand") {
							items[id].weaponType = WEAPON_WAND;
						} else if (identifier == "ammunition") {
							items[id].weaponType = WEAPON_AMMO;
						} else {
							script.error("Unknown weapon type");
							return false;
						}
					} else if (identifier == "attack") {
						items[id].attack = script.readNumber();
					} else if (identifier == "defense") {
						items[id].defense = script.readNumber();
					} else if (identifier == "range") {
						items[id].shootRange = static_cast<uint8_t>(script.readNumber());
					} else if (identifier == "ammotype") {
						items[id].ammoType = getAmmoType(script.readIdentifier());
						if (items[id].ammoType == AMMO_NONE) {
							script.error("Unknown ammo type");
							return false;
						}
					} else if (identifier == "missileeffect") {
						items[id].shootType = static_cast<ShootType_t>(script.readNumber());
					} else if (identifier == "fragility") {
						items[id].fragility = script.readNumber();
					} else if (identifier == "armorvalue") {
						items[id].armor = script.readNumber();
					} else if (identifier == "disguisetarget") {
						items[id].disguiseId = script.readNumber();
					} else if (identifier == "equiptarget") {
						items[id].transformEquipTo = script.readNumber();
					} else if (identifier == "deequiptarget") {
						items[id].transformDeEquipTo = script.readNumber();
					} else {
						script.error("Unknown attribute");
						return false;
					}
				}

				if (script.getSpecial() == '}') {
					break;
				}

				if (script.Token != SPECIAL || script.getSpecial() != ',') {
					continue;
				}
			}
		} else if (identifier == "magicfield") {
			script.readSymbol('{');

			CombatType_t combatType = COMBAT_NONE;
			ConditionDamage* conditionDamage = nullptr;

			int32_t cycles = 0;
			int32_t hit_damage = 0;

			while (true) {
				while (true) {
					script.nextToken();
					if (script.Token == SPECIAL) {
						break;
					}

					identifier = script.getIdentifier();
					script.readSymbol('=');

					if (identifier == "type") {
						identifier = script.readIdentifier();
						if (identifier == "fire") {
							conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE);
							combatType = COMBAT_FIREDAMAGE;
							items[id].combatType = combatType;
							items[id].conditionDamage.reset(conditionDamage);
						} else if (identifier == "energy") {
							conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY);
							combatType = COMBAT_ENERGYDAMAGE;
							items[id].combatType = combatType;
							items[id].conditionDamage.reset(conditionDamage);
						} else if (identifier == "poison") {
							conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON);
							conditionDamage->setParam(CONDITION_PARAM_DELAYED, true);
							combatType = COMBAT_EARTHDAMAGE;
							items[id].combatType = combatType;
							items[id].conditionDamage.reset(conditionDamage);
						} else {
							script.error("unknown magicfield type");
							return false;
						}
					} else if (identifier == "count") {
						cycles = script.readNumber();
					} else if (identifier == "damage") {
						hit_damage = script.readNumber();
					} else {
						script.error("unknown identifier");
						return false;
					}
				}

				if (script.getSpecial() == '}') {
					break;
				}

				if (script.Token != SPECIAL || script.getSpecial() != ',') {
					continue;
				}
			}

			int32_t count = 3;

			if (combatType == COMBAT_FIREDAMAGE) {
				cycles /= 10;
				count = 8;
			} else if (combatType == COMBAT_ENERGYDAMAGE) {
				cycles /= 20;
				count = 10;
			}

			conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles);
			conditionDamage->setParam(CONDITION_PARAM_COUNT, count);
			conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count);
			conditionDamage->setParam(CONDITION_PARAM_HIT_DAMAGE, hit_damage);

			conditionDamage->setParam(CONDITION_PARAM_FIELD, 1);
		}
	}

	script.close();
	items.shrink_to_fit();

	for (ItemType& type : items) {
		std::string& name = type.name;
		extractArticleAndName(name, type.article, type.name);
		nameToItems.insert({ asLowerCaseString(type.name), type.id });
		if (!name.empty()) {
			if (type.stackable) {
				type.showCount = true;
				type.pluralName = pluralizeString(name);
			}
		}
	}

	return true;
}

ItemType& Items::getItemType(size_t id)
{
	if (id < items.size()) {
		return items[id];
	}
	return items.front();
}

const ItemType& Items::getItemType(size_t id) const
{
	if (id < items.size()) {
		return items[id];
	}
	return items.front();
}

uint16_t Items::getItemIdByName(const std::string& name)
{
	auto result = nameToItems.find(asLowerCaseString(name));

	if (result == nameToItems.end())
		return 0;

	return result->second;
}
