// copied from THREE.JS three/examples/js/exporters/STLExporter and slightly modified
import {Mesh} from 'three';


/**
 * Usage:
 *  const exporter = new STLExporter();
 *
 *  // second argument is a list of options
 *  const data = exporter.parse( mesh, { binary: true } );
 *
 */

export class STLExporter {

  parse(shellViews, options = {}) {

    const binary = options.binary !== undefined ? options.binary : false; //

    const objects = [];
    let triangles = 0;


    shellViews.forEach(function (view) {
      if (!view.faceViews) {
        return;
      }

      view.faceViews.forEach(faceView => {
        const object = faceView.mesh;
        if (object instanceof Mesh) {
          const geometry = object.geometry;

          if (geometry.isBufferGeometry !== true) {

            throw new Error('THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.');

          }

          const index = geometry.index;
          const positionAttribute = geometry.getAttribute('position');
          triangles += index !== null ? index.count / 3 : positionAttribute.count / 3;
          objects.push({
            object3d: object,
            geometry: geometry,
            modelId: view.model.id
          });
        }
      });
    });
    let output;
    let offset = 80; // skip header

    if (binary === true) {

      const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
      const arrayBuffer = new ArrayBuffer(bufferLength);
      output = new DataView(arrayBuffer);
      output.setUint32(offset, triangles, true);
      offset += 4;

    } else {

      output = '';
      output += 'solid exported\n';

    }

    const vA = new THREE.Vector3();
    const vB = new THREE.Vector3();
    const vC = new THREE.Vector3();
    const cb = new THREE.Vector3();
    const ab = new THREE.Vector3();
    const normal = new THREE.Vector3();

    for (let i = 0, il = objects.length; i < il; i++) {

      const object = objects[i].object3d;
      const geometry = objects[i].geometry;
      const index = geometry.index;
      const positionAttribute = geometry.getAttribute('position');

      if (index !== null) {

        // indexed geometry
        for (let j = 0; j < index.count; j += 3) {

          const a = index.getX(j + 0);
          const b = index.getX(j + 1);
          const c = index.getX(j + 2);
          writeFace(a, b, c, positionAttribute, object);

        }

      } else {

        // non-indexed geometry
        for (let j = 0; j < positionAttribute.count; j += 3) {

          const a = j + 0;
          const b = j + 1;
          const c = j + 2;
          writeFace(a, b, c, positionAttribute, object);

        }

      }

    }

    if (binary === false) {

      output += 'endsolid exported\n';

    }

    return output;

    function writeFace(a, b, c, positionAttribute, object) {

      vA.fromBufferAttribute(positionAttribute, a);
      vB.fromBufferAttribute(positionAttribute, b);
      vC.fromBufferAttribute(positionAttribute, c);

      if (object.isSkinnedMesh === true) {

        object.boneTransform(a, vA);
        object.boneTransform(b, vB);
        object.boneTransform(c, vC);

      }

      vA.applyMatrix4(object.matrixWorld);
      vB.applyMatrix4(object.matrixWorld);
      vC.applyMatrix4(object.matrixWorld);
      writeNormal(vA, vB, vC);
      writeVertex(vA);
      writeVertex(vB);
      writeVertex(vC);

      if (binary === true) {

        output.setUint16(offset, 0, true);
        offset += 2;

      } else {

        output += '\t\tendloop\n';
        output += '\tendfacet\n';

      }

    }

    function writeNormal(vA, vB, vC) {

      cb.subVectors(vC, vB);
      ab.subVectors(vA, vB);
      cb.cross(ab).normalize();
      normal.copy(cb).normalize();

      if (binary === true) {

        output.setFloat32(offset, normal.x, true);
        offset += 4;
        output.setFloat32(offset, normal.y, true);
        offset += 4;
        output.setFloat32(offset, normal.z, true);
        offset += 4;

      } else {

        output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
        output += '\t\touter loop\n';

      }

    }

    function writeVertex(vertex) {

      if (binary === true) {

        output.setFloat32(offset, vertex.x, true);
        offset += 4;
        output.setFloat32(offset, vertex.y, true);
        offset += 4;
        output.setFloat32(offset, vertex.z, true);
        offset += 4;

      } else {

        output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';

      }

    }

  }

}



