#pragma once

#if HASTY
#include <hastyNoise.h>
#else
#include <FastNoiseSIMD.h>
#endif
#include <memory>
#include "../../core/world/position.h"

namespace world {
    /// Noise handler
    class Noise {
    public:
#if HASTY
        using handle = HastyNoise::NoiseSIMD;
        using set = std::unique_ptr<float>;
#else
        using handle = FastNoiseSIMD;
        struct fast_set_deleter {
            constexpr fast_set_deleter() noexcept = default;

            void operator()(float* set) const {
                FastNoiseSIMD::FreeNoiseSet(set);
            }
        };
        using set = std::unique_ptr<float, fast_set_deleter>;
#endif

        struct Base {
            int seed = 1337;
            float frequency = .01f;
        };
        struct Simplex: Base {
            Simplex(int _seed, float _frequency) {
                seed = _seed;
                frequency = _frequency;
            }
        };
        struct SimplexFractal: Simplex {
            SimplexFractal(int seed, float frequency = .01f, int octaves = 3, float lacunarity = 2.0f, float gain = .5f):
                Simplex(seed, frequency), octaves(octaves), lacunarity(lacunarity), gain(gain) { }
            int octaves;
            float lacunarity;
            float gain;
            //FractalType m_fractalType = FBM;
        };
        struct Cellular: Base {
            Cellular(int _seed, float _frequency) {
                seed = _seed;
                frequency = _frequency;
            }
            //MAYBE: add options
        };

        Noise(Simplex s): Noise(s, type::Simplex) { }
        Noise(SimplexFractal sf): Noise(sf, type::SimplexFractal) {
            noise->SetFractalGain(sf.gain);
            noise->SetFractalLacunarity(sf.lacunarity);
            noise->SetFractalOctaves(sf.octaves);
            //noise->SetFractalType();
        }
        Noise(Cellular c): Noise(c, type::Cellular) {
#if HASTY
            noise->SetCellularReturnType(HastyNoise::CellularReturnType::Value);
            noise->SetCellularDistanceFunction(HastyNoise::CellularDistance::Natural);
#else
            noise->SetCellularReturnType(FastNoiseSIMD::CellValue);
            noise->SetCellularDistanceFunction(FastNoiseSIMD::Natural);
#endif
        }

        /// Get block of given size with index pos.
        inline set Get(const chunk_pos& pos, int size) {
#if HASTY
            return noise->GetNoiseSet(pos.x * size, pos.y * size, pos.z * size, size, size, size);
#else
            return std::unique_ptr<float, fast_set_deleter>(noise->GetNoiseSet(pos.x * size, pos.y * size, pos.z * size, size, size, size), fast_set_deleter());
#endif
        }

    private:
        enum class type {
            SimplexFractal, Simplex, Cellular
        };

#if HASTY
        static HastyNoise::NoiseType ConvertType(type t) {
            switch(t) {
                case type::Simplex:
                    return HastyNoise::NoiseType::Simplex;
                case type::SimplexFractal:
                    return HastyNoise::NoiseType::SimplexFractal;
                case type::Cellular:
                    return HastyNoise::NoiseType::Cellular;
                }
            return HastyNoise::NoiseType::Simplex;
        }
#else
        static FastNoiseSIMD::NoiseType ConvertType(type t) {
            switch(t) {
                case type::Simplex:
                    return FastNoiseSIMD::NoiseType::Simplex;
                case type::SimplexFractal:
                    return FastNoiseSIMD::NoiseType::SimplexFractal;
                case type::Cellular:
                    return FastNoiseSIMD::NoiseType::Cellular;
                }
            return FastNoiseSIMD::NoiseType::Simplex;
        }
#endif

        Noise(Base p, type t) {
#if HASTY
            const size_t fastestSimd = HastyNoise::GetFastestSIMD();
            noise = HastyNoise::CreateNoise(p.seed, fastestSimd);
#else
            noise = std::unique_ptr<FastNoiseSIMD>(FastNoiseSIMD::NewFastNoiseSIMD(p.seed));
#endif
            noise->SetNoiseType(ConvertType(t));
            noise->SetFrequency(p.frequency);
        }

        std::unique_ptr<handle> noise;
    };
}
