import {Vector2 as Vector22} from "three/src/math/Vector2";
import {Vector3 as Vector32} from "three/src/math/Vector3";
import {Vector4 as Vector42} from "three/src/math/Vector4";
import {TypedSopNode} from "./_Base";
import {CoreMath} from "../../../core/math/_Module";
import {InputCloneMode as InputCloneMode2} from "../../poly/InputCloneMode";
import {TypeAssert} from "../../poly/Assert";
import {SimplexNoise as SimplexNoise2} from "../../../modules/three/examples/jsm/math/SimplexNoise";
var Operation;
(function(Operation2) {
  Operation2["ADD"] = "add";
  Operation2["SET"] = "set";
  Operation2["MULT"] = "mult";
  Operation2["SUBSTRACT"] = "substract";
  Operation2["DIVIDE"] = "divide";
})(Operation || (Operation = {}));
const Operations = [Operation.ADD, Operation.SET, Operation.MULT, Operation.SUBSTRACT, Operation.DIVIDE];
const ATTRIB_NORMAL = "normal";
import {NodeParamsConfig, ParamConfig} from "../utils/params/ParamsConfig";
import {CoreType} from "../../../core/Type";
class NoiseSopParamsConfig extends NodeParamsConfig {
  constructor() {
    super(...arguments);
    this.amplitude = ParamConfig.FLOAT(1);
    this.tamplitudeAttrib = ParamConfig.BOOLEAN(0);
    this.amplitudeAttrib = ParamConfig.STRING("amp", {visibleIf: {tamplitudeAttrib: true}});
    this.freq = ParamConfig.VECTOR3([1, 1, 1]);
    this.offset = ParamConfig.VECTOR3([0, 0, 0]);
    this.octaves = ParamConfig.INTEGER(3, {
      range: [1, 8],
      rangeLocked: [true, false]
    });
    this.ampAttenuation = ParamConfig.FLOAT(0.5, {range: [0, 1]});
    this.freqIncrease = ParamConfig.FLOAT(2, {range: [0, 10]});
    this.seed = ParamConfig.INTEGER(0, {range: [0, 100]});
    this.separator = ParamConfig.SEPARATOR();
    this.useNormals = ParamConfig.BOOLEAN(0);
    this.attribName = ParamConfig.STRING("position");
    this.useRestAttributes = ParamConfig.BOOLEAN(0);
    this.restP = ParamConfig.STRING("restP", {visibleIf: {useRestAttributes: true}});
    this.restN = ParamConfig.STRING("restN", {visibleIf: {useRestAttributes: true}});
    this.operation = ParamConfig.INTEGER(Operations.indexOf(Operation.ADD), {
      menu: {
        entries: Operations.map((operation) => {
          return {
            name: operation,
            value: Operations.indexOf(operation)
          };
        })
      }
    });
    this.computeNormals = ParamConfig.BOOLEAN(1);
  }
}
const ParamsConfig2 = new NoiseSopParamsConfig();
export class NoiseSopNode extends TypedSopNode {
  constructor() {
    super(...arguments);
    this.params_config = ParamsConfig2;
    this._simplex_by_seed = new Map();
    this._rest_pos = new Vector32();
    this._rest_value2 = new Vector22();
    this._noise_value_v = new Vector32();
  }
  static type() {
    return "noise";
  }
  static displayedInputNames() {
    return ["geometry to add noise to"];
  }
  initializeNode() {
    this.io.inputs.setCount(1);
    this.io.inputs.initInputsClonedState([InputCloneMode2.FROM_NODE]);
  }
  async cook(input_contents) {
    const core_group = input_contents[0];
    const dest_points = core_group.points();
    const simplex = this._get_simplex();
    const useNormals = this.pv.useNormals && core_group.hasAttrib(ATTRIB_NORMAL);
    const target_attrib_size = core_group.attribSize(this.pv.attribName);
    const operation = Operations[this.pv.operation];
    const dest_attribName = this.pv.attribName;
    const useRestAttributes = this.pv.useRestAttributes;
    const base_amplitude = this.pv.amplitude;
    const use_amplitudeAttrib = this.pv.tamplitudeAttrib;
    let restP;
    let restN;
    let current_attrib_value;
    for (let i = 0; i < dest_points.length; i++) {
      const dest_point = dest_points[i];
      current_attrib_value = dest_point.attribValue(dest_attribName);
      if (useRestAttributes) {
        restP = dest_point.attribValue(this.pv.restP);
        restN = useNormals ? dest_point.attribValue(this.pv.restN) : void 0;
        current_attrib_value = restP;
      } else {
        restN = useNormals ? dest_point.attribValue("normal") : void 0;
      }
      const amplitude = use_amplitudeAttrib ? this._amplitude_from_attrib(dest_point, base_amplitude) : base_amplitude;
      const noise_result = this._noise_value(useNormals, simplex, amplitude, current_attrib_value, restN);
      const noise_value = this._make_noise_value_correct_size(noise_result, target_attrib_size);
      if (CoreType.isNumber(current_attrib_value) && CoreType.isNumber(noise_value)) {
        const new_attrib_value_f = this._new_attrib_value_from_float(operation, current_attrib_value, noise_value);
        dest_point.setAttribValue(dest_attribName, new_attrib_value_f);
      } else {
        if (current_attrib_value instanceof Vector22 && noise_value instanceof Vector22) {
          const new_attrib_value_v = this._new_attrib_value_from_vector2(operation, current_attrib_value, noise_value);
          dest_point.setAttribValue(dest_attribName, new_attrib_value_v);
        } else {
          if (current_attrib_value instanceof Vector32 && noise_value instanceof Vector32) {
            const new_attrib_value_v = this._new_attrib_value_from_vector3(operation, current_attrib_value, noise_value);
            dest_point.setAttribValue(dest_attribName, new_attrib_value_v);
          } else {
            if (current_attrib_value instanceof Vector42 && noise_value instanceof Vector42) {
              const new_attrib_value_v = this._new_attrib_value_from_vector4(operation, current_attrib_value, noise_value);
              dest_point.setAttribValue(dest_attribName, new_attrib_value_v);
            }
          }
        }
      }
    }
    if (!this.io.inputs.clone_required(0)) {
      for (let geometry of core_group.geometries()) {
        geometry.getAttribute(dest_attribName).needsUpdate = true;
      }
    }
    if (this.pv.computeNormals) {
      core_group.computeVertexNormals();
    }
    this.setCoreGroup(core_group);
  }
  _noise_value(useNormals, simplex, amplitude, restP, restN) {
    this._rest_pos.copy(restP).add(this.pv.offset).multiply(this.pv.freq);
    if (useNormals && restN) {
      const noise = amplitude * this._fbm(simplex, this._rest_pos.x, this._rest_pos.y, this._rest_pos.z);
      this._noise_value_v.copy(restN);
      return this._noise_value_v.multiplyScalar(noise);
    } else {
      this._noise_value_v.set(amplitude * this._fbm(simplex, this._rest_pos.x + 545, this._rest_pos.y + 125454, this._rest_pos.z + 2142), amplitude * this._fbm(simplex, this._rest_pos.x - 425, this._rest_pos.y - 25746, this._rest_pos.z + 95242), amplitude * this._fbm(simplex, this._rest_pos.x + 765132, this._rest_pos.y + 21, this._rest_pos.z - 9245));
      return this._noise_value_v;
    }
  }
  _make_noise_value_correct_size(noise_value, target_attrib_size) {
    switch (target_attrib_size) {
      case 1:
        return noise_value.x;
      case 2:
        this._rest_value2.set(noise_value.x, noise_value.y);
        return this._rest_value2;
      case 3:
        return noise_value;
      default:
        return noise_value;
    }
  }
  _new_attrib_value_from_float(operation, current_attrib_value, noise_value) {
    switch (operation) {
      case Operation.ADD:
        return current_attrib_value + noise_value;
      case Operation.SET:
        return noise_value;
      case Operation.MULT:
        return current_attrib_value * noise_value;
      case Operation.DIVIDE:
        return current_attrib_value / noise_value;
      case Operation.SUBSTRACT:
        return current_attrib_value - noise_value;
    }
    TypeAssert.unreachable(operation);
  }
  _new_attrib_value_from_vector2(operation, current_attrib_value, noise_value) {
    switch (operation) {
      case Operation.ADD:
        return current_attrib_value.add(noise_value);
      case Operation.SET:
        return noise_value;
      case Operation.MULT:
        return current_attrib_value.multiply(noise_value);
      case Operation.DIVIDE:
        return current_attrib_value.divide(noise_value);
      case Operation.SUBSTRACT:
        return current_attrib_value.sub(noise_value);
    }
    TypeAssert.unreachable(operation);
  }
  _new_attrib_value_from_vector3(operation, current_attrib_value, noise_value) {
    switch (operation) {
      case Operation.ADD:
        return current_attrib_value.add(noise_value);
      case Operation.SET:
        return noise_value;
      case Operation.MULT:
        return current_attrib_value.multiply(noise_value);
      case Operation.DIVIDE:
        return current_attrib_value.divide(noise_value);
      case Operation.SUBSTRACT:
        return current_attrib_value.sub(noise_value);
    }
    TypeAssert.unreachable(operation);
  }
  _new_attrib_value_from_vector4(operation, current_attrib_value, noise_value) {
    switch (operation) {
      case Operation.ADD:
        return current_attrib_value.add(noise_value);
      case Operation.SET:
        return noise_value;
      case Operation.MULT:
        return current_attrib_value.multiplyScalar(noise_value.x);
      case Operation.DIVIDE:
        return current_attrib_value.divideScalar(noise_value.x);
      case Operation.SUBSTRACT:
        return current_attrib_value.sub(noise_value);
    }
    TypeAssert.unreachable(operation);
  }
  _amplitude_from_attrib(point, base_amplitude) {
    const attrib_value = point.attribValue(this.pv.amplitudeAttrib);
    if (CoreType.isNumber(attrib_value)) {
      return attrib_value * base_amplitude;
    } else {
      if (attrib_value instanceof Vector22 || attrib_value instanceof Vector32 || attrib_value instanceof Vector42) {
        return attrib_value.x * base_amplitude;
      }
    }
    return 1;
  }
  _fbm(simplex, x, y, z) {
    let value = 0;
    let amplitude = 1;
    for (let i = 0; i < this.pv.octaves; i++) {
      value += amplitude * simplex.noise3d(x, y, z);
      x *= this.pv.freqIncrease;
      y *= this.pv.freqIncrease;
      z *= this.pv.freqIncrease;
      amplitude *= this.pv.ampAttenuation;
    }
    return value;
  }
  _get_simplex() {
    const simplex = this._simplex_by_seed.get(this.pv.seed);
    if (simplex) {
      return simplex;
    } else {
      const simplex2 = this._create_simplex();
      this._simplex_by_seed.set(this.pv.seed, simplex2);
      return simplex2;
    }
  }
  _create_simplex() {
    const seed = this.pv.seed;
    const random_generator = {
      random: function() {
        return CoreMath.rand_float(seed);
      }
    };
    const simplex = new SimplexNoise2(random_generator);
    this._simplex_by_seed.delete(seed);
    return simplex;
  }
}
