import * as THREE from 'three';
/* babel-plugin-inline-import './Shader/PointsVS.glsl' */
const PointsVS = "#include <common>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvarying vec4 vColor; // color_pars_vertex\n\n#ifdef USE_POINTS_UV\n    varying vec2 vUv;\n    uniform mat3 uvTransform;\n#endif\n\n#define NB_CLASS 8.\n\nuniform float size;\nuniform float scale;\n\nuniform bool picking;\nuniform int mode;\n\nuniform vec2 elevationRange;\nuniform vec2 intensityRange;\nuniform vec2 angleRange;\n\nuniform sampler2D classificationTexture;\nuniform sampler2D discreteTexture;\nuniform sampler2D gradientTexture;\nuniform int sizeMode;\nuniform float minAttenuatedSize;\nuniform float maxAttenuatedSize;\n\nattribute vec4 unique_id;\nattribute float intensity;\nattribute float classification;\nattribute float pointSourceID;\n\nattribute float returnNumber;\nattribute float numberOfReturns;\nattribute float scanAngle;\n\nvoid main() {\n    vColor = vec4(1.0);\n    if (picking) {\n        vColor = unique_id;\n    } else {\n        if (mode == PNTS_MODE_CLASSIFICATION) {\n            vec2 uv = vec2(classification/255., 0.5);\n            vColor = texture2D(classificationTexture, uv);\n        } else if (mode == PNTS_MODE_NORMAL) {\n            vColor.rgb = abs(normal);\n        } else if (mode == PNTS_MODE_COLOR) {\n#if defined(USE_COLOR)\n            vColor.rgb = color.rgb;\n#elif defined(USE_COLOR_ALPHA)\n            vColor = color;\n#endif\n        } else if (mode == PNTS_MODE_RETURN_NUMBER) {\n            vec2 uv = vec2(returnNumber/255., 0.5);\n            vColor = texture2D(discreteTexture, uv);\n        } else if (mode == PNTS_MODE_RETURN_TYPE) {\n            float returnType;\n            if (returnNumber > numberOfReturns) {\n                returnType = 4.;\n            } else if (returnNumber == 1.) {\n                if (numberOfReturns == 1.) {\n                    // single\n                    returnType = 0.;\n                } else {\n                    // first\n                    returnType = 1.;\n                }\n            } else {\n                if (returnNumber == numberOfReturns) {\n                    // last\n                    returnType = 3.;\n                } else {\n                    // intermediate\n                    returnType = 2.;\n                }\n            }\n            vec2 uv = vec2(returnType/255., 0.5);\n            vColor = texture2D(discreteTexture, uv);\n        } else if (mode == PNTS_MODE_RETURN_COUNT) {\n            vec2 uv = vec2(numberOfReturns/255., 0.5);\n            vColor = texture2D(discreteTexture, uv);\n        } else if (mode == PNTS_MODE_POINT_SOURCE_ID) {\n            vec2 uv = vec2(mod(pointSourceID, NB_CLASS)/255., 0.5);\n            vColor = texture2D(discreteTexture, uv);\n        } else if (mode == PNTS_MODE_SCAN_ANGLE) {\n            float i = (scanAngle - angleRange.x) / (angleRange.y - angleRange.x);\n            vec2 uv = vec2(i, (1. - i));\n            vColor = texture2D(gradientTexture, uv);\n        } else if (mode == PNTS_MODE_INTENSITY) {\n            float i = (intensity - intensityRange.x) / (intensityRange.y - intensityRange.x);\n            vec2 uv = vec2(i, (1. - i));\n            vColor = texture2D(gradientTexture, uv);\n        } else if (mode == PNTS_MODE_ELEVATION) {\n            float z = (modelMatrix * vec4(position, 1.0)).z;\n            float i = (z - elevationRange.x) / (elevationRange.y - elevationRange.x);\n            vec2 uv = vec2(i, (1. - i));\n            vColor = texture2D(gradientTexture, uv);\n        }\n    }\n\n#define USE_COLOR_ALPHA\n#include <morphcolor_vertex>\n#include <begin_vertex>\n#include <morphtarget_vertex>\n#include <project_vertex>\n\n    gl_PointSize = size;\n\n    if (sizeMode == PNTS_SIZE_MODE_ATTENUATED) {\n        bool isPerspective = isPerspectiveMatrix(projectionMatrix);\n\n        if (isPerspective) {\n            gl_PointSize *= scale / -mvPosition.z;\n            gl_PointSize = clamp(gl_PointSize, minAttenuatedSize, maxAttenuatedSize);\n        }\n    }\n\n#include <logdepthbuf_vertex>\n#include <clipping_planes_vertex>\n#include <worldpos_vertex>\n#include <fog_vertex>\n}\n";
/* babel-plugin-inline-import './Shader/PointsFS.glsl' */
const PointsFS = "#define USE_COLOR_ALPHA\n\n#include <color_pars_fragment>\n#include <map_particle_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\n\nuniform vec3 diffuse;\nuniform float opacity;\n\nuniform bool picking;\nuniform int shape;\n\nvoid main() {\n\n// Early discard (clipping planes and shape)\n#include <clipping_planes_pars_fragment>\n    if (shape == PNTS_SHAPE_CIRCLE) {\n        //circular rendering in glsl\n        if ((length(gl_PointCoord - 0.5) > 0.5) || (vColor.a == 0.0)) {\n            discard;\n        }\n    }\n\n#include <logdepthbuf_fragment>\n\n    vec4 diffuseColor = vec4(diffuse, opacity);\n#include <map_particle_fragment>\n#include <color_fragment>\n\n#include <alphatest_fragment>\n#include <alphahash_fragment>\n\n    vec3 outgoingLight = diffuseColor.rgb;\n#include <opaque_fragment> // gl_FragColor\n#include <tonemapping_fragment>\n#include <fog_fragment>\n#include <premultiplied_alpha_fragment>\n\n}\n";
import CommonMaterial from "./CommonMaterial.js";
import Gradients from "../Utils/Gradients.js";
export const PNTS_MODE = {
  COLOR: 0,
  INTENSITY: 1,
  CLASSIFICATION: 2,
  ELEVATION: 3,
  RETURN_NUMBER: 4,
  RETURN_TYPE: 5,
  RETURN_COUNT: 6,
  POINT_SOURCE_ID: 7,
  SCAN_ANGLE: 8,
  NORMAL: 9
};
export const PNTS_SHAPE = {
  CIRCLE: 0,
  SQUARE: 1
};
export const PNTS_SIZE_MODE = {
  VALUE: 0,
  ATTENUATED: 1
};
const white = new THREE.Color(1.0, 1.0, 1.0);

