"use strict"

import * as THREE from '../node_modules/three/src/Three';
import { CSG, Vertex, Polygon } from "./csg-lib.js"


CSG.fromGeometry = function (geom, objectIndex) {
  if (!geom.isBufferGeometry) {
    console.error("Unsupported CSG input type:" + geom.type)
    return
  }
  let polys = []

  let posattr = geom.attributes.position
  let normalattr = geom.attributes.normal
  let uvattr = geom.attributes.uv
  let colorattr = geom.attributes.color
  let index;
  if (geom.index)
    index = geom.index.array;
  else {
    index = new Array((posattr.array.length / posattr.itemSize) | 0);
    for (let i = 0; i < index.length; i++)
      index[i] = i
  }
  let triCount = (index.length / 3) | 0
  polys = new Array(triCount)
  for (let i = 0, pli = 0, l = index.length; i < l; i += 3,
    pli++) {
    let vertices = new Array(3)
    for (let j = 0; j < 3; j++) {
      let vi = index[i + j]
      let vp = vi * 3;
      let vt = vi * 2;
      let x = posattr.array[vp]
      let y = posattr.array[vp + 1]
      let z = posattr.array[vp + 2]
      let nx = normalattr.array[vp]
      let ny = normalattr.array[vp + 1]
      let nz = normalattr.array[vp + 2]
      let u = uvattr.array[vt]
      let v = uvattr.array[vt + 1]
      vertices[j] = new Vertex({
        x,
        y,
        z
      }, {
        x: nx,
        y: ny,
        z: nz
      }, {
        x: u,
        y: v,
        z: 0
      }, colorattr && { x: colorattr.array[vt], y: colorattr.array[vt + 1], z: colorattr.array[vt + 2] });
    }
    polys[pli] = new Polygon(vertices, objectIndex)
  }

  return CSG.fromPolygons(polys)
}

let ttvv0 = new THREE.Vector3()
let tmpm3 = new THREE.Matrix3();
CSG.fromMesh = function (mesh, objectIndex) {
  let csg = CSG.fromGeometry(mesh.geometry, objectIndex)
  tmpm3.getNormalMatrix(mesh.matrix);
  for (let i = 0; i < csg.polygons.length; i++) {
    let p = csg.polygons[i]
    for (let j = 0; j < p.vertices.length; j++) {
      let v = p.vertices[j]
      v.pos.copy(ttvv0.copy(v.pos).applyMatrix4(mesh.matrix));
      v.normal.copy(ttvv0.copy(v.normal).applyMatrix3(tmpm3))
    }
  }
  return csg;
}

let nbuf3 = (ct) => {
  return {
    top: 0,
    array: new Float32Array(ct),
    write: function (v) { (this.array[this.top++] = v.x); (this.array[this.top++] = v.y); (this.array[this.top++] = v.z); }
  }
}
let nbuf2 = (ct) => {
  return {
    top: 0,
    array: new Float32Array(ct),
    write: function (v) { (this.array[this.top++] = v.x); (this.array[this.top++] = v.y) }
  }
}

CSG.toMesh = function (csg, toMatrix, toMaterial) {

  let ps = csg.polygons;
  let geom;
  let g2;
  if (0) //Old geometry path...
  {
    geom = new Geometry();
    let vs = geom.vertices;
    let fvuv = geom.faceVertexUvs[0]
    for (let i = 0; i < ps.length; i++) {
      let p = ps[i]
      let pvs = p.vertices;
      let v0 = vs.length;
      let pvlen = pvs.length

      for (let j = 0; j < pvlen; j++)
        vs.push(new THREE.Vector3().copy(pvs[j].pos))

      for (let j = 3; j <= pvlen; j++) {
        let fc = new THREE.Face3();
        let fuv = []
        fvuv.push(fuv)
        let fnml = fc.vertexNormals;
        fc.a = v0;
        fc.b = v0 + j - 2;
        fc.c = v0 + j - 1;

        fnml.push(new THREE.Vector3().copy(pvs[0].normal))
        fnml.push(new THREE.Vector3().copy(pvs[j - 2].normal))
        fnml.push(new THREE.Vector3().copy(pvs[j - 1].normal))
        fuv.push(new THREE.Vector3().copy(pvs[0].uv))
        fuv.push(new THREE.Vector3().copy(pvs[j - 2].uv))
        fuv.push(new THREE.Vector3().copy(pvs[j - 1].uv))

        fc.normal = new THREE.Vector3().copy(p.plane.normal)
        geom.faces.push(fc)
      }
    }
    geom = new THREE.BufferGeometry().fromGeometry(geom)
    geom.verticesNeedUpdate = geom.elementsNeedUpdate = geom.normalsNeedUpdate = true;
  }
  if (1) { //BufferGeometry path
    let triCount = 0;
    ps.forEach(p => triCount += (p.vertices.length - 2))
    geom = new THREE.BufferGeometry()

    let vertices = nbuf3(triCount * 3 * 3)
    let normals = nbuf3(triCount * 3 * 3)
    let uvs = nbuf2(triCount * 2 * 3)
    let colors;
    let grps = []
    ps.forEach(p => {
      let pvs = p.vertices
      let pvlen = pvs.length
      if (p.shared !== undefined) {
        if (!grps[p.shared]) grps[p.shared] = []
      }
      if (pvlen && pvs[0].color !== undefined) {
        if (!colors) colors = nbuf3(triCount * 3 * 3);
      }
      for (let j = 3; j <= pvlen; j++) {
        (p.shared !== undefined) && (grps[p.shared].push(vertices.top / 3, (vertices.top / 3) + 1, (vertices.top / 3) + 2));
        vertices.write(pvs[0].pos)
        vertices.write(pvs[j - 2].pos)
        vertices.write(pvs[j - 1].pos)
        normals.write(pvs[0].normal)
        normals.write(pvs[j - 2].normal)
        normals.write(pvs[j - 1].normal)
        uvs.write(pvs[0].uv)
        uvs.write(pvs[j - 2].uv)
        uvs.write(pvs[j - 1].uv)
        colors && (colors.write(pvs[0].color) || colors.write(pvs[j - 2].color) || colors.write(pvs[j - 1].color))
      }
    }
    )
    geom.setAttribute('position', new THREE.BufferAttribute(vertices.array, 3));
    geom.setAttribute('normal', new THREE.BufferAttribute(normals.array, 3));
    geom.setAttribute('uv', new THREE.BufferAttribute(uvs.array, 2));
    colors && geom.setAttribute('color', new THREE.BufferAttribute(colors.array, 3));
    if (grps.length) {
      let index = []
      let gbase = 0;
      for (let gi = 0; gi < grps.length; gi++) {
        geom.addGroup(gbase, grps[gi].length, gi)
        gbase += grps[gi].length
        index = index.concat(grps[gi]);
      }
      geom.setIndex(index)
    }
    g2 = geom;
  }

  let inv = new THREE.Matrix4().copy(toMatrix).invert();
  geom.applyMatrix4(inv);
  geom.computeBoundingSphere();
  geom.computeBoundingBox();
  let m = new THREE.Mesh(geom, toMaterial);
  m.matrix.copy(toMatrix);
  m.matrix.decompose(m.position, m.quaternion, m.scale)
  m.rotation.setFromQuaternion(m.quaternion)
  m.updateMatrixWorld();
  m.castShadow = m.receiveShadow = true;
  return m
}

export default CSG
