#include "StructureCollection.h"

#include "Constants.h"
#include "Game.h"
#include "JNoise.h"

StructureTemplateCollection::StructureTemplateCollection()
    : ReferenceCounter(),
      activeNoise(0),
      activeNoiseConfig(0),
      structureNoise(0),
      structureNoiseConfig(0),
      condition(0)
{}

StructureTemplateCollection::~StructureTemplateCollection()
{
    if (activeNoise) activeNoise->release();
    if (activeNoiseConfig) activeNoiseConfig->release();
    if (structureNoise) structureNoise->release();
    if (structureNoiseConfig) structureNoiseConfig->release();
    if (condition) condition->release();
}

void StructureTemplateCollection::initialize(JExpressionMemory* zMemory)
{
    if (activeNoise) activeNoise->release();
    if (structureNoise) structureNoise->release();
    activeNoise = JNoise::parseNoise(activeNoiseConfig, zMemory);
    structureNoise = JNoise::parseNoise(structureNoiseConfig, zMemory);
}

void StructureTemplateCollection::generateStructures(int x,
    int y,
    int z,
    int dimensionId,
    JExpressionMemory* zMemory,
    Framework::Vec3<int> minPos,
    Framework::Vec3<int> maxPos,
    Framework::RCArray<GeneratedStructure>* zResult)
{
    int minSearchX = minPos.x - maxAffected.x;
    int minSearchY = minPos.y - maxAffected.y;
    int minSearchZ = MAX(minPos.z - maxAffected.z, 0);
    int maxSearchX = maxPos.x - minAffected.x;
    int maxSearchY = maxPos.y - minAffected.y;
    int maxSearchZ = MIN(maxPos.z - minAffected.z, WORLD_HEIGHT - 1);
    if (x >= minSearchX && x <= maxSearchX && y >= minSearchY && y <= maxSearchY
        && z >= minSearchZ && z <= maxSearchZ)
    {
        if (activeNoise->getNoise((double)x, (double)y, (double)z) < threshold)
        {
            if (condition->getValue(zMemory))
            {
                double rValue
                    = structureNoise->getNoise((double)x, (double)y, (double)z);
                double probSum = 0;
                for (auto t : structures)
                {
                    if (rValue - probSum <= t->getPropability())
                    {
                        zResult->add(
                            t->generateAt(Framework::Vec3<int>(x, y, z),
                                structureNoise,
                                dimensionId));
                        break;
                    }
                    probSum += t->getPropability();
                }
            }
        }
    }
}

void StructureTemplateCollection::setThreshold(double threshold)
{
    this->threshold = threshold;
}

double StructureTemplateCollection::getThreshold() const
{
    return threshold;
}

void StructureTemplateCollection::setCondition(JBoolExpression* condition)
{
    if (this->condition) this->condition->release();
    this->condition = condition;
}

JBoolExpression* StructureTemplateCollection::zCondition() const
{
    return condition;
}

void StructureTemplateCollection::setActiveNoiseConfig(
    Framework::JSON::JSONObject* activeNoiseConfig)
{
    if (this->activeNoiseConfig) this->activeNoiseConfig->release();
    this->activeNoiseConfig = activeNoiseConfig;
}

Framework::JSON::JSONObject*
StructureTemplateCollection::zActiveNoiseConfig() const
{
    return activeNoiseConfig;
}

void StructureTemplateCollection::setStructureNoiseConfig(
    Framework::JSON::JSONObject* structureNoiseConfig)
{
    if (this->structureNoiseConfig) this->structureNoiseConfig->release();
    this->structureNoiseConfig = structureNoiseConfig;
}

Framework::JSON::JSONObject*
StructureTemplateCollection::zStructureNoiseConfig() const
{
    return structureNoiseConfig;
}

void StructureTemplateCollection::addStructure(GeneratorTemplate* structure)
{
    structures.add(structure);
    if (structures.getEintragAnzahl() == 1)
    {
        minAffected = structure->getMinAffectedOffset();
        maxAffected = structure->getMaxAffectedOffset();
    }
    else
    {
        Framework::Vec3<int> min = structure->getMinAffectedOffset();
        Framework::Vec3<int> max = structure->getMaxAffectedOffset();
        if (minAffected.x > min.x) minAffected.x = min.x;
        if (minAffected.y > min.y) minAffected.y = min.y;
        if (minAffected.z > min.z) minAffected.z = min.z;
        if (maxAffected.x < max.x) maxAffected.x = max.x;
        if (maxAffected.y < max.y) maxAffected.y = max.y;
        if (maxAffected.z < max.z) maxAffected.z = max.z;
    }
}

const Framework::RCArray<GeneratorTemplate>&
StructureTemplateCollection::getStructures() const
{
    return structures;
}

Framework::Vec3<int> StructureTemplateCollection::getMinAffected() const
{
    return minAffected;
}

Framework::Vec3<int> StructureTemplateCollection::getMaxAffected() const
{
    return maxAffected;
}

StructureTemplateCollectionFactory::StructureTemplateCollectionFactory()
    : ObjectTypeFactory<StructureTemplateCollection>()
{}

StructureTemplateCollection* StructureTemplateCollectionFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    StructureTemplateCollection* result = new StructureTemplateCollection();
    result->setActiveNoiseConfig(zJson->getValue("activeNoise")->asObject());
    result->setStructureNoiseConfig(
        zJson->getValue("structureNoise")->asObject());
    result->setThreshold(zJson->zValue("threshold")->asNumber()->getNumber());
    result->setCondition(
        Game::INSTANCE->zTypeRegistry()->fromJson<JBoolExpression>(
            zJson->zValue("condition")));
    for (Framework::JSON::JSONValue* structure :
        *zJson->zValue("structures")->asArray())
    {
        result->addStructure(
            Game::INSTANCE->zTypeRegistry()->fromJson<GeneratorTemplate>(
                structure->asObject()));
    }
    return result;
}

Framework::JSON::JSONObject* StructureTemplateCollectionFactory::toJsonObject(
    StructureTemplateCollection* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    result->addValue("activeNoise",
        dynamic_cast<Framework::JSON::JSONValue*>(
            zObject->zActiveNoiseConfig()->getThis()));
    result->addValue("structureNoise",
        dynamic_cast<Framework::JSON::JSONValue*>(
            zObject->zStructureNoiseConfig()->getThis()));
    result->addValue(
        "threshold", new Framework::JSON::JSONNumber(zObject->getThreshold()));
    result->addValue("condition",
        Game::INSTANCE->zTypeRegistry()->toJson(zObject->zCondition()));
    Framework::JSON::JSONArray* structures = new Framework::JSON::JSONArray();
    for (GeneratorTemplate* t : zObject->getStructures())
    {
        structures->addValue(Game::INSTANCE->zTypeRegistry()->toJson(t));
    }
    result->addValue("structures", structures);
    return result;
}

JSONObjectValidationBuilder* StructureTemplateCollectionFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return builder
        ->withRequiredAttribute("activeNoise", JNoise::getValidator(false))
        ->withRequiredAttribute("structureNoise", JNoise::getValidator(false))
        ->withRequiredNumber("threshold")
        ->whichIsGreaterThen(0)
        ->finishNumber()
        ->withRequiredAttribute("condition",
            Game::INSTANCE->zTypeRegistry()->getValidator<JBoolExpression>())
        ->withRequiredArray("structures")
        ->addAcceptedTypeInArray(
            Game::INSTANCE->zTypeRegistry()->getValidator<GeneratorTemplate>())
        ->finishArray();
}