/**
 * Every lidar point can have a classification assigned to it that defines
 * the type of object that has reflected the laser pulse. Lidar points can be
 * classified into a number of categories including bare earth or ground,
 * top of canopy, and water. The different classes are defined using numeric
 * integer codes in the files.
 *
 * @property {object} category - category classification,
 * @property {boolean} category.visible - category visibility,
 * @property {string} category.name - category name,
 * @property {THREE.Color} category.color - category color,
 * @property {number} category.opacity - category opacity,
 */
// eslint-disable-next-line
class /* istanbul ignore next */Classification {}
export const ClassificationScheme = {
  DEFAULT: {
    0: {
      visible: true,
      name: 'never classified',
      color: new THREE.Color(0.5, 0.5, 0.5),
      opacity: 1.0
    },
    1: {
      visible: true,
      name: 'unclassified',
      color: new THREE.Color(0.5, 0.5, 0.5),
      opacity: 1.0
    },
    2: {
      visible: true,
      name: 'ground',
      color: new THREE.Color(0.63, 0.32, 0.18),
      opacity: 1.0
    },
    3: {
      visible: true,
      name: 'low vegetation',
      color: new THREE.Color(0.0, 1.0, 0.0),
      opacity: 1.0
    },
    4: {
      visible: true,
      name: 'medium vegetation',
      color: new THREE.Color(0.0, 0.8, 0.0),
      opacity: 1.0
    },
    5: {
      visible: true,
      name: 'high vegetation',
      color: new THREE.Color(0.0, 0.6, 0.0),
      opacity: 1.0
    },
    6: {
      visible: true,
      name: 'building',
      color: new THREE.Color(1.0, 0.66, 0.0),
      opacity: 1.0
    },
    7: {
      visible: true,
      name: 'low point(noise)',
      color: new THREE.Color(1.0, 0.0, 1.0),
      opacity: 1.0
    },
    8: {
      visible: true,
      name: 'key-point',
      color: new THREE.Color(1.0, 0.0, 0.0),
      opacity: 1.0
    },
    9: {
      visible: true,
      name: 'water',
      color: new THREE.Color(0.0, 0.0, 1.0),
      opacity: 1.0
    },
    10: {
      visible: true,
      name: 'rail',
      color: new THREE.Color(0.8, 0.8, 1.0),
      opacity: 1.0
    },
    11: {
      visible: true,
      name: 'road Surface',
      color: new THREE.Color(0.4, 0.4, 0.7),
      opacity: 1.0
    },
    12: {
      visible: true,
      name: 'overlap',
      color: new THREE.Color(1.0, 1.0, 0.0),
      opacity: 1.0
    },
    DEFAULT: {
      visible: true,
      name: 'default',
      color: new THREE.Color(0.3, 0.6, 0.6),
      opacity: 1.0
    }
  }
};
const DiscreteScheme = {
  DEFAULT: {
    0: {
      visible: true,
      name: '0',
      color: new THREE.Color('rgb(67, 99, 216)'),
      opacity: 1.0
    },
    1: {
      visible: true,
      name: '1',
      color: new THREE.Color('rgb(60, 180, 75);'),
      opacity: 1.0
    },
    2: {
      visible: true,
      name: '2',
      color: new THREE.Color('rgb(255, 255, 25)'),
      opacity: 1.0
    },
    3: {
      visible: true,
      name: '3',
      color: new THREE.Color('rgb(145, 30, 180)'),
      opacity: 1.0
    },
    4: {
      visible: true,
      name: '4',
      color: new THREE.Color('rgb(245, 130, 49)'),
      opacity: 1.0
    },
    5: {
      visible: true,
      name: '5',
      color: new THREE.Color('rgb(230, 25, 75)'),
      opacity: 1.0
    },
    6: {
      visible: true,
      name: '6',
      color: new THREE.Color('rgb(66, 212, 244)'),
      opacity: 1.0
    },
    7: {
      visible: true,
      name: '7',
      color: new THREE.Color('rgb(240, 50, 230)'),
      opacity: 1.0
    },
    DEFAULT: {
      visible: true,
      name: 'default',
      color: white,
      opacity: 1.0
    }
  }
};

