#pragma once

#include <Array.h>
#include <Either.h>
#include <ReferenceCounter.h>

#include "BiomGenerator.h"
#include "CaveGenerator.h"
#include "Chunk.h"
#include "JNoise.h"
#include "JsonExpression.h"

class DimensionGenerator;

class WorldHeightLayer : public virtual Framework::ReferenceCounter
{
private:
    Framework::JSON::JSONObject* noiseConfig;
    Noise* noise;
    Framework::Text name;
    JFloatExpression* value;

public:
    WorldHeightLayer();
    ~WorldHeightLayer();

    void initialize(JExpressionMemory* zMemory);

    void calculateValue(JExpressionMemory* zMemory);

    void setNoiseConfig(Framework::JSON::JSONObject* noiseConfig);
    Framework::JSON::JSONObject* zNoiseConfig() const;
    void setName(Framework::Text name);
    Framework::Text getName() const;
    void setValue(JFloatExpression* value);
    JFloatExpression* zValue() const;
};

class WorldHeightLayerFactory : public ObjectTypeFactory<WorldHeightLayer>
{
public:
    WorldHeightLayerFactory();
    WorldHeightLayer* fromJson(
        Framework::JSON::JSONObject* zJson) const override;
    Framework::JSON::JSONObject* toJsonObject(
        WorldHeightLayer* zObject) const override;
    JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const override;
};

class DimensionGenerator : public virtual Framework::ReferenceCounter
{
private:
    JExpressionMemory* jExpressionMemory;
    JFloatExpression* seedExpression;
    Framework::RCArray<WorldHeightLayer> heightLayers;
    Framework::Text name;
    int dimensionId;

protected:
    DimensionGenerator();
    ~DimensionGenerator();

    JExpressionMemory* zMemory() const;
    void calculateHeightLayers();

public:
    Dimension* createDimension();
    virtual void initialize(int worldSeed);
    virtual Chunk* generateChunk(int centerX, int centerY) = 0;
    virtual Framework::Either<Block*, int> generateBlock(
        Framework::Vec3<int> location)
        = 0;
    virtual bool spawnStructure(Framework::Vec3<int> location,
        std::function<bool(GeneratorTemplate* tmpl)> filter)
        = 0;
    virtual void generateEntities(Chunk* zChunk) = 0;
    int getDimensionId() const;
    void addHeightLayer(WorldHeightLayer* layer);
    const Framework::RCArray<WorldHeightLayer>& getHeightLayers() const;
    void setName(Framework::Text name);
    Framework::Text getName() const;
    void setId(int id);
    int getId() const;
    void setSeed(JFloatExpression* seed);
    JFloatExpression* zSeed() const;
};

template<typename S> class DimensionGeneratorFactory
    : public SubTypeFactory<DimensionGenerator, S>
{
public:
    DimensionGeneratorFactory()
        : SubTypeFactory<DimensionGenerator, S>()
    {}

    S* fromJson(Framework::JSON::JSONObject* zJson) const override
    {
        S* result = createValue(zJson);
        DimensionGenerator* zGenerator
            = dynamic_cast<DimensionGenerator*>(result);
        zGenerator->setName(zJson->zValue("name")->asString()->getString());
        zGenerator->setId((int)zJson->zValue("id")->asNumber()->getNumber());
        zGenerator->setSeed(
            Game::INSTANCE->zTypeRegistry()->fromJson<JFloatExpression>(
                zJson->zValue("dimensionSeed")));
        for (Framework::JSON::JSONValue* layer :
            *zJson->zValue("heightLayers")->asArray())
        {
            zGenerator->addHeightLayer(
                Game::INSTANCE->zTypeRegistry()->fromJson<WorldHeightLayer>(
                    layer));
        }
        return result;
    }

    Framework::JSON::JSONObject* toJsonObject(S* zObject) const override
    {
        Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
        DimensionGenerator* zGenerator
            = dynamic_cast<DimensionGenerator*>(zObject);
        result->addValue(
            "name", new Framework::JSON::JSONString(zGenerator->getName()));
        result->addValue(
            "id", new Framework::JSON::JSONNumber(zGenerator->getId()));
        result->addValue("dimensionSeed",
            Game::INSTANCE->zTypeRegistry()->toJson<JFloatExpression>(
                zGenerator->zSeed()));
        Framework::JSON::JSONArray* hightLayers
            = new Framework::JSON::JSONArray();
        for (WorldHeightLayer* layer : zGenerator->getHeightLayers())
        {
            hightLayers->addValue(
                Game::INSTANCE->zTypeRegistry()->toJson(layer));
        }
        result->addValue("heightLayers", hightLayers);
        return result;
    }

    JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const override
    {
        return builder
            ->withRequiredAttribute("dimensionSeed",
                Game::INSTANCE->zTypeRegistry()
                    ->getValidator<JFloatExpression>())
            ->withRequiredArray("heightLayers")
            ->addAcceptedTypeInArray(Game::INSTANCE->zTypeRegistry()
                                         ->getValidator<WorldHeightLayer>())
            ->finishArray()
            ->withRequiredString("name")
            ->finishString()
            ->withRequiredNumber("id")
            ->finishNumber();
    }

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

class BiomGenerator;

class BiomedCavedDimensionGenerator : public DimensionGenerator
{
private:
    Framework::RCArray<BiomGenerator> biomGenerators;
    CaveGenerator* caveGenerator;
    Framework::JSON::JSONObject* noiseConfig;
    Noise* biomNoise;
    Framework::Vec3<int> minStructureOffset;
    Framework::Vec3<int> maxStructureOffset;

    BiomGenerator* zBiomGenerator();

protected:
    Framework::RCArray<GeneratedStructure>* getGeneratedStructoresForArea(
        Framework::Vec3<int> minPos, Framework::Vec3<int> maxPos);

public:
    BiomedCavedDimensionGenerator();
    ~BiomedCavedDimensionGenerator();

    virtual void initialize(int worldSeed) override;

    Chunk* generateChunk(int centerX, int centerY);
    void generateEntities(Chunk* zChunk);
    Framework::Either<Block*, int> generateBlock(Framework::Vec3<int> location);
    bool spawnStructure(Framework::Vec3<int> location,
        std::function<bool(GeneratorTemplate* tmpl)> filter);

    void addBiomGenerator(BiomGenerator* biomGenerator);
    const Framework::RCArray<BiomGenerator>& getBiomGenerators() const;
    void setBiomNoiseConfig(Framework::JSON::JSONObject* biomNoiseConfig);
    Framework::JSON::JSONObject* zBiomNoiseConfig() const;
};

class BiomedCavedDimensionGeneratorFactory
    : public DimensionGeneratorFactory<BiomedCavedDimensionGenerator>
{
public:
    BiomedCavedDimensionGeneratorFactory();
    BiomedCavedDimensionGenerator* createValue(
        Framework::JSON::JSONObject* zJson) const override;
    BiomedCavedDimensionGenerator* fromJson(
        Framework::JSON::JSONObject* zJson) const override;
    Framework::JSON::JSONObject* toJsonObject(
        BiomedCavedDimensionGenerator* zObject) const override;
    JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const override;
    const char* getTypeToken() const override;
};