#include "JNoise.h"

#include "FactorizeNoise.h"
#include "FastNoiseWrapper.h"
#include "FlattenNoise.h"
#include "Game.h"
#include "MultiplyNoise.h"
#include "NegateNoise.h"
#include "RandNoise.h"
#include "ScaleNoise.h"

using namespace Framework;

Noise* JNoise::parseNoise(JSON::JSONValue* zConfig, JExpressionMemory* zMemory)
{
    Text type = zConfig->asObject()->zValue("type")->asString()->getString();
    if (type.istGleich("random"))
    {
        JFloatExpression* seedExpression
            = Game::INSTANCE->zTypeRegistry()->fromJson<JFloatExpression>(
                zConfig->asObject()->zValue("seed"));
        float seed = seedExpression->getValue(zMemory);
        return new RandNoise((int)(round(seed)));
    }
    else if (type.istGleich("factorize"))
    {
        double factor
            = zConfig->asObject()->zValue("factorA")->asNumber()->getNumber();
        Noise* a = parseNoise(zConfig->asObject()->zValue("noiseA"), zMemory);
        Noise* b = parseNoise(zConfig->asObject()->zValue("noiseB"), zMemory);
        return new FactorizeNoise(a, b, factor);
    }
    else if (type.istGleich("multiply"))
    {
        Noise* a = parseNoise(zConfig->asObject()->zValue("base"), zMemory);
        Noise* b
            = parseNoise(zConfig->asObject()->zValue("multiplier"), zMemory);
        return new MultiplyNoise(a, b);
    }
    else if (type.istGleich("negate"))
    {
        Noise* a = parseNoise(zConfig->asObject()->zValue("noise"), zMemory);
        return new NegateNoise(a);
    }
    else if (type.istGleich("flatten"))
    {
        Noise* a = parseNoise(zConfig->asObject()->zValue("noise"), zMemory);
        double factor
            = zConfig->asObject()->zValue("factor")->asNumber()->getNumber();
        double addition
            = zConfig->asObject()->zValue("addition")->asNumber()->getNumber();
        return new FlattenNoise(a, factor, addition);
    }
    else if (type.istGleich("scale"))
    {
        Noise* a = parseNoise(zConfig->asObject()->zValue("noise"), zMemory);
        double factor
            = zConfig->asObject()->zValue("factor")->asNumber()->getNumber();
        return new ScaleNoise(a, factor);
    }
    else
    {
        JFloatExpression* seedExpression
            = Game::INSTANCE->zTypeRegistry()->fromJson<JFloatExpression>(
                zConfig->asObject()->zValue("seed"));
        float seed = seedExpression->getValue(zMemory);
        FastNoiseLite* noise = new FastNoiseLite((int)(round(seed)));
        if (type.istGleich("Cellular"))
            noise->SetNoiseType(FastNoiseLite::NoiseType::NoiseType_Cellular);
        else if (type.istGleich("ValueCubic"))
            noise->SetNoiseType(FastNoiseLite::NoiseType::NoiseType_ValueCubic);
        else if (type.istGleich("Perlin"))
            noise->SetNoiseType(FastNoiseLite::NoiseType::NoiseType_Perlin);
        else if (type.istGleich("OpenSimplex2S"))
            noise->SetNoiseType(
                FastNoiseLite::NoiseType::NoiseType_OpenSimplex2S);
        else if (type.istGleich("OpenSimplex2"))
            noise->SetNoiseType(
                FastNoiseLite::NoiseType::NoiseType_OpenSimplex2);
        else if (type.istGleich("Value"))
            noise->SetNoiseType(FastNoiseLite::NoiseType::NoiseType_Value);
        if (zConfig->asObject()->hasValue("rotationType3D"))
        {
            Text value = zConfig->asObject()
                             ->zValue("rotationType3D")
                             ->asString()
                             ->getString();
            if (value.istGleich("None"))
            {
                noise->SetRotationType3D(
                    FastNoiseLite::RotationType3D::RotationType3D_None);
            }
            else if (value.istGleich("ImproveXYPlanes"))
            {
                noise->SetRotationType3D(FastNoiseLite::RotationType3D::
                        RotationType3D_ImproveXYPlanes);
            }
            else if (value.istGleich("ImproveXZPlanes"))
            {
                noise->SetRotationType3D(FastNoiseLite::RotationType3D::
                        RotationType3D_ImproveXZPlanes);
            }
        }
        if (zConfig->asObject()->hasValue("frequency"))
        {
            noise->SetFrequency((float)zConfig->asObject()
                                    ->zValue("frequency")
                                    ->asNumber()
                                    ->getNumber());
        }
        if (zConfig->asObject()->hasValue("fractalType"))
        {
            Text value = zConfig->asObject()
                             ->zValue("fractalType")
                             ->asString()
                             ->getString();
            if (value.istGleich("None"))
            {
                noise->SetFractalType(
                    FastNoiseLite::FractalType::FractalType_None);
            }
            else if (value.istGleich("FBm"))
            {
                noise->SetFractalType(
                    FastNoiseLite::FractalType::FractalType_FBm);
            }
            else if (value.istGleich("Ridged"))
            {
                noise->SetFractalType(
                    FastNoiseLite::FractalType::FractalType_Ridged);
            }
            else if (value.istGleich("PingPong"))
            {
                noise->SetFractalType(
                    FastNoiseLite::FractalType::FractalType_PingPong);
            }
            else if (value.istGleich("DomainWarpProgressive"))
            {
                noise->SetFractalType(FastNoiseLite::FractalType::
                        FractalType_DomainWarpProgressive);
            }
            else if (value.istGleich("DomainWarpIndependent"))
            {
                noise->SetFractalType(FastNoiseLite::FractalType::
                        FractalType_DomainWarpIndependent);
            }
        }
        if (zConfig->asObject()->hasValue("fractalOctaves"))
        {
            noise->SetFractalOctaves((int)round(zConfig->asObject()
                                                    ->zValue("fractalOctaves")
                                                    ->asNumber()
                                                    ->getNumber()));
        }
        if (zConfig->asObject()->hasValue("fractalLacunarity"))
        {
            noise->SetFractalLacunarity((float)zConfig->asObject()
                                            ->zValue("fractalLacunarity")
                                            ->asNumber()
                                            ->getNumber());
        }
        if (zConfig->asObject()->hasValue("fractalGain"))
        {
            noise->SetFractalGain((float)zConfig->asObject()
                                      ->zValue("fractalGain")
                                      ->asNumber()
                                      ->getNumber());
        }
        if (zConfig->asObject()->hasValue("cellularDistanceFunction"))
        {
            Text value = zConfig->asObject()
                             ->zValue("cellularDistanceFunction")
                             ->asString()
                             ->getString();
            if (value.istGleich("Hybrid"))
            {
                noise->SetCellularDistanceFunction(
                    FastNoiseLite::CellularDistanceFunction::
                        CellularDistanceFunction_Hybrid);
            }
            else if (value.istGleich("Manhattan"))
            {
                noise->SetCellularDistanceFunction(
                    FastNoiseLite::CellularDistanceFunction::
                        CellularDistanceFunction_Manhattan);
            }
            else if (value.istGleich("EuclideanSq"))
            {
                noise->SetCellularDistanceFunction(
                    FastNoiseLite::CellularDistanceFunction::
                        CellularDistanceFunction_EuclideanSq);
            }
            else if (value.istGleich("Euclidean"))
            {
                noise->SetCellularDistanceFunction(
                    FastNoiseLite::CellularDistanceFunction::
                        CellularDistanceFunction_Euclidean);
            }
        }
        if (zConfig->asObject()->hasValue("cellularReturnType"))
        {
            Text value = zConfig->asObject()
                             ->zValue("cellularReturnType")
                             ->asString()
                             ->getString();
            if (value.istGleich("CellValue"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_CellValue);
            }
            else if (value.istGleich("Distance"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance);
            }
            else if (value.istGleich("Distance2"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance2);
            }
            else if (value.istGleich("Distance2Add"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance2Add);
            }
            else if (value.istGleich("Distance2Sub"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance2Sub);
            }
            else if (value.istGleich("Distance2Mul"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance2Mul);
            }
            else if (value.istGleich("Distance2Div"))
            {
                noise->SetCellularReturnType(FastNoiseLite::CellularReturnType::
                        CellularReturnType_Distance2Div);
            }
        }
        if (zConfig->asObject()->hasValue("cellularJitter"))
        {
            noise->SetCellularJitter((float)zConfig->asObject()
                                         ->zValue("cellularJitter")
                                         ->asNumber()
                                         ->getNumber());
        }
        if (zConfig->asObject()->hasValue("domainWarpType"))
        {
            Text value = zConfig->asObject()
                             ->zValue("domainWarpType")
                             ->asString()
                             ->getString();
            if (value.istGleich("OpenSimplex2Reduced"))
            {
                noise->SetDomainWarpType(FastNoiseLite::DomainWarpType::
                        DomainWarpType_OpenSimplex2Reduced);
            }
            else if (value.istGleich("OpenSimplex2"))
            {
                noise->SetDomainWarpType(
                    FastNoiseLite::DomainWarpType::DomainWarpType_OpenSimplex2);
            }
            else if (value.istGleich("BasicGrid"))
            {
                noise->SetDomainWarpType(
                    FastNoiseLite::DomainWarpType::DomainWarpType_BasicGrid);
            }
        }
        if (zConfig->asObject()->hasValue("domainWarpAmp"))
        {
            noise->SetDomainWarpAmp((float)zConfig->asObject()
                                        ->zValue("domainWarpAmp")
                                        ->asNumber()
                                        ->getNumber());
        }
        FastNoiseWrapper* result
            = new FastNoiseWrapper(noise, (int)(round(seed)));
        if (zConfig->asObject()->hasValue("multiplier"))
        {
            result->setMultiplier((float)zConfig->asObject()
                                      ->zValue("multiplier")
                                      ->asNumber()
                                      ->getNumber());
        }
        return result;
    }
    return 0;
}

Validator::DataValidator* JNoise::getValidator(bool optional)
{
    auto validator1 = Validator::DataValidator::buildForObject();
    auto validator2 = Validator::DataValidator::buildForObject();
    auto validator3 = Validator::DataValidator::buildForObject();
    auto validator4 = Validator::DataValidator::buildForObject();
    auto validator5 = Validator::DataValidator::buildForObject();
    auto validator6 = Validator::DataValidator::buildForObject();
    auto validator7 = Validator::DataValidator::buildForObject();
    if (optional)
    {
        validator1 = validator1->whichIsOptional();
        validator2 = validator2->whichIsOptional();
        validator3 = validator3->whichIsOptional();
        validator4 = validator4->whichIsOptional();
        validator5 = validator5->whichIsOptional();
        validator6 = validator6->whichIsOptional();
        validator7 = validator7->whichIsOptional();
    }
    Validator::DataValidator* refs
        = Validator::DataValidator::buildForOneOf()
              ->typeSpecifiedByAttribute("type")
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_random"))
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_factorize"))
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_multiply"))
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_negate"))
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_flatten"))
              ->addAcceptedType(
                  Validator::DataValidator::buildForReference("jn_scale"))
              ->addAcceptedType(Validator::DataValidator::buildForReference(
                  "jn_fastNoiseLite"))
              ->finishOneOf();
    return Validator::DataValidator::buildForOneOf()
        ->typeSpecifiedByAttribute("type")
        ->addAcceptedType(validator1->setReferenceId("jn_random")
                              ->withRequiredString("type")
                              ->withExactMatch("random")
                              ->finishString()
                              ->withRequiredAttribute("seed",
                                  Game::INSTANCE->zTypeRegistry()
                                      ->getValidator<JFloatExpression>())
                              ->finishObject())
        ->addAcceptedType(
            validator2->setReferenceId("jn_factorize")
                ->withRequiredString("type")
                ->withExactMatch("factorize")
                ->finishString()
                ->withRequiredNumber("factorA")
                ->whichIsGreaterThen(0)
                ->whichIsLessThen(1)
                ->finishNumber()
                ->withRequiredAttribute("noiseA",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->withRequiredAttribute("noiseB",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->finishObject())
        ->addAcceptedType(
            validator3->setReferenceId("jn_multiply")
                ->withRequiredString("type")
                ->withExactMatch("multiply")
                ->finishString()
                ->withRequiredAttribute("base",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->withRequiredAttribute("multiplier",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->finishObject())
        ->addAcceptedType(
            validator4->setReferenceId("jn_negate")
                ->withRequiredString("type")
                ->withExactMatch("negate")
                ->finishString()
                ->withRequiredAttribute("noise",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->finishObject())
        ->addAcceptedType(
            validator5->setReferenceId("jn_flatten")
                ->withRequiredString("type")
                ->withExactMatch("flatten")
                ->finishString()
                ->withRequiredAttribute("noise",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->withRequiredNumber("factor")
                ->withDefault(1.0)
                ->finishNumber()
                ->withRequiredNumber("addition")
                ->withDefault(0.0)
                ->finishNumber()
                ->finishObject())
        ->addAcceptedType(
            validator6->setReferenceId("jn_scale")
                ->withRequiredString("type")
                ->withExactMatch("scale")
                ->finishString()
                ->withRequiredAttribute("noise",
                    dynamic_cast<Validator::DataValidator*>(refs->getThis()))
                ->withRequiredNumber("factor")
                ->finishNumber()
                ->finishObject())
        ->addAcceptedType(
            validator7->setReferenceId("jn_fastNoiseLite")
                ->withRequiredString("type")
                ->whichIsOneOf({"Cellular",
                    "ValueCubic",
                    "Perlin",
                    "OpenSimplex2S",
                    "OpenSimplex2",
                    "Value"})
                ->finishString()
                ->withRequiredAttribute("seed",
                    Game::INSTANCE->zTypeRegistry()
                        ->getValidator<JFloatExpression>())
                ->withRequiredString("rotationType3D")
                ->whichIsOptional()
                ->whichIsOneOf({"None", "ImproveXYPlanes", "ImproveXZPlanes"})
                ->finishString()
                ->withRequiredNumber("frequency")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredString("fractalType")
                ->whichIsOptional()
                ->whichIsOneOf({"None",
                    "FBm",
                    "Ridged",
                    "PingPong",
                    "DomainWarpProgressive",
                    "DomainWarpIndependent"})
                ->finishString()
                ->withRequiredNumber("fractalOctaves")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredNumber("fractalLacunarity")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredNumber("fractalGain")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredString("cellularDistanceFunction")
                ->whichIsOptional()
                ->whichIsOneOf(
                    {"Hybrid", "Manhattan", "EuclideanSq", "Euclidean"})
                ->finishString()
                ->withRequiredString("cellularReturnType")
                ->whichIsOptional()
                ->whichIsOneOf({"CellValue",
                    "Distance",
                    "Distance2",
                    "Distance2Add",
                    "Distance2Sub",
                    "Distance2Mul",
                    "Distance2Div"})
                ->finishString()
                ->withRequiredNumber("cellularJitter")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredString("domainWarpType")
                ->whichIsOptional()
                ->whichIsOneOf(
                    {"BasicGrid", "OpenSimplex2", "OpenSimplex2Reduced"})
                ->finishString()
                ->withRequiredNumber("domainWarpAmp")
                ->whichIsOptional()
                ->finishNumber()
                ->withRequiredNumber("multiplier")
                ->whichIsOptional()
                ->whichIsGreaterThen(0)
                ->finishNumber()
                ->finishObject())
        ->finishOneOf();
}