import {Vector3 as Vector32} from "three/src/math/Vector3";
import {Vector2 as Vector22} from "three/src/math/Vector2";
import {Quaternion as Quaternion2} from "three/src/math/Quaternion";
import {Matrix4 as Matrix42} from "three/src/math/Matrix4";
import {InstancedBufferGeometry as InstancedBufferGeometry2} from "three/src/core/InstancedBufferGeometry";
import {InstancedBufferAttribute as InstancedBufferAttribute2} from "three/src/core/InstancedBufferAttribute";
import {CoreGeometry} from "./Geometry";
import {CoreType} from "../Type";
const DEFAULT = {
  SCALE: new Vector32(1, 1, 1),
  PSCALE: 1,
  EYE: new Vector32(0, 0, 0),
  UP: new Vector32(0, 1, 0)
};
const SCALE_ATTRIB_NAME = "scale";
const PSCALE_ATTRIB_NAME = "pscale";
const NORMAL_ATTRIB_NAME = "normal";
const UP_ATTRIB_NAME = "up";
const MATRIX_T = "translate";
const MATRIX_R = "rotate";
const MATRIX_S = "scale";
const DEFAULT_COLOR = new Vector32(1, 1, 1);
const DEFAULT_UV = new Vector22(0, 0);
const ATTRIB_NAME_UV = "uv";
const ATTRIB_NAME_COLOR = "color";
const CoreInstancer2 = class {
  constructor(_group_wrapper) {
    this._group_wrapper = _group_wrapper;
    this._matrices = {};
    this._point_scale = new Vector32();
    this._point_normal = new Vector32();
    this._point_up = new Vector32();
    this._is_pscale_present = this._group_wrapper.hasAttrib("pscale");
    this._is_scale_present = this._group_wrapper.hasAttrib("scale");
    this._is_normal_present = this._group_wrapper.hasAttrib("normal");
    this._is_up_present = this._group_wrapper.hasAttrib("up");
    this._do_rotate_matrices = this._is_normal_present;
  }
  matrices() {
    this._matrices = {};
    this._matrices[MATRIX_T] = new Matrix42();
    this._matrices[MATRIX_R] = new Matrix42();
    this._matrices[MATRIX_S] = new Matrix42();
    return this._group_wrapper.points().map((point) => {
      const matrix = new Matrix42();
      this._matrix_from_point(point, matrix);
      return matrix;
    });
  }
  _matrix_from_point(point, matrix) {
    const t = point.position();
    if (this._is_scale_present) {
      point.attribValue(SCALE_ATTRIB_NAME, this._point_scale);
    } else {
      this._point_scale.copy(DEFAULT.SCALE);
    }
    const pscale = this._is_pscale_present ? point.attribValue(PSCALE_ATTRIB_NAME) : DEFAULT.PSCALE;
    this._point_scale.multiplyScalar(pscale);
    const scale_matrix = this._matrices[MATRIX_S];
    scale_matrix.makeScale(this._point_scale.x, this._point_scale.y, this._point_scale.z);
    const translate_matrix = this._matrices[MATRIX_T];
    translate_matrix.makeTranslation(t.x, t.y, t.z);
    matrix.multiply(translate_matrix);
    if (this._do_rotate_matrices) {
      const rotate_matrix = this._matrices[MATRIX_R];
      const eye = DEFAULT.EYE;
      point.attribValue(NORMAL_ATTRIB_NAME, this._point_normal);
      this._point_normal.multiplyScalar(-1);
      if (this._is_up_present) {
        point.attribValue(UP_ATTRIB_NAME, this._point_up);
      } else {
        this._point_up.copy(DEFAULT.UP);
      }
      this._point_up.normalize();
      rotate_matrix.lookAt(eye, this._point_normal, this._point_up);
      matrix.multiply(rotate_matrix);
    }
    matrix.multiply(scale_matrix);
  }
  static create_instance_buffer_geo(geometry_to_instance, template_core_group, attributes_to_copy) {
    const instance_pts = template_core_group.points();
    const geometry = new InstancedBufferGeometry2();
    geometry.copy(geometry_to_instance);
    geometry.instanceCount = Infinity;
    const instances_count = instance_pts.length;
    const positions = new Float32Array(instances_count * 3);
    const colors = new Float32Array(instances_count * 3);
    const scales = new Float32Array(instances_count * 3);
    const orients = new Float32Array(instances_count * 4);
    const has_color = template_core_group.hasAttrib(ATTRIB_NAME_COLOR);
    const position = new Vector32(0, 0, 0);
    const quaternion = new Quaternion2();
    const scale = new Vector32(1, 1, 1);
    const instancer = new CoreInstancer2(template_core_group);
    const instance_matrices = instancer.matrices();
    instance_pts.forEach((instance_pt, i) => {
      const index3 = i * 3;
      const index4 = i * 4;
      const matrix = instance_matrices[i];
      matrix.decompose(position, quaternion, scale);
      position.toArray(positions, index3);
      quaternion.toArray(orients, index4);
      scale.toArray(scales, index3);
      const color = has_color ? instance_pt.attribValue(ATTRIB_NAME_COLOR, this._point_color) : DEFAULT_COLOR;
      color.toArray(colors, index3);
    });
    const has_uv = template_core_group.hasAttrib(ATTRIB_NAME_UV);
    if (has_uv) {
      const uvs = new Float32Array(instances_count * 2);
      instance_pts.forEach((instance_pt, i) => {
        const index2 = i * 2;
        const uv = has_uv ? instance_pt.attribValue(ATTRIB_NAME_UV, this._point_uv) : DEFAULT_UV;
        uv.toArray(uvs, index2);
      });
      geometry.setAttribute("instanceUv", new InstancedBufferAttribute2(uvs, 2));
    }
    geometry.setAttribute("instancePosition", new InstancedBufferAttribute2(positions, 3));
    geometry.setAttribute("instanceScale", new InstancedBufferAttribute2(scales, 3));
    geometry.setAttribute("instanceOrientation", new InstancedBufferAttribute2(orients, 4));
    geometry.setAttribute("instanceColor", new InstancedBufferAttribute2(colors, 3));
    const attrib_names = template_core_group.attribNamesMatchingMask(attributes_to_copy);
    attrib_names.forEach((attrib_name) => {
      const attrib_size = template_core_group.attribSize(attrib_name);
      const values = new Float32Array(instances_count * attrib_size);
      instance_pts.forEach((pt, i) => {
        const value = pt.attribValue(attrib_name);
        if (CoreType.isNumber(value)) {
          values[i] = value;
        } else {
          value.toArray(values, i * attrib_size);
        }
      });
      geometry.setAttribute(attrib_name, new InstancedBufferAttribute2(values, attrib_size));
    });
    const core_geometry = new CoreGeometry(geometry);
    core_geometry.markAsInstance();
    return geometry;
  }
};
export let CoreInstancer = CoreInstancer2;
CoreInstancer._point_color = new Vector32();
CoreInstancer._point_uv = new Vector22();
