#pragma once

#include <Array.h>
#include <functional>
#include <JSON.h>
#include <Text.h>
#include <Trie.h>

#include "Chunk.h"
#include "Noise.h"

class JExpressionMemory;

class JExpressionMemory : public virtual Framework::ReferenceCounter
{
private:
    Framework::RCTrie<Noise> noises;
    Framework::Trie<float> floatVariables;
    Framework::Trie<bool> boolVariables;
    Chunk* currentChunk;
    Framework::Critical cs;

public:
    JExpressionMemory();
    ~JExpressionMemory();

    void lock();
    void unlock();

    float getNoise(Framework::Text name, float x, float y, float z) const;
    void setNoise(Framework::Text name, Noise* noise);
    void setCurrentChunk(Chunk* chunk);

    float getFloatVariable(Framework::Text name) const;
    void setFloatVariable(Framework::Text name, float value);
    bool getBoolVariable(Framework::Text name) const;
    void setBoolVariable(Framework::Text name, bool value);
    Chunk* zCurrentChunk();
};

class JFloatExpression : public virtual Framework::ReferenceCounter
{
private:

public:
    JFloatExpression();
    virtual float getValue(JExpressionMemory* zMemory) = 0;
};

class JBoolExpression : public virtual Framework::ReferenceCounter
{
private:

public:
    JBoolExpression();
    virtual bool getValue(JExpressionMemory* zMemory) = 0;
};

class JVariableFloatExpression : public JFloatExpression
{
private:
    Framework::Text name;

public:
    JVariableFloatExpression(Framework::Text name);
    float getValue(JExpressionMemory* zMemory) override;
};

class JVariableBoolExpression : public JBoolExpression
{
private:
    Framework::Text name;

public:
    JVariableBoolExpression(Framework::Text name);
    bool getValue(JExpressionMemory* zMemory) override;
};

class JConstantFloatExpression : public JFloatExpression
{
private:
    float value;

public:
    JConstantFloatExpression(float value);
    float getValue(JExpressionMemory* zMemory) override;
};

class JConstantBoolExpression : public JBoolExpression
{
private:
    bool value;

public:
    JConstantBoolExpression(bool value);
    bool getValue(JExpressionMemory* zMemory) override;
};

class JNoiseFloatExpression : public JFloatExpression
{
private:
    Framework::Text name;
    JFloatExpression* x;
    JFloatExpression* y;
    JFloatExpression* z;

public:
    JNoiseFloatExpression(Framework::Text name,
        JFloatExpression* x,
        JFloatExpression* y,
        JFloatExpression* z);
    ~JNoiseFloatExpression();
    float getValue(JExpressionMemory* zMemory) override;
};

class JOperatorFloatExpression : public JFloatExpression
{
private:
    std::function<float(float a, float b)> accumulator;
    Framework::RCArray<JFloatExpression>* values;

public:
    JOperatorFloatExpression(std::function<float(float a, float b)> accumulator,
        Framework::RCArray<JFloatExpression>* values);
    ~JOperatorFloatExpression();
    float getValue(JExpressionMemory* zMemory) override;
};

class JBoolOperatorBoolExpression : public JBoolExpression
{
private:
    std::function<bool(bool a, bool b)> accumulator;
    Framework::RCArray<JBoolExpression>* values;

public:
    JBoolOperatorBoolExpression(std::function<bool(bool a, bool b)> accumulator,
        Framework::RCArray<JBoolExpression>* values);
    ~JBoolOperatorBoolExpression();
    bool getValue(JExpressionMemory* zMemory) override;
};

class JFloatOperatorBoolExpression : public JBoolExpression
{
private:
    std::function<bool(float a, float b)> accumulator;
    Framework::RCArray<JFloatExpression>* values;

public:
    JFloatOperatorBoolExpression(
        std::function<bool(float a, float b)> accumulator,
        Framework::RCArray<JFloatExpression>* values);
    ~JFloatOperatorBoolExpression();
    bool getValue(JExpressionMemory* zMemory) override;
};

class JBlockTypeBoolExpression : public JBoolExpression
{
private:
    int typeId;
    JFloatExpression* x;
    JFloatExpression* y;
    JFloatExpression* z;

public:
    JBlockTypeBoolExpression(int typeId,
        JFloatExpression* x,
        JFloatExpression* y,
        JFloatExpression* z);
    ~JBlockTypeBoolExpression();
    bool getValue(JExpressionMemory* zMemory) override;
};

namespace JExpressionParser
{
    JFloatExpression* parseFloatExpression(Framework::JSON::JSONValue* zValue);
    Framework::JSON::Validator::JSONValidator* getFloatValidator();

    JBoolExpression* parseBoolExpression(Framework::JSON::JSONValue* zValue);
    Framework::JSON::Validator::JSONValidator* getBoolValidator();
}; // namespace JExpressionParser