/**
 * 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.
 */

#ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56
#define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56

#include <unordered_set>

#include "cylinder.h"
#include "item.h"
#include "tools.h"

class Creature;
class Teleport;
class Mailbox;
class MagicField;
class QTreeLeafNode;
class BedItem;

typedef std::vector<Creature*> CreatureVector;
typedef std::vector<Item*> ItemVector;
typedef std::unordered_set<Creature*> SpectatorVec;

enum tileflags_t : uint32_t {
	TILESTATE_NONE = 0,

	TILESTATE_PROTECTIONZONE = 1 << 0,
	TILESTATE_NOPVPZONE = 1 << 1,
	TILESTATE_NOLOGOUT = 1 << 2,
	TILESTATE_PVPZONE = 1 << 3,
	TILESTATE_REFRESH = 1 << 4,
	TILESTATE_TELEPORT = 1 << 5,
	TILESTATE_MAGICFIELD = 1 << 6,
	TILESTATE_MAILBOX = 1 << 7,
	TILESTATE_BED = 1 << 8,
	TILESTATE_DEPOT = 1 << 9,
	TILESTATE_BLOCKSOLID = 1 << 10,
	TILESTATE_BLOCKPATH = 1 << 11,
	TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 12,
	TILESTATE_IMMOVABLEBLOCKPATH = 1 << 13,
	TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 14,
	TILESTATE_NOFIELDBLOCKPATH = 1 << 15,
	TILESTATE_SUPPORTS_HANGABLE = 1 << 16,
	TILESTATE_FIREDAMAGE = 1 << 17,
	TILESTATE_POISONDAMAGE = 1 << 18,
	TILESTATE_ENERGYDAMAGE = 1 << 19,
};

enum ZoneType_t {
	ZONE_PROTECTION,
	ZONE_NOPVP,
	ZONE_PVP,
	ZONE_NOLOGOUT,
	ZONE_NORMAL,
};

class TileItemVector : private ItemVector
{
	public:
		using ItemVector::begin;
		using ItemVector::end;
		using ItemVector::rbegin;
		using ItemVector::rend;
		using ItemVector::size;
		using ItemVector::clear;
		using ItemVector::at;
		using ItemVector::insert;
		using ItemVector::erase;
		using ItemVector::push_back;
		using ItemVector::value_type;
		using ItemVector::iterator;
		using ItemVector::const_iterator;
		using ItemVector::reverse_iterator;
		using ItemVector::const_reverse_iterator;

		iterator getBeginDownItem() {
			return begin();
		}
		const_iterator getBeginDownItem() const {
			return begin();
		}
		iterator getEndDownItem() {
			return begin() + downItemCount;
		}
		const_iterator getEndDownItem() const {
			return begin() + downItemCount;
		}
		iterator getBeginTopItem() {
			return getEndDownItem();
		}
		const_iterator getBeginTopItem() const {
			return getEndDownItem();
		}
		iterator getEndTopItem() {
			return end();
		}
		const_iterator getEndTopItem() const {
			return end();
		}

		uint32_t getTopItemCount() const {
			return size() - downItemCount;
		}
		uint32_t getDownItemCount() const {
			return downItemCount;
		}
		inline Item* getTopTopItem() const {
			if (getTopItemCount() == 0) {
				return nullptr;
			}
			return *(getEndTopItem() - 1);
		}
		inline Item* getTopDownItem() const {
			if (downItemCount == 0) {
				return nullptr;
			}
			return *getBeginDownItem();
		}
		void addDownItemCount(int32_t increment) {
			downItemCount += increment;
		}

	private:
		uint16_t downItemCount = 0;
};

class Tile : public Cylinder
{
	public:
		static Tile& nullptr_tile;
		Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {}
		virtual ~Tile();

		// non-copyable
		Tile(const Tile&) = delete;
		Tile& operator=(const Tile&) = delete;

		virtual TileItemVector* getItemList() = 0;
		virtual const TileItemVector* getItemList() const = 0;
		virtual TileItemVector* makeItemList() = 0;

		virtual CreatureVector* getCreatures() = 0;
		virtual const CreatureVector* getCreatures() const = 0;
		virtual CreatureVector* makeCreatures() = 0;

		int32_t getThrowRange() const final {
			return 0;
		}
		bool isPushable() const final {
			return false;
		}

		MagicField* getFieldItem() const;
		Teleport* getTeleportItem() const;
		Mailbox* getMailbox() const;
		BedItem* getBedItem() const;

		Creature* getTopCreature() const;
		const Creature* getBottomCreature() const;
		Creature* getTopVisibleCreature(const Creature* creature) const;
		const Creature* getBottomVisibleCreature(const Creature* creature) const;
		Item* getTopTopItem() const;
		Item* getTopDownItem() const;
		bool isMoveableBlocking() const;
		Thing* getTopVisibleThing(const Creature* creature);
		Item* getItemByTopOrder(int32_t topOrder);

