#include "noise.h"
#include <iostream>
#include <glm/ext.hpp>
#include <ctime>

void Random::init()
{
    std::srand(std::time(nullptr));
}

void Random::init(unsigned int seed)
{
    std::srand(seed);
}


int Random::rand(int min, int max)
{
    return std::rand();
}

long Random::rand(long min, long max)
{
    long val;
    int * ptr = ((int*)&val);
    ptr[0] = std::rand();
    ptr[1] = std::rand();
    long range = max-min;
    val = (val % range);
    return (val > 0 ? val : val + range) + min;
}

float Random::rand(float min, float max)
{
    float val = float(std::rand()) / RAND_MAX;
    return val*(max-min) + min;
}

double Random::rand(double min, double max)
{
    double val;
    int * ptr = ((int*)&val);
    ptr[0] = std::rand();
    ptr[1] = std::rand();

    return val*(max-min) + min;
}


Noise::Noise()
{

}

Noise::~Noise()
{

}

void Noise::initPerlinNoise(int seed)
{
    int i,j,k;
    srand(seed);
    //INFO: permutation array
    for (i = 0 ; i < ARBITRARY_VALUE ; i++) permut[i] = i;
    for (i = ARBITRARY_VALUE ; i > 0 ; i -= 2)
    {
        k = permut[i];
        permut[i] = permut[j = std::rand() % ARBITRARY_VALUE];
        permut[j] = k;
    };
}

float Noise::PerlinNoise2D(float x, float z)
{
    int x0,x1,z0,z1;
    x0 = fastfloor(x);
    x1 = x0 + 1;
    z0 = fastfloor(z);
    z1 = z0 + 1;

    float dx,dz;
    dx = x - x0;
    dz = z - z0;

    glm::vec2 grad[4];
    int idx[4];

    idx[0] = process2DIndex(x0,z0);
    idx[1] = process2DIndex(x1,z0);
    idx[2] = process2DIndex(x0,z1);
    idx[3] = process2DIndex(x1,z1);

    for(int i=0; i < 4; i++)
        grad[i]	= gradient2[idx[i]];

    glm::vec2 vect[4];
    vect[0] = glm::vec2(dx+1, dz+1);
    vect[1] = glm::vec2(dx-1, dz+1);
    vect[2] = glm::vec2(dx+1, dz-1);
    vect[3] = glm::vec2(dx-1, dz-1);

    float val[4];
    for(int i=0; i < 4; i++)
        val[i] = glm::dot(grad[i], vect[i]);

    float sx, sz;
    sx = (3.f * dx * dx) - (2.f * dx * dx * dx);
    sz = (3.f * dz * dz) - (2.f * dz * dz * dz);

    // interpolation on axis x
    float u[2];
    for (int i = 0;i <2; i++)
        u[i]= lerp(val[i*2],val[(i*2)+1], sx);

    // interpolation on axis z
    return lerp(u[0],u[1],sz);
}


