#pragma once

#include <Array.h>
#include <Critical.h>
#include <Model3D.h>
#include <Model3DCollection.h>
#include <Point.h>
#include <Reader.h>

#include "Area.h"
#include "Block.h"
#include "BlockAnimation.h"
#include "ChunkModelBuilder.h"
#include "Constants.h"
#include "FactoryCraftModel.h"

class Chunk : public Framework::ReferenceCounter
{
public:
    class CombinedModels
    {
    public:
        static const int GROUND = 1;
        static const int FLUID = 2;
        static const int TRANSPARENT_GROUND = 4;
    };

private:
    Framework::Point location;
    // TODO: use native array for bedder performance?
    Block** blocks;
    Framework::Array<Block*> visibleBlocks;
    Framework::RCArray<ChunkModelBuilder> modelBuilders;
    bool isLoading;
    Framework::ReadWriteLock bLock;
    Framework::ReadWriteLock vLock;
    Framework::ReadWriteLock aLock;
    Framework::RCArray<BlockAnimation> animations;
    int lightChanged;
    int modelChanged;

    void appendAnimation(
        Block* zB, int boneId, double time, Vec3<float> pos, Vec3<float> rot);
    void load(Framework::StreamReader* zReader);
    void buildModel(ChunkModelBuilder* builder);
    void updateLight(ChunkModelBuilder* builder);

public:
    Chunk(Framework::Point location);
    Chunk(Framework::Point location, Framework::StreamReader* zReader);
    ~Chunk();

    void renderSolid(std::function<void(Model3D*)> f);
    void renderTransparent(std::function<void(Model3D*)> f);
    bool tick(std::function<void(Model3D*)> f, double time);

    void destroy();
    void api(char* message);
    Block* zBlockAt(Framework::Vec3<int> cLocation);
    void setBlock(Block* block);
    void removeBlock(Block* zBlock);
    void blockVisibilityChanged(Block* zB);
    Framework::Point getCenter() const;
    Framework::Vec3<int> getMin() const;
    Framework::Vec3<int> getMax() const;
    void setModelChanged(int modelType);
    void setLightChanged(int modelType);
    Lock& getBlockReadLock() const;

    inline static int index(Framework::Vec3<int> localLocation)
    {
        return (localLocation.x * CHUNK_SIZE + localLocation.y) * WORLD_HEIGHT
             + localLocation.z;
    }

    friend ChunkModelBuilder;
};