#pragma once

#include <Array.h>
#include <Either.h>
#include <Vec3.h>
#include <Writer.h>

#include "ModelInfo.h"

class Item;
class Block;
class Game;
class ItemType;

class BlockTypeEnum
{
public:
    static const int NO_BLOCK = 0;
    static const int AIR = 1;
};

class BlockType : public virtual Framework::ReferenceCounter
{
private:
    int id;
    ModelInfo* model;
    int initialMaxHP;
    bool needsClientInstance;
    bool lightSource;
    Framework::Text name;
    bool needModelSubscription;
    int initialMapColor;
    Block* defaultBlock;
    Framework::RCArray<Framework::Text> groupNames;
    float hardness;

protected:
    BlockType();
    virtual ~BlockType();

    virtual void loadSuperBlock(
        Block* zBlock, Framework::StreamReader* zReader, int dimensionId) const;
    virtual void saveSuperBlock(
        Block* zBlock, Framework::StreamWriter* zWriter) const;
    virtual void createSuperBlock(Block* zBlock, Item* zItem) const;
    virtual void createSuperItem(Block* zBlock, Item* zItem) const;
    virtual Block* createBlock(
        Framework::Vec3<int> position, int dimensionId) const
        = 0;
    virtual Item* createItem() const = 0;

public:
    virtual bool initialize(Game* zGame);
    BlockType* initializeDefault();
    virtual const Block* zDefault() const;

    virtual ItemType* createItemType() const = 0;

    void writeTypeInfo(Framework::StreamWriter* zWriter) const;
    virtual Framework::Text getTargetUIML() const;
    virtual Block* loadBlock(Framework::Vec3<int> position,
        Framework::StreamReader* zReader,
        int dimensionId) const;
    virtual void saveBlock(
        Block* zBlock, Framework::StreamWriter* zWriter) const;
    virtual Item* getItemFromBlock(Block* zBlock) const;
    virtual Block* createBlockAt(
        Framework::Vec3<int> position, int dimensionId, Item* zUsedItem) const;

    int getId() const;
    virtual bool isFluid() const;
    virtual unsigned char getFlowDistance() const;
    void setTypeId(int id);
    void setModel(ModelInfo* model);
    ModelInfo* zModel() const;
    void setInitialMaxHP(int initialMaxHP);
    int getInitialMaxHP() const;
    void setNeedsClientInstance(bool needsClientInstance);
    bool doesNeedClientInstance() const;
    void setLightSource(bool lightSource);
    bool isLightSource() const;
    void setName(Framework::Text name);
    const char* getName() const;
    void setNeedModelSubscription(bool needModelSubscription);
    const bool doesNeedModelSubscription() const;
    void setMapColor(int mapColor);
    int getMapColor() const;
    void setGroupNames(Framework::RCArray<Framework::Text> groupNames);
    const Framework::RCArray<Framework::Text>& getGroupNames() const;
    void setHardness(float hardness);
    float getHardness() const;

    static int getTypeId(const char* name);
    static Framework::Text getTypeName(int id);
};

const Block* getDefaultBlock(Framework::Either<Block*, int> b);

template<typename S> class BlockTypeFactoryBase
    : public SubTypeFactory<BlockType, S>
{
public:
    BlockTypeFactoryBase()
        : SubTypeFactory<BlockType, S>()
    {}

    virtual S* fromJson(Framework::JSON::JSONObject* zJson) const override
    {
        S* result = createValue(zJson);
        BlockType* zType = dynamic_cast<BlockType*>(result);
        zType->setModel(Game::INSTANCE->zTypeRegistry()->fromJson<ModelInfo>(
            zJson->zValue("model")->asObject()));
        zType->setInitialMaxHP(
            (int)zJson->zValue("maxHp")->asNumber()->getNumber());
        zType->setNeedsClientInstance(
            zJson->zValue("needsClientInstance")->asBool()->getBool());
        zType->setLightSource(
            zJson->zValue("lightSource")->asBool()->getBool());
        zType->setName(zJson->zValue("name")->asString()->getString());
        zType->setNeedModelSubscription(
            zJson->zValue("needModelSubscription")->asBool()->getBool());
        zType->setMapColor(
            (int)zJson->zValue("mapColor")->asString()->getString());
        Framework::RCArray<Framework::Text> groupNames;
        for (Framework::JSON::JSONValue* value :
            *zJson->zValue("groupNames")->asArray())
        {
            groupNames.add(new Framework::Text(value->asString()->getString()));
        }
        zType->setGroupNames(groupNames);
        zType->setHardness(
            (float)zJson->zValue("hardness")->asNumber()->getNumber());
        return result;
    }

    virtual Framework::JSON::JSONObject* toJsonObject(S* zObject) const override
    {
        Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
        BlockType* zType = dynamic_cast<BlockType*>(zObject);
        result->addValue("model",
            Game::INSTANCE->zTypeRegistry()->toJson<ModelInfo>(
                zType->zModel()));
        result->addValue("maxHp",
            new Framework::JSON::JSONNumber((double)zType->getInitialMaxHP()));
        result->addValue("needsClientInstance",
            new Framework::JSON::JSONBool(zType->doesNeedClientInstance()));
        result->addValue("lightSource",
            new Framework::JSON::JSONBool(zType->isLightSource()));
        result->addValue(
            "name", new Framework::JSON::JSONString(zType->getName()));
        result->addValue("needModelSubscription",
            new Framework::JSON::JSONBool(zType->doesNeedModelSubscription()));
        result->addValue("mapColor",
            new Framework::JSON::JSONString(
                Framework::Text(zType->getMapColor())));
        Framework::JSON::JSONArray* groupNames
            = new Framework::JSON::JSONArray();
        for (Framework::Text* groupName : zType->getGroupNames())
        {
            groupNames->addValue(new Framework::JSON::JSONString(*groupName));
        }
        result->addValue("groupNames", groupNames);
        result->addValue(
            "hardness", new Framework::JSON::JSONNumber(zType->getHardness()));
        return result;
    }

    virtual JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const override
    {
        return builder
            ->withRequiredAttribute("model",
                Game::INSTANCE->zTypeRegistry()->getValidator<ModelInfo>())
            ->withRequiredNumber("maxHp")
            ->withDefault(100.0)
            ->finishNumber()
            ->withRequiredBool("needsClientInstance")
            ->withDefault(true)
            ->finishBool()
            ->withRequiredBool("lightSource")
            ->withDefault(false)
            ->finishBool()
            ->withRequiredString("name")
            ->finishString()
            ->withRequiredBool("needModelSubscription")
            ->withDefault(true)
            ->finishBool()
            ->withRequiredString("mapColor")
            ->finishString()
            ->withRequiredArray("groupNames")
            ->withDefault(new Framework::JSON::JSONArray())
            ->addAcceptedStringInArray()
            ->finishString()
            ->finishArray()
            ->withRequiredNumber("hardness")
            ->withDefault(1.0)
            ->finishNumber();
    }

protected:
    virtual S* createValue(Framework::JSON::JSONObject* zJson) const = 0;
};