#include "common/world/HighMap.h"
#include "gaming-core/utils/Random.h"

static constexpr int permutation[] = {
    151, 160, 137, 91,  90,  15,  131, 13,  201, 95,  96,  53,  194, 233, 7,   225, 140, 36,  103, 30,  69,  142,
    8,   99,  37,  240, 21,  10,  23,  190, 6,   148, 247, 120, 234, 75,  0,   26,  197, 62,  94,  252, 219, 203,
    117, 35,  11,  32,  57,  177, 33,  88,  237, 149, 56,  87,  174, 20,  125, 136, 171, 168, 68,  175, 74,  165,
    71,  134, 139, 48,  27,  166, 77,  146, 158, 231, 83,  111, 229, 122, 60,  211, 133, 230, 220, 105, 92,  41,
    55,  46,  245, 40,  244, 102, 143, 54,  65,  25,  63,  161, 1,   216, 80,  73,  209, 76,  132, 187, 208, 89,
    18,  169, 200, 196, 135, 130, 116, 188, 159, 86,  164, 100, 109, 198, 173, 186, 3,   64,  52,  217, 226, 250,
    124, 123, 5,   202, 38,  147, 118, 126, 255, 82,  85,  212, 207, 206, 59,  227, 47,  16,  58,  17,  182, 189,
    28,  42,  223, 183, 170, 213, 119, 248, 152, 2,   44,  154, 163, 70,  221, 153, 101, 155, 167, 43,  172, 9,
    129, 22,  39,  253, 19,  98,  108, 110, 79,  113, 224, 232, 178, 185, 112, 104, 218, 246, 97,  228, 251, 34,
    242, 193, 238, 210, 144, 12,  191, 179, 162, 241, 81,  51,  145, 235, 249, 14,  239, 107, 49,  192, 214, 31,
    181, 199, 106, 157, 184, 84,  204, 176, 115, 121, 50,  45,  127, 4,   150, 254, 138, 236, 205, 93,  222, 114,
    67,  29,  24,  72,  243, 141, 128, 195, 78,  66,  215, 61,  156, 180};

static int p[512];

static double fade(double t) {
    return t * t * t * (t * (t * 6 - 15) + 10);
}

static double lerp(double t, double a, double b) {
    return a + t * (b - a);
}

static double grad(int hash, double x, double y) {
    int h = hash & 15;
    double u = h < 8 ? x : y;
    double v = h < 4 ? y : (h == 12 || h == 14 ? x : 0);
    return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}

HighMap::HighMap(int size, int height) : size(size), height(height) {
    for(int i = 0; i < 256; i++)
        p[256 + i] = p[i] = permutation[i];
}

int HighMap::getHeight(int x2, int y2) const {
    x2 &= size - 1;
    y2 &= size - 1;
    float x = x2 / 64.0f;
    float y = y2 / 64.0f;
    int X = ((int)x) & 255;
    int Y = ((int)y) & 255;
    x -= (int)(x);
    y -= (int)(y);
    double u = fade(x);
    double v = fade(y);
    int A = p[X] + Y;
    int AA = p[A];
    int AB = p[A + 1];
    int B = p[X + 1] + Y;
    int BA = p[B];
    int BB = p[B + 1];

    double d = lerp(v, lerp(u, grad(p[AA], x, y), grad(p[BA], x - 1, y)),
                    lerp(u, grad(p[AB], x, y - 1), grad(p[BB], x - 1, y - 1)));
    d *= d;
    return static_cast<unsigned int>(d * height + 1.0) % height;
}

unsigned int pot(unsigned int a, unsigned int n) {
    unsigned int sum = 0;
    while(n > 0) {
        if(n & 1) {
            sum += a;
        }
        a *= a;
        n >>= 1;
    }
    return sum;
}

int HighMap::random(int x, int y) const {
    return (pot(328249, x) + pot(894503, y)) % height;
}