		size_t getThingCount() const {
			size_t thingCount = getCreatureCount() + getItemCount();
			if (ground) {
				thingCount++;
			}
			return thingCount;
		}
		// If these return != 0 the associated vectors are guaranteed to exists
		size_t getCreatureCount() const;
		size_t getItemCount() const;
		uint32_t getTopItemCount() const;
		uint32_t getDownItemCount() const;

		bool hasProperty(ITEMPROPERTY prop) const;
		bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const;

		inline bool hasFlag(uint32_t flag) const {
			return hasBitSet(flag, this->flags);
		}
		inline void setFlag(uint32_t flag) {
			this->flags |= flag;
		}
		inline void resetFlag(uint32_t flag) {
			this->flags &= ~flag;
		}

		ZoneType_t getZone() const {
			if (hasFlag(TILESTATE_PROTECTIONZONE)) {
				return ZONE_PROTECTION;
			} else if (hasFlag(TILESTATE_NOPVPZONE)) {
				return ZONE_NOPVP;
			} else if (hasFlag(TILESTATE_PVPZONE)) {
				return ZONE_PVP;
			} else {
				return ZONE_NORMAL;
			}
		}

		bool hasHeight(uint32_t n) const;
		int32_t getHeight();

		std::string getDescription(int32_t lookDistance) const final;

		int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const;
		int32_t getStackposOfCreature(const Player* player, const Creature* creature) const;
		int32_t getStackposOfItem(const Player* player, const Item* item) const;

		//cylinder implementations
		ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
				uint32_t flags, Creature* actor = nullptr) const override;
		ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count,
				uint32_t& maxQueryCount, uint32_t flags) const final;
		ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final;
		Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override;

		void addThing(Thing* thing) final;
		void addThing(int32_t index, Thing* thing) override;

		void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final;
		void replaceThing(uint32_t index, Thing* thing) final;

		void removeThing(Thing* thing, uint32_t count) final;

		void removeCreature(Creature* creature);

		int32_t getThingIndex(const Thing* thing) const final;
		size_t getFirstIndex() const final;
		size_t getLastIndex() const final;
		uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final;
		Thing* getThing(size_t index) const final;

		void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
		void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;

		void internalAddThing(Thing* thing) final;
		void internalAddThing(uint32_t index, Thing* thing) override;

		const Position& getPosition() const final {
			return tilePos;
		}

		bool isRemoved() const final {
			return false;
		}

		Item* getUseItem(int32_t index) const;

		Item* getGround() const {
			return ground;
		}
		void setGround(Item* item) {
			ground = item;
		}

	private:
		void onAddTileItem(Item* item);
		void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType);
		void onRemoveTileItem(const SpectatorVec& list, const std::vector<int32_t>& oldStackPosVector, Item* item);
		void onUpdateTile(const SpectatorVec& list);

		void setTileFlags(const Item* item);
		void resetTileFlags(const Item* item);

	protected:
		Item* ground = nullptr;
		Position tilePos;
		uint32_t flags = 0;
};

// Used for walkable tiles, where there is high likeliness of
// items being added/removed
class DynamicTile : public Tile
{
		// By allocating the vectors in-house, we avoid some memory fragmentation
		TileItemVector items;
		CreatureVector creatures;

	public:
		DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {}
		~DynamicTile();

		// non-copyable
		DynamicTile(const DynamicTile&) = delete;
		DynamicTile& operator=(const DynamicTile&) = delete;

		TileItemVector* getItemList() final {
			return &items;
		}
		const TileItemVector* getItemList() const final {
			return &items;
		}
		TileItemVector* makeItemList() final {
			return &items;
		}

		CreatureVector* getCreatures() final {
			return &creatures;
		}
		const CreatureVector* getCreatures() const final {
			return &creatures;
		}
		CreatureVector* makeCreatures() final {
			return &creatures;
		}
};

// For blocking tiles, where we very rarely actually have items
class StaticTile final : public Tile
{
	// We very rarely even need the vectors, so don't keep them in memory
	std::unique_ptr<TileItemVector> items;
	std::unique_ptr<CreatureVector> creatures;

	public:
		StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {}
		~StaticTile();

		// non-copyable
		StaticTile(const StaticTile&) = delete;
		StaticTile& operator=(const StaticTile&) = delete;

		TileItemVector* getItemList() final {
			return items.get();
		}
		const TileItemVector* getItemList() const final {
			return items.get();
		}
		TileItemVector* makeItemList() final {
			if (!items) {
				items.reset(new TileItemVector);
			}
			return items.get();
		}

		CreatureVector* getCreatures() final {
			return creatures.get();
		}
		const CreatureVector* getCreatures() const final {
			return creatures.get();
		}
		CreatureVector* makeCreatures() final {
			if (!creatures) {
				creatures.reset(new CreatureVector);
			}
			return creatures.get();
		}
};

inline Tile::~Tile()
{
	delete ground;
}

inline StaticTile::~StaticTile()
{
	if (items) {
		for (Item* item : *items) {
			item->decrementReferenceCounter();
		}
	}
}

inline DynamicTile::~DynamicTile()
{
	for (Item* item : items) {
		item->decrementReferenceCounter();
	}
}

#endif