// Taken from Potree. Copyright (c) 2011-2020, Markus Schütz All rights reserved.
// https://github.com/potree/potree/blob/develop/src/materials/PointCloudMaterial.js
function generateGradientTexture(gradient) {
  const size = 64;

  // create canvas
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;

  // get context
  const context = canvas.getContext('2d');

  // draw gradient
  context.rect(0, 0, size, size);
  const ctxGradient = context.createLinearGradient(0, 0, size, size);
  for (let i = 0; i < gradient.length; i++) {
    const step = gradient[i];
    ctxGradient.addColorStop(step[0], `#${step[1].getHexString()}`);
  }
  context.fillStyle = ctxGradient;
  context.fill();
  const texture = new THREE.CanvasTexture(canvas);
  texture.needsUpdate = true;
  texture.minFilter = THREE.LinearFilter;
  texture.wrap = THREE.RepeatWrapping;
  texture.repeat = 2;
  return texture;
}
function recomputeTexture(scheme, texture, nbClass) {
  let needTransparency;
  const data = texture.image.data;
  const width = texture.image.width;
  if (!nbClass) {
    nbClass = Object.keys(scheme).length;
  }
  for (let i = 0; i < width; i++) {
    let color;
    let opacity;
    let visible = true;
    if (scheme[i]) {
      color = scheme[i].color;
      visible = scheme[i].visible;
      opacity = scheme[i].opacity;
    } else if (scheme[i % nbClass]) {
      color = scheme[i % nbClass].color;
      visible = scheme[i % nbClass].visible;
      opacity = scheme[i % nbClass].opacity;
    } else if (scheme.DEFAULT) {
      color = scheme.DEFAULT.color;
      visible = scheme.DEFAULT.visible;
      opacity = scheme.DEFAULT.opacity;
    } else {
      color = white;
      opacity = 1.0;
    }
    const j = 4 * i;
    data[j + 0] = parseInt(255 * color.r, 10);
    data[j + 1] = parseInt(255 * color.g, 10);
    data[j + 2] = parseInt(255 * color.b, 10);
    data[j + 3] = visible ? parseInt(255 * opacity, 10) : 0;
    needTransparency = needTransparency || opacity < 1;
  }
  texture.needsUpdate = true;
  return needTransparency;
}
class PointsMaterial extends THREE.ShaderMaterial {
  /**
   * @class      PointsMaterial
   * @param      {object}  [options={}]  The options
   * @param      {number}  [options.size=0]  size point
   * @param      {number}  [options.mode=PNTS_MODE.COLOR]  display mode.
   * @param      {number}  [options.mode=PNTS_SHAPE.CIRCLE]  rendered points shape.
   * @param      {THREE.Vector4}  [options.overlayColor=new THREE.Vector4(0, 0, 0, 0)]  overlay color.
   * @param      {THREE.Vector2}  [options.intensityRange=new THREE.Vector2(1, 65536)]  intensity range.
   * @param      {THREE.Vector2}  [options.elevationRange=new THREE.Vector2(0, 1000)]  elevation range.
   * @param      {THREE.Vector2}  [options.angleRange=new THREE.Vector2(-90, 90)]  scan angle range.
   * @param      {Scheme}  [options.classificationScheme]  LUT for point classification colorization.
   * @param      {Scheme}  [options.discreteScheme]  LUT for other discret point values colorization.
   * @param      {string}  [options.gradient]  Descrition of the gradient to use for continuous point values.
   *                          (Default value will be the 'SPECTRAL' gradient from Utils/Gradients)
   * @param      {number}  [options.sizeMode=PNTS_SIZE_MODE.VALUE]  point cloud size mode. Only 'VALUE' or 'ATTENUATED' are possible. VALUE use constant size, ATTENUATED compute size depending on distance from point to camera.
   * @param      {number}  [options.minAttenuatedSize=3]  minimum scale used by 'ATTENUATED' size mode
   * @param      {number}  [options.maxAttenuatedSize=10]  maximum scale used by 'ATTENUATED' size mode
   *
   * @property {Scheme}  classificationScheme - Color scheme for point classification values.
   * @property {Scheme}  discreteScheme - Color scheme for all other discrete values.
   * @property {object}  gradients - Descriptions of all available gradients.
   * @property {object}  gradient - Description of the gradient to use for display.
   * @property {THREE.CanvasTexture}  gradientTexture - The texture generate from the choosen gradient.
   *
   * @example
   * // change color category classification
   * const pointMaterial = new PointsMaterial();
   * pointMaterial.classification[3].color.setStyle('red');
   * pointMaterial.recomputeClassification();
   */
  constructor() {
    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    const gradients = {
      ...options.gradient,
      ...Gradients
    };
    options.gradient = Object.values(gradients)[0];
    const {
      intensityRange = new THREE.Vector2(1, 65536),
      elevationRange = new THREE.Vector2(0, 1000),
      angleRange = new THREE.Vector2(-90, 90),
      classificationScheme = ClassificationScheme.DEFAULT,
      discreteScheme = DiscreteScheme.DEFAULT,
      size = 1,
      mode = PNTS_MODE.COLOR,
      shape = PNTS_SHAPE.CIRCLE,
      sizeMode = PNTS_SIZE_MODE.ATTENUATED,
      minAttenuatedSize = 3,
      maxAttenuatedSize = 10,
      gradient,
      scale = 0.05 * 0.5 / Math.tan(1.0 / 2.0),
      ...materialOptions
    } = options;
    super({
      ...materialOptions,
      fog: true,
      precision: 'highp',
      vertexColors: true
    });
    this.uniforms = THREE.UniformsUtils.merge([
    // THREE.PointsMaterial uniforms
    THREE.UniformsLib.points, THREE.UniformsLib.fog]);
    this.vertexShader = PointsVS;
    this.fragmentShader = PointsFS;
    this.userData.needTransparency = {};
    this.gradients = gradients;
    this.gradientTexture = new THREE.CanvasTexture();
    CommonMaterial.setDefineMapping(this, 'PNTS_MODE', PNTS_MODE);
    CommonMaterial.setDefineMapping(this, 'PNTS_SHAPE', PNTS_SHAPE);
    CommonMaterial.setDefineMapping(this, 'PNTS_SIZE_MODE', PNTS_SIZE_MODE);
    this.size = size;
    CommonMaterial.setUniformProperty(this, 'mode', mode);
    CommonMaterial.setUniformProperty(this, 'shape', shape);
    CommonMaterial.setUniformProperty(this, 'picking', false);
    CommonMaterial.setUniformProperty(this, 'opacity', this.opacity);
    CommonMaterial.setUniformProperty(this, 'intensityRange', intensityRange);
    CommonMaterial.setUniformProperty(this, 'elevationRange', elevationRange);
    CommonMaterial.setUniformProperty(this, 'angleRange', angleRange);
    CommonMaterial.setUniformProperty(this, 'sizeMode', sizeMode);
    CommonMaterial.setUniformProperty(this, 'scale', scale);
    CommonMaterial.setUniformProperty(this, 'minAttenuatedSize', minAttenuatedSize);
    CommonMaterial.setUniformProperty(this, 'maxAttenuatedSize', maxAttenuatedSize);

    // add classification texture to apply classification lut.
    const data = new Uint8Array(256 * 4);
    const texture = new THREE.DataTexture(data, 256, 1, THREE.RGBAFormat);
    texture.needsUpdate = true;
    texture.magFilter = THREE.NearestFilter;
    CommonMaterial.setUniformProperty(this, 'classificationTexture', texture);

    // add texture to applying the discrete lut.
    const dataLUT = new Uint8Array(256 * 4);
    const textureLUT = new THREE.DataTexture(dataLUT, 256, 1, THREE.RGBAFormat);
    textureLUT.needsUpdate = true;
    textureLUT.magFilter = THREE.NearestFilter;
    CommonMaterial.setUniformProperty(this, 'discreteTexture', textureLUT);

    // Classification and other discrete values scheme
    this.classificationScheme = classificationScheme;
    this.discreteScheme = discreteScheme;

    // Update classification and discrete Texture
    this.recomputeClassification();
    this.recomputeDiscreteTexture();

    // Gradient texture for continuous values
    this.gradient = gradient;
    CommonMaterial.setUniformProperty(this, 'gradientTexture', this.gradientTexture);
  }

