import { create_simplex_noise_2d } from "../../../../core/math/noise/create_simplex_noise_2d.js";
import { seededRandom } from "../../../../core/math/random/seededRandom.js";
import { CellFilter } from "../../CellFilter.js";
import { CellFilterLiteralFloat } from "../CellFilterLiteralFloat.js";
import { CellFilterAdd } from "../math/algebra/CellFilterAdd.js";
import { CellFilterDivide } from "../math/algebra/CellFilterDivide.js";
import { CellFilterMultiply } from "../math/algebra/CellFilterMultiply.js";

export class CellFilterSimplexNoise extends CellFilter {

        /**
         * @private
         * @type {function(x:number,y:number):number}
         */
        noise = null;

        /**
         * @private
         * @type {function}
         */
        random = seededRandom(0);

        scale_x = 1;
        scale_y = 1;

        inv_scale_x = 1;
        inv_scale_y = 1;

        /**
         * RNG Seed offset
         * @type {number}
         * @private
         */
        __seed = 0;

    /**
     * Build a fractal noise filter with a given number of octaves
     * @param {number} octaves
     * @param {number} scale
     * @param {number} [persistence]
     * @param {number} [lacunarity]
     * @param {number} [seed]
     * @returns {CellFilter}
     */
    static fractal(octaves, scale, seed = 0, persistence = 0.5, lacunarity = 2) {
        let s = seed;
        let r;

        let frequency = 1;
        let amplitude = 1;
        let totalAmplitude = 0;

        const G = Math.pow(2, -persistence);

        for (let i = 0; i < octaves; i++) {
            const noise = CellFilterSimplexNoise.from(scale * frequency, scale * frequency, s);

            if (r === undefined) {
                r = noise;
            } else {
                r = CellFilterAdd.from(
                    CellFilterMultiply.from(noise, CellFilterLiteralFloat.from(amplitude)),
                    r
                );
            }

            totalAmplitude += amplitude;
            amplitude *= G;
            frequency *= lacunarity;

            s = (s + 1) * 31;
        }

        if (totalAmplitude === 1) {
            return r;
        } else {

            return CellFilterDivide.from(
                r,
                CellFilterLiteralFloat.from(totalAmplitude)
            );

        }
    }

    /**
     *
     * @param {number} scale_x
     * @param {number} scale_y
     * @param {number} [seed]
     * @returns {CellFilterSimplexNoise}
     */
    static from(scale_x, scale_y, seed = 0) {
        const r = new CellFilterSimplexNoise();

        r.scale_x = scale_x;
        r.scale_y = scale_y;
        r.__seed = seed;

        r.inv_scale_x = 1 / scale_x;
        r.inv_scale_y = 1 / scale_y;

        return r;
    }

    initialize(grid, seed) {
        this.random.setCurrentSeed(seed + this.__seed);

        this.noise = create_simplex_noise_2d(this.random);

        super.initialize(grid, seed);
    }

    execute(grid, x, y, rotation) {
        const noiseValue = this.noise(x * this.inv_scale_x, y * this.inv_scale_y);

        //noise function returns values in range [-1,1] we need to scale that to [0,1] range
        return (noiseValue + 1) * 0.5;
    }
}
