/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#ifndef OSGEARTH_SIMPLEX_NOISE_H
#define OSGEARTH_SIMPLEX_NOISE_H 1

#include <osgEarth/Common>
#include <osg/Image>

namespace osgEarth { namespace Util
{
    /**
     * Simplex Noise Generator.
     * Adapted from https://github.com/Taywee/Noise
     */
    class OSGEARTH_EXPORT SimplexNoise
    {
    public:
        SimplexNoise();
        SimplexNoise(const SimplexNoise& rhs);
        virtual ~SimplexNoise() { }

        static const double DefaultFrequency;
        static const double DefaultPersistence;
        static const double DefaultLacunarity;
        static const double DefaultRangeLow;
        static const double DefaultRangeHigh;
        static const unsigned DefaultOctaves;
        static const bool   DefaultNormalize;

        /**
         * Frequency is how often the noise pattern resets to zero across
         * the input domain. For example, if your input range from 
         * -100 < x < 100, a frequency of 1/200 will cause the noise function to
         * reset every 200 units, i.e., the extent of the input domain. (It will
         * be zero at -100 and zero again at 100. It may cross zero in between,
         * but that is not guaranteed.)
         *
         * In mapping terms, say your map is 100000m wide. A frequency of 1/100000
         * will result in a pattern that resets (and wraps around) every 100000m.
         *
         * Default = 1.0.
         */ 
        void setFrequency(double freq) { _freq = freq; }
        double getFrequency() const { return _freq; }

        /**
         * Persietence is the factor by which the noise function's amplitude
         * decreases with each successive octave.
         *   i.e.: Amp(Oct2) = Amp(Oct1) * Persistance.
         * Default = 0.5.
         * Effect: Higher persistence: noisiness persists as you get more detail;
         *         Lower persistence: data smooths out faster as you get more detail.
         */
        void setPersistence(double pers) { _pers = pers; }
        double getPersistence() const { return _pers; }

        /**
         * Lacunarity is the factor by which the noise function's frequency
         * increases with each successive octave.
         *   i.e.: Freq(Oct2) = Freq(Oct1) * Lacunarity.
         * Default = 2.0.
         * Effect: Higher lacunarity: more "plateaus" in the output;
         *         Lower lacunarity:  more "chopiness" in the output.
         */
        void setLacunarity(double lac) { _lacunarity = lac; }
        double getLacunarity() const { return _lacunarity; }

        /**
         * The output range for noise data.
         * Default = [-1..1].
         */
        void setRange(double low, double high) { _low = low, _high = high; }
        double getRangeLow() const { return _low; }
        double getRangeHigh() const { return _high; }

        /**
         * Whether to normalize output values within the limits set by
         * setRange().
         */
        void setNormalize(bool value) { _normalize = value; }
        bool getNormalize() const { return _normalize; }
        
        /**
         * Number of iterations over which to generate noise. The frequency starts
         * where you set it, and over each successive iteration, it is scaled by
         * the persistence. This lets you control the rate at which the "noisiness"
         * decreases with each octave.
         * Default = 1.
         */
        void setOctaves(unsigned octaves) { _octaves = octaves; }
        unsigned getOctaves() const { return _octaves; }

        /**
         * Generates 2D simplex Noise
         */
        double getValue(double xin, double yin) const;

        /**
         * Generates 3D simplex Noise
         */
        double getValue(double xin, double yin, double zin) const;

        /**
         * Generates 4D simplex Noise
         */
        double getValue(double x, double y, double z, double w) const;

        /**
         * Generates tilable 2D noise for x[0..1), y[0..1).
         */
        double getTiledValue(double x, double y) const;
        
        double getTiledValueWithTurbulence(double x, double y, double F) const;

        /**
         * Creates a tileable image of the requested dimensions.
         * The image will be histogram-stretched in the range [0..1].
         */
        class osg::Image* createSeamlessImage(unsigned dim) const;

    private:
        // Inner class to speed up gradient computations
        // (array access is a lot slower than member access)
        struct Grad
        {
            Grad(double x, double y, double z);
            Grad(double x, double y, double z, double w);
            double x, y, z, w;
        };

        const static Grad grad3[12];
        const static Grad grad4[32];
        const static unsigned char perm[512];

        // Skewing and unskewing factors for 2, 3, and 4 dimensions
        static double const F2;
        static double const G2;
        static double const F3;
        static double const G3;
        static double const F4;
        static double const G4;

        unsigned char permMod12[512];

        // This method is a *lot* faster than using (int)Math.floor(x)
        inline static int FastFloor(double x);
        static double Dot(Grad const & g, double x, double y);
        static double Dot(Grad const & g, double x, double y, double z);
        static double Dot(Grad const & g, double x, double y, double z, double w);

        double Noise(double x, double y) const;
        double Noise(double x, double y, double z) const;
        double Noise(double x, double y, double z, double w) const;

        double _freq;
        double _pers;
        double _lacunarity;
        double _low, _high;
        unsigned _octaves;
        bool _normalize;
    };

} } // namespace osgEarth

#endif //OSGEARTH_SIMPLEX_NOISE_H