  /**
   * Copy the parameters from the passed material into this material.
   * @override
   * @param {THREE.PointsMaterial} source
   * @returns {this}
   */
  copy(source) {
    if (source.isShaderMaterial) {
      super.copy(source);
    } else {
      THREE.Material.prototype.copy.call(this, source);
    }

    // Parameters of THREE.PointsMaterial
    this.color.copy(source.color);
    this.map = source.map;
    this.alphaMap = source.alphaMap;
    this.size = source.size;
    this.sizeAttenuation = source.sizeAttenuation;
    this.fog = source.fog;
    return this;
  }

  /** @returns {THREE.Color} */
  get color() {
    return this.uniforms.diffuse.value;
  }

  /** @param {THREE.Color} color */
  set color(color) {
    this.uniforms.diffuse.value.copy(color);
  }

  /** @returns {THREE.Texture | null} */
  get map() {
    return this.uniforms.map.value;
  }

  /** @param {THREE.Texture | null} map */
  set map(map) {
    this.uniforms.map.value = map;
    if (!map) {
      return;
    }
    if (map.matrixAutoUpdate) {
      map.updateMatrix();
    }
    this.uniforms.uvTransform.value.copy(map.matrix);
  }

  /** @returns {THREE.Texture | null} */
  get alphaMap() {
    return this.uniforms.alphaMap.value;
  }

