import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.112.1/build/three.module.js';

import {math} from './math.js';
import {spline} from './spline.js';


export const texture_splatter = (function() {

  const _WHITE = new THREE.Color(0x808080);

  const _DEEP_OCEAN = new THREE.Color(0x20020FF);
  const _SHALLOW_OCEAN = new THREE.Color(0x8080FF);
  const _BEACH = new THREE.Color(0xd9d592);
  const _SNOW = new THREE.Color(0xFFFFFF);
  const _ApplyWeightsOREST_TROPICAL = new THREE.Color(0x4f9f0f);
  const _ApplyWeightsOREST_TEMPERATE = new THREE.Color(0x2b960e);
  const _ApplyWeightsOREST_BOREAL = new THREE.Color(0x29c100);
  
  const _GREEN = new THREE.Color(0x80FF80);
  const _RED = new THREE.Color(0xFF8080);
  const _BLACK = new THREE.Color(0x000000);


  class FixedHeightGenerator {
    constructor() {}
  
    Get() {
      return [50, 1];
    }
  }
  

  class FixedColourGenerator {
    constructor(params) {
      this._params = params;
    }
  
    Get() {
      return this._params.colour;
    }
  }


  class HeightGenerator {
    constructor(generator, position, minRadius, maxRadius) {
      this._position = position.clone();
      this._radius = [minRadius, maxRadius];
      this._generator = generator;
    }

    Get(x, y, z) {
      return [this._generator.Get(x, y, z), 1];
    }
  }


  class TextureSplatter {
    constructor(params) {
      const _colourLerp = (t, p0, p1) => {
        const c = p0.clone();

        return c.lerp(p1, t);
      };
      this._colourSpline = [
        new spline.LinearSpline(_colourLerp),
        new spline.LinearSpline(_colourLerp)
      ];

      // Arid
      this._colourSpline[0].AddPoint(0.0, new THREE.Color(0xb7a67d));
      this._colourSpline[0].AddPoint(0.5, new THREE.Color(0xf1e1bc));
      this._colourSpline[0].AddPoint(1.0, _SNOW);

      // Humid
      this._colourSpline[1].AddPoint(0.0, _ApplyWeightsOREST_BOREAL);
      this._colourSpline[1].AddPoint(0.5, new THREE.Color(0xcee59c));
      this._colourSpline[1].AddPoint(1.0, _SNOW);

      this._oceanSpline = new spline.LinearSpline(_colourLerp);
      this._oceanSpline.AddPoint(0, _DEEP_OCEAN);
      this._oceanSpline.AddPoint(0.03, _SHALLOW_OCEAN);
      this._oceanSpline.AddPoint(0.05, _SHALLOW_OCEAN);

      this._params = params;
    }

    _BaseColour(x, y, z) {
      const m = this._params.biomeGenerator.Get(x, y, z);
      const h = math.sat(z / 100.0);

      const c1 = this._colourSpline[0].Get(h);
      const c2 = this._colourSpline[1].Get(h);

      let c = c1.lerp(c2, m);

      if (h < 0.1) {
        c = c.lerp(new THREE.Color(0x54380e), 1.0 - math.sat(h / 0.05));
      }
      return c;      
    }

    _Colour(x, y, z) {
      const c = this._BaseColour(x, y, z);
      const r = this._params.colourNoise.Get(x, y, z) * 2.0 - 1.0;

      c.offsetHSL(0.0, 0.0, r * 0.01);
      return c;
    }

    _GetTextureWeights(p, n, up) {
      const m = this._params.biomeGenerator.Get(p.x, p.y, p.z);
      const h = p.z / 100.0;

      const types = {
        dirt: {index: 0, strength: 0.0},
        grass: {index: 1, strength: 0.0},
        gravel: {index: 2, strength: 0.0},
        rock: {index: 3, strength: 0.0},
        snow: {index: 4, strength: 0.0},
        snowrock: {index: 5, strength: 0.0},
        cobble: {index: 6, strength: 0.0},
        sandyrock: {index: 7, strength: 0.0},
      };

      function _ApplyWeights(dst, v, m) {
        for (let k in types) {
          types[k].strength *= m;
        }
        types[dst].strength = v;
      };

      types.grass.strength = 1.0;
      _ApplyWeights('gravel', 1.0 - m, m);

      if (h < 0.2) {
        const s = 1.0 - math.sat((h - 0.1) / 0.05);
        _ApplyWeights('cobble', s, 1.0 - s);

        if (h < 0.1) {
          const s = 1.0 - math.sat((h - 0.05) / 0.05);
          _ApplyWeights('sandyrock', s, 1.0 - s);
        }
      } else {
        if (h > 0.125) {
          const s = (math.sat((h - 0.125) / 1.25));
          _ApplyWeights('rock', s, 1.0 - s);
        }

        if (h > 1.5) {
          const s = math.sat((h - 0.75) / 2.0);
          _ApplyWeights('snow', s, 1.0 - s);
        }
      }

      // In case nothing gets set.
      types.dirt.strength = 0.01;

      let total = 0.0;
      for (let k in types) {
        total += types[k].strength;
      }
      if (total < 0.01) {
        const a = 0;
      }
      const normalization = 1.0 / total;

      for (let k in types) {
        types[k].strength / normalization;
      }

      return types;
    }

    GetColour(position) {
      return this._Colour(position.x, position.y, position.z);
    }

    GetSplat(position, normal, up) {
      return this._GetTextureWeights(position, normal, up);
    }
  }

  return {
    HeightGenerator: HeightGenerator,
    TextureSplatter: TextureSplatter,
  }
})();
