#include "Library/Math/FractalGenerator.h"

#include "Library/Math/MathUtil.h"

namespace al {
FractalGenerator::FractalGenerator(u32 permutations, f32 amplitude, f32 scale,
                                   f32 nextOrderAmplitude)
    : mPermutations(permutations), mAmplitude(amplitude), mScale(scale),
      mNextOrderAmplitude(nextOrderAmplitude) {}

void FractalGenerator::setParam(u32 permutations, f32 amplitude, f32 scale,
                                f32 nextOrderAmplitude) {
    mPermutations = permutations;
    mAmplitude = amplitude;
    mScale = scale;
    mNextOrderAmplitude = nextOrderAmplitude;
}

f32 FractalGenerator::calcFractal(f32 x, f32 y, bool useSmoothPerlingNoise) {
    f32 value = 0.0f;

    f32 amplitude = mAmplitude;
    f32 scale = mScale;
    for (s32 i = 0; i < (s32)(mPermutations - 1); i++) {
        if (useSmoothPerlingNoise)
            value += makeSmoothPerlinNoise(scale * x, scale * y) * amplitude;
        else
            value += makePerlinNoise(scale * x, scale * y) * amplitude;

        scale *= 2;
        amplitude *= mNextOrderAmplitude;
    }

    return value;
}

f32 FractalGenerator::makeSmoothPerlinNoise(f32 x, f32 y) {
    s32 gridX = (s32)x;
    s32 gridY = (s32)y;
    f32 fracX = x - gridX;
    f32 fracY = y - gridY;

    // NOTE: ASM is using an undef call for subsequent calls. This is not a bug since
    // makeSmoothRandom doesn't make use of any class members. This is probably a compiler
    // optimization error
    FractalGenerator* gen;
    f32 r00 = makeSmoothRandom(gridX, gridY);
    f32 r10 = gen->makeSmoothRandom(gridX + 1, gridY);
    f32 r01 = gen->makeSmoothRandom(gridX, gridY + 1);
    f32 r11 = gen->makeSmoothRandom(gridX + 1, gridY + 1);

    return lerpValue(lerpValue(r00, r10, easeInOut(fracX)), lerpValue(r01, r11, easeInOut(fracX)),
                     easeInOut(fracY));
}

f32 FractalGenerator::makePerlinNoise(f32 x, f32 y) {
    s32 gridX = (s32)x;
    s32 gridY = (s32)y;
    f32 fracX = x - gridX;
    f32 fracY = y - gridY;

    f32 r00 = makeRandom(gridX, gridY);
    f32 r10 = makeRandom(gridX + 1, gridY);
    f32 r01 = makeRandom(gridX, gridY + 1);
    f32 r11 = makeRandom(gridX + 1, gridY + 1);

    return lerpValue(lerpValue(r00, r10, easeInOut(fracX)), lerpValue(r01, r11, easeInOut(fracX)),
                     easeInOut(fracY));
}

f32 FractalGenerator::calcMultiFractal(f32 x, f32 y, f32 baseAmplitude,
                                       bool useSmoothPerlingNoise) {
    f32 value = 1.0f;

    f32 amplitude = mAmplitude;
    f32 scale = mScale;
    for (s32 i = 0; i < (s32)(mPermutations - 1); i++) {
        if (useSmoothPerlingNoise)
            value *= baseAmplitude * makeSmoothPerlinNoise(scale * x, scale * y) * amplitude;
        else
            value *= baseAmplitude * makePerlinNoise(scale * x, scale * y) * amplitude;

        scale *= 2;
        amplitude *= mNextOrderAmplitude;
    }

    return value;
}

inline s32 makeSeed(s32 x, s32 y) {
    s32 seed = x + y * 57;
    seed ^= seed << 13;
    return seed;
}

inline f32 makeNoise(s32 seed) {
    // NOTE: The only true random are all these magic values
    s32 hash = (seed * seed * 0x4bd9 + 0x2e59b) * seed + 0x4bcb4b;
    return static_cast<f32>(hash & 0x4b746f) / -3354521.0f;
}

f32 FractalGenerator::makeRandom(s32 x, s32 y) {
    return makeNoise(makeSeed(x, y)) + 1.0f;
}

f32 FractalGenerator::makeSmoothRandom(s32 x, s32 y) {
    constexpr f32 weight_corners = 0.25f;
    constexpr f32 weight_sides = 0.5f;
    constexpr f32 weight_center = 0.25f;

    f32 corners = (makeRandom(x - 1, y - 1) + makeRandom(x + 1, y - 1) + makeRandom(x - 1, y + 1) +
                   makeRandom(x + 1, y + 1)) *
                  ((1.0f / 4.0f) * weight_corners);
    f32 sides = (makeRandom(x - 1, y) + makeRandom(x + 1, y) + makeRandom(x, y - 1) +
                 makeRandom(x, y + 1)) *
                ((1.0f / 4.0f) * weight_sides);
    ;
    f32 center = makeRandom(x, y) * weight_center;
    return corners + sides + center;
}

}  // namespace al
