import OpenSimplexNoise from 'open-simplex-noise'
import { vec3 } from 'gl-matrix'
import Resource from '../../resource'

class HeightMap {
  constructor (size) {
    this.size = size
  }

  static async fromFile (file, amplitude) {
    let img = await Resource.loadImage(file)
    if (img.width / img.height !== 1) throw new Error('Height Map needs to be of 1:1 aspect ratio.')

    let hmap = new HeightMap(img.width)
    let sampler = Resource.imageToSampler(img)

    for (let x = 0; x < img.width; x++) {
      for (let y = 0; y < img.width; y++) {
        hmap['h' + x + ';' + y] = (sampler(x, y)[0] / 255 * 2 - 1) * amplitude
      }
    }

    sampler = null

    return hmap
  }

  getHeight (x, y) {
    if (x > this.size || y > this.size || x < 0 || y < 0) return 0
    if (!this['h' + x + ';' + y]) return 0
    return this['h' + x + ';' + y]
  }

  getNormal (x, y) {
    let hL = this.getHeight(x - 1, y)
    let hR = this.getHeight(x + 1, y)
    let hD = this.getHeight(x, y - 1)
    let hU = this.getHeight(x, y + 1)
    let normal = vec3.fromValues(hL - hR, 2.0, hD - hU)
    vec3.normalize(normal, normal)
    return normal
  }

  // Vertex position based getters with LOD subnode support

  getHeightPlaneMesh (mesh, vx, vz, absolute) {
    let pos = mesh.pos
    if (absolute) pos = mesh.absolutePosition
    return this.getHeight(pos[0] + vx, pos[2] + vz)
  }

  getNormalPlaneMesh (mesh, vx, vz, absolute) {
    let pos = mesh.pos
    if (absolute) pos = mesh.absolutePosition
    return this.getNormal(pos[0] + vx, pos[2] + vz)
  }
}

class SimplexHeightMap extends HeightMap {
  // amplitude    - Controls the amount the height changes. The higher, the taller the hills.
  // persistence  - Controls details, value in [0,1]. Higher increases grain, lower increases smoothness.
  // octaves      - Number of noise layers
  // period       - Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain.
  // lacunarity   - Controls period change across octaves. 2 is usually a good value to address all detail levels.
  constructor (offsetX, offsetY, size, seed, amplitude = 15, persistence = 0.4, octaves = 5, period = 80, lacunarity = 2) {
    super(size)
    this.ix = offsetX
    this.iy = offsetY
    this.seed = seed

    this.osn = new OpenSimplexNoise(seed)

    this.amplitude = amplitude
    this.period = period
    this.lacunarity = lacunarity
    this.octaves = octaves
    this.persistence = persistence
  }

  getNoise (zx, zy) {
    let x = ((this.size * this.ix) + zx) / this.period
    let y = ((this.size * this.iy) + zy) / this.period

    let amp = 1.0
    let max = 1.0
    let sum = this.osn.noise2D(x, y)

    let i = 0
    while (++i < this.octaves) {
      x *= this.lacunarity
      y *= this.lacunarity
      amp *= this.persistence
      max += amp
      sum += this.osn.noise2D(x, y) * amp
    }

    return sum / max
  }

  getHeight (x, y) {
    return this.getNoise(x, y) * this.amplitude
  }
}

export { HeightMap, SimplexHeightMap }