float Noise::PerlinNoise3D(float x, float y, float z)
{
    int x0, x1, y0, y1, z0, z1;

    x0 = fastfloor(x);
    x1 = x0 + 1;

    y0 = fastfloor(y);
    y1 = y0 + 1;

    z0 = fastfloor(z);
    z1 = z0 + 1;

    float dx,dy,dz;
    dx = x - x0;
    dy = y - y0;
    dz = z - z0;

    glm::vec3 grad[8];
    grad[0] = gradient3[(permut[(permut[x0%ARBITRARY_VALUE] + y0)%ARBITRARY_VALUE] + z0)];
    grad[1] = gradient3[(permut[(permut[x1%ARBITRARY_VALUE] + y0)%ARBITRARY_VALUE] + z0)];
    grad[2] = gradient3[(permut[(permut[x0%ARBITRARY_VALUE] + y1)%ARBITRARY_VALUE] + z0)];
    grad[3] = gradient3[(permut[(permut[x1%ARBITRARY_VALUE] + y1)%ARBITRARY_VALUE] + z0)];
    grad[4] = gradient3[(permut[(permut[x0%ARBITRARY_VALUE] + y0)%ARBITRARY_VALUE] + z1)];
    grad[5] = gradient3[(permut[(permut[x1%ARBITRARY_VALUE] + y0)%ARBITRARY_VALUE] + z1)];
    grad[6] = gradient3[(permut[(permut[x0%ARBITRARY_VALUE] + y1)%ARBITRARY_VALUE] + z1)];
    grad[7] = gradient3[(permut[(permut[x1%ARBITRARY_VALUE] + y1)%ARBITRARY_VALUE] + z1)];

    glm::vec3 vect[8];
    vect[0] = glm::vec3(dx,   dy,   dz);
    vect[1] = glm::vec3(dx-1, dy,   dz);
    vect[2] = glm::vec3(dx,   dy-1, dz);
    vect[3] = glm::vec3(dx-1, dy-1, dz);
    vect[4] = glm::vec3(dx,   dy,   dz-1);
    vect[5] = glm::vec3(dx-1, dy,   dz-1);
    vect[6] = glm::vec3(dx,   dy-1, dz-1);
    vect[7] = glm::vec3(dx-1, dy-1, dz-1);

    float val[8];
    for(int i=0; i < 8; i++)
        val[i] = glm::dot(grad[i], vect[i]);

    // process weight for interpolation
    float sx, sy, sz;
    sx = (3.f * dx * dx) - (2.f * dx * dx * dx);
    sy = (3.f * dy * dy) - (2.f * dy * dy * dy);
    sz = (3.f * dz * dz) - (2.f * dz * dz * dz);

    // interpolation on axis x
    float u[4];
    for (int i = 0;i <4; i++){
        u[i]= lerp(val[i*2],val[(i*2)+1], sx);
    }

    // interpolation on axis y
    float v[2];
    for (int i = 0;i <2; i++)
        v[i]= lerp(u[i*2],u[(i*2)+1],sy);

    // interpolation on axis z
    return lerp(v[0],v[1],sz);
}

//shitty function
float Noise::OctavePerlinNoise(float nb_octave, float persistence, float x, float y, float z)
{
    float total = 0;
    float frequency = 1;
    float amplitude = 100;

    float maxAmplitude = 0;

    for(int i=0; i < nb_octave; i++){
        total += PerlinNoise3D(x/frequency, y/frequency, z/frequency)*amplitude;

        frequency *= 2;
        maxAmplitude += amplitude;
        amplitude *= persistence;
    }

    return total/maxAmplitude;
}

// NOT IMPLEMENTED
float Noise::SimplexNoise(float x, float y, float z){
    //    int x0, x1, y0, y1, z0, z1;
    return 1.f;
}

int Noise::process2DIndex(int x, int z)
{
    int u = x%ARBITRARY_VALUE;
    u = u < 0 ? u + ARBITRARY_VALUE : u;
    int v = (permut[u] + z) % ARBITRARY_VALUE;
    v = v < 0 ? v + ARBITRARY_VALUE : v;
    return permut[v];
}

int Noise::fastfloor(float v)
{
    int res = (int) v;
    return res > v ? res -1 : res;
}

//linear interpolation
float Noise::lerp(float a, float b, float w){
    return (1-w)*a + w*b;
}

glm::vec2 Noise::gradient2[] = {
    glm::vec2(1,0), glm::vec2(-1,0), glm::vec2(0,1), glm::vec2(0,-1),
    glm::vec2(1,1), glm::vec2(-1,1), glm::vec2(1,-1), glm::vec2(-1,-1),
    glm::vec2(0,1), glm::vec2(0,-1), glm::vec2(1,0), glm::vec2(-1,0)
};

glm::vec3 Noise::gradient3[] = {
//    irr::core::vector3df(1,1,0), irr::core::vector3df(-1,1,0), irr::core::vector3df(1,-1,0), irr::core::vector3df(-1,-1,0),
//    irr::core::vector3df(1,0,1), irr::core::vector3df(-1,0,1), irr::core::vector3df(1,0,-1), irr::core::vector3df(-1,0,-1),
//    irr::core::vector3df(0,1,1), irr::core::vector3df(0,-1,1), irr::core::vector3df(0,1,-1), irr::core::vector3df(0,-1,-1)
};

int Noise::permut[] = {0};
