#pragma once

#include <Console.h>
#include <Critical.h>
#include <Either.h>
#include <InMemoryBuffer.h>
#include <Network.h>
#include <Punkt.h>
#include <Text.h>
#include <Thread.h>

#include "Area.h"
#include "BlockTypeNameFactory.h"
#include "Constants.h"
#include "GameClient.h"
#include "ItemTypeNameFactory.h"
#include "NetworkMessage.h"
#include "TypeRegistry.h"

class QuestManager;
class TickOrganizer;
class WorldGenerator;
class WorldLoader;
class RecipieLoader;
class Chat;
class PlayerRegister;
class UIController;
class EntityType;
class MultiblockStructureType;
class ItemStack;
class BlockType;
class ItemType;
class Item;

class Game : public virtual Framework::Thread
{
public:
    static Framework::ConsoleHandler* consoleHandler;
    static Framework::InputLine* consoleInput;

private:
    Framework::Text name;
    TypeRegistry* typeRegistry;
    BlockTypeNameFactory* blockTypeNameFactory;
    ItemTypeNameFactory* itemTypeNameFactory;
    Framework::RCArray<Dimension>* dimensions;
    Framework::RCArray<GameClient>* clients;
    Framework::Array<std::function<void()>> actions;
    QuestManager* questManager;
    Framework::Critical actionsCs;
    TickOrganizer* ticker;
    Framework::Text path;
    bool stop;
    __int64 tickId;
    Framework::Critical cs;
    int nextEntityId;
    WorldGenerator* generator;
    WorldLoader* loader;
    RecipieLoader* recipies;
    Chat* chat;
    PlayerRegister* playerRegister;
    UIController* uiController;
    double totalTickTime;
    int tickCounter;
    double averageTickTime;
    int ticksPerSecond;
    double totalTime;
    BlockType** blockTypes;
    int blockTypeCount;
    ItemType** itemTypes;
    int itemTypeCount;
    EntityType** entityTypes;
    int entityTypeCount;
    MultiblockStructureType** multiblockStructureTypes;
    int multiblockStructureTypeCount;

    void thread() override;

    Game(Framework::Text name, Framework::Text worldsDir);

public:
    ~Game();
    void initialize();
    void api(Framework::InMemoryBuffer* zRequest, GameClient* zOrigin);
    void updateLightning(int dimensionId, Framework::Vec3<int> location);
    void updateLightningWithoutWait(
        int dimensionId, Framework::Vec3<int> location);
    void broadcastMessage(NetworkMessage* response);
    void sendMessage(NetworkMessage* response, Entity* zTargetPlayer);
    bool checkPlayer(Framework::Text name, Framework::Text secret);
    bool existsPlayer(Framework::Text name);
    Framework::Text createPlayer(Framework::Text name);
    GameClient* addPlayer(FCKlient* client, Framework::Text name);
    bool doesChunkExist(int x, int y, int dimension);
    void blockTargetChanged(Block* zBlock);
    void entityTargetChanged(Entity* zEntity);
    void spawnItem(
        Framework::Vec3<float> location, int dimensionId, Item* stack);
    void spawnItem(
        Framework::Vec3<float> location, int dimensionId, ItemStack* stack);
    bool isChunkLoaded(int x, int y, int dimension) const;
    Framework::Either<Block*, int> zBlockAt(
        Framework::Vec3<int> location, int dimension, OUT Chunk** zChunk) const;
    Block* zRealBlockInstance(Framework::Vec3<int> location, int dimension);
    int getBlockType(Framework::Vec3<int> location, int dimension);
    Dimension* zDimension(int id) const;
    static Framework::Punkt getChunkCenter(int x, int y);
    Area getChunckArea(Framework::Punkt center) const;
    Framework::Text getWorldDirectory() const;
    void requestArea(Area area);
    void save() const;
    void requestStop();
    void addDimension(Dimension* d);
    int getNextEntityId();
    WorldGenerator* zGenerator() const;
    Entity* zEntity(int id, int dimensionId) const;
    Entity* zEntity(int id) const;
    Entity* zNearestEntity(int dimensionId,
        Framework::Vec3<float> pos,
        std::function<bool(Entity*)> filter);
    RecipieLoader* zRecipies() const;
    void doLater(std::function<void()> action);
    TickOrganizer* zTickOrganizer() const;
    Chat* zChat() const;
    Player* zPlayerByName(const char* name) const;
    void listPlayerNames(Framework::RCArray<Framework::Text>& names);
    TypeRegistry* zTypeRegistry() const;
    int getPlayerId(const char* name) const;
    QuestManager* zQuestManager() const;
    UIController* zUIController() const;

    double getAverageTickTime() const;
    int getTicksPerSecond() const;
    int getPlayerCount() const;
    int getChunkCount() const;

    const BlockType* zBlockType(int id) const;
    const ItemType* zItemType(int id) const;
    const EntityType* zEntityType(int id) const;
    int getEntityTypeId(const char* name) const;
    int getBlockTypeId(const char* name) const;
    int getItemTypeId(const char* name) const;
    int getBlockTypeCount() const;
    int getItemTypeCount() const;
    int getEntityTypeCount() const;
    const MultiblockStructureType* zMultiblockStructureType(int id) const;
    int getMultiblockStructureTypeCount() const;

    static Game* INSTANCE;
    static Framework::Critical INSTANCE_CS;
    static void initialize(Framework::Text name, Framework::Text worldsDir);
};