/**
 * @author AndrĂ© Storhaug <[email protected]>
 */

import autoBind from 'auto-bind';
import { FileLoader, Loader, Vector3 } from 'three';
import { PointOctree } from "sparse-octree";
import { Parser } from 'binvox';
import { levelOfDetail } from '../mixins/levelOfDetail';

class BINVOXLoader extends Loader {

  /**
   * Create a BINVOXLoader.
   * @classdesc Class for loading voxel data stored in BINVOX files.
   * @extends Loader
   * @mixes levelOfDetail
   * @param {LoadingManager} manager
   */
  constructor(manager) {
    super(manager);
    autoBind(this);
    Object.assign(this, levelOfDetail);
  }

	/**
	 * Loads and parses a BINVOX file from a URL.
	 *
	 * @param {String} url - URL to the BINVOX file.
	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
	 * @param {Function} [onProgress] - Callback for download progress.
	 * @param {Function} [onError] - Callback for download errors.
	 */
  load(url, onLoad, onProgress, onError) {
    var scope = this;

    var loader = new FileLoader(this.manager);
    loader.setPath(this.path);
    loader.setResponseType('arraybuffer')
    loader.load(url, function (buffer) {

      scope.parse(buffer)
        .then(octree => onLoad(octree))
        .catch(err => console.error(err))

    }, onProgress, onError);
  }

	/**
	 * Parse BINVOX file data.
	 * @param {Buffer} buffer Content of BINVOX file.
	 * @return {Promise<PointOctree>} Promise with an octree filled with voxel data.
	 */
  parse(buffer) {

    return new Promise((resolve, reject) => {
      const parser = new Parser();
      let data = parser.parse(buffer);
      const depth = data.dimension.depth;
      const width = data.dimension.width;
      const height = data.dimension.height;

      const min = new Vector3(-depth / 2, -height / 2, -width / 2);
      const max = new Vector3(depth / 2, height / 2, width / 2);

      const octree = new PointOctree(min, max, 0, this.LOD.maxPoints, this.LOD.maxDepth);
      let voxelData = {};

      data.voxels.forEach(voxel => {
        let x, y, z;

        x = voxel.x - (depth / 2);
        y = voxel.y - (width / 2);
        z = voxel.z - (height / 2);

        let point = new Vector3(x, z, y);
        octree.insert(point, voxelData);
      });

      resolve(octree);
    });
  }

}

export { BINVOXLoader };