#pragma once

#include <FastNoise/FastNoise.h>
#include "../units.hpp"

namespace world::generator {
    /// Noise handler
    class Noise {
    public:
        using handle = FastNoise::SmartNode<>;
        using set = std::unique_ptr<float[]>;

        struct Core {
            Core(int seed, float frequency): seed(seed), frequency(frequency) { }
            int seed;
            float frequency;
        };
        struct Simplex: Core {
            Simplex(int seed, float frequency = .01f): Core(seed, frequency) { }
        };
        struct Fractal {
            enum class Type {
                FBm, Billow, Ridged, RidgedMulti
            };

            Fractal(int octaves, float lacunarity, float gain, Type type):
                octaves(octaves), lacunarity(lacunarity), gain(gain), type(type) { }
            int octaves;
            float lacunarity;
            float gain;
            Type type;
        };
        struct SimplexFractal: Simplex, Fractal {
            SimplexFractal(int seed, float frequency = .01f, int octaves = 3, float lacunarity = 2.0f, float gain = .5f, Type type = Type::FBm):
                Simplex(seed, frequency), Fractal(octaves, lacunarity, gain, type) { }
        };
        struct Cellular: Core {
            Cellular(int seed, float frequency = .01f, float jitter = 1.0f): Core(seed, frequency), jitter(jitter) { }
            float jitter;
            //MAYBE: add options
        };

        Noise(handle h, Core s): ptr(h), state(s) { }
        Noise(Simplex s): Noise(FastNoise::New<FastNoise::Simplex>(), s) { }
        Noise(handle src, Core s, Fractal f): state(s) {
            const auto frac = [](Fractal::Type type) -> FastNoise::SmartNode<FastNoise::Fractal<>> {
                switch (type) {
                    case Fractal::Type::FBm:
                        return FastNoise::New<FastNoise::FractalFBm>();
                    case Fractal::Type::Billow:
                        return FastNoise::New<FastNoise::FractalBillow>();
                    case Fractal::Type::Ridged:
                        return FastNoise::New<FastNoise::FractalRidged>();
                    case Fractal::Type::RidgedMulti:
                        return FastNoise::New<FastNoise::FractalRidgedMulti>();
                    default:
                        return nullptr;
                }
            }(f.type);
            frac->SetSource(src);
            frac->SetGain(f.gain);
            frac->SetLacunarity(f.lacunarity);
            frac->SetOctaveCount(f.octaves);
            ptr = frac;
        }
        Noise(SimplexFractal sf): Noise(FastNoise::New<FastNoise::Simplex>(), sf, sf) { }
        Noise(Cellular c): state(c) {
            auto cel = FastNoise::New<FastNoise::CellularValue>();
            cel->SetJitterModifier(c.jitter);
            ptr = cel;
        }

        inline set getRaw(const glm::i32vec3& start, const glm::i32vec3& size) const {
            auto out = std::make_unique<float[]>(size.x * size.y * size.z);
            ptr->GenUniformGrid3D(out.get(), start.x, start.y, start.z, size.x, size.y, size.z, state.frequency, state.seed);
            return out;
        }
        /// Get 3D block of given size with index pos.
        inline set getBlock(const glm::i32vec3& idx, int size) {
            return getRaw(idx * glm::i32vec3(size), glm::i32vec3(size));
        }

    private:
        handle ptr;
        Core state;
    };
}