  /** @param {THREE.Texture | null} map */
  set alphaMap(map) {
    this.uniforms.alphaMap.value = map;
    if (!map) {
      return;
    }
    if (map.matrixAutoUpdate) {
      map.updateMatrix();
    }
    this.uniforms.alphaMapTransform.value.copy(map.matrix);
  }

  /** @returns {number} */
  get size() {
    return this.uniforms.size.value;
  }

  /** @param {number} size */
  set size(size) {
    this.uniforms.size.value = size;
  }

  /** @returns {boolean} */
  get sizeAttenuation() {
    return this.sizeMode !== PNTS_SIZE_MODE.VALUE;
  }

  /** @param {boolean} value */
  set sizeAttenuation(value) {
    this.sizeMode = value ? PNTS_SIZE_MODE.ATTENUATED : PNTS_SIZE_MODE.VALUE;
  }
  recomputeClassification() {
    const needTransparency = recomputeTexture(this.classificationScheme, this.classificationTexture, 256);
    this.userData.needTransparency[PNTS_MODE.CLASSIFICATION] = needTransparency;
    this.dispatchEvent({
      type: 'material_property_changed',
      target: this.uniforms
    });
  }
  recomputeDiscreteTexture() {
    const needTransparency = recomputeTexture(this.discreteScheme, this.discreteTexture);
    this.userData.needTransparency[PNTS_MODE.RETURN_NUMBER] = needTransparency;
    this.userData.needTransparency[PNTS_MODE.RETURN_TYPE] = needTransparency;
    this.userData.needTransparency[PNTS_MODE.RETURN_COUNT] = needTransparency;
    this.userData.needTransparency[PNTS_MODE.POINT_SOURCE_ID] = needTransparency;
    this.dispatchEvent({
      type: 'material_property_changed',
      target: this.uniforms
    });
  }
  enablePicking(picking) {
    this.picking = picking;
    this.blending = picking ? THREE.NoBlending : THREE.NormalBlending;
  }
  set gradient(value) {
    this.gradientTexture = generateGradientTexture(value);
  }
}
export default PointsMaterial;