/**
 * 72 values per box.
 * Divided by 3 (x, y, z) is 24 vertices.
 * 6 vertices per side of the cube (2 faces per side).
 */
const NUM_VALUES_PER_BOX = 72;

/**
 * Column bars moving in sync to the audio via audio analyser.
 */
AFRAME.registerComponent('audio-columns', {
  schema: {
    analyser: {type: 'selector', default: '#audioAnalyser'},
    height: {default: 1.0},
    mirror: {default: 3},
    scale: {default: 4.0},
    separation: {default: 0.3},
    thickness: {default: 0.1}
  },

  init: function () {
    this.analyser = this.data.analyser.components.audioanalyser;

    // Number of levels is half the FFT size.
    this.frequencyBinCount = this.analyser.data.fftSize / 2;

    // Create boxes (one row on each side per level).
    const geometries = [];
    let zPosition = 0;
    for (let i = 0; i < this.frequencyBinCount; i++) {
      for (let side = 0; side < 2; side++) {
        const box = new THREE.BoxBufferGeometry();
        this.initBox(box, side === 0 ? 1 : -1, zPosition);
        geometries.push(box);
        // Move Z back.
        zPosition -= this.data.separation;
      }
    }

    this.geometry = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries);
    const mesh = new THREE.Mesh(this.geometry, this.el.sceneEl.systems.materials.black);
    this.el.setObject3D('mesh', mesh);
  },

  tick: function () {
    // Step by 2 since we create one box of each side per level.
    for (let i = 0; i < this.frequencyBinCount * 2; i += 2) {
      let yScale = (this.data.height / 2) + this.analyser.levels[Math.floor(i / 2)] / 256.0 *
                   this.data.scale;
      if (isNaN(yScale)) { return; }
      this.setBoxHeight(i, yScale);
      this.setBoxHeight(i + 1, yScale);
      this.geometry.attributes.position.needsUpdate = true;
    }
  },

  /**
   * Initialize box by changing vertex buffer, to set position and scale.
   */
  initBox: function (box, flip, zPosition) {
    const data = this.data;

    // Set position and scale of box via vertices.
    for (let v = 0; v < box.attributes.position.array.length; v += 3) {
      // Apply thickness to X and Z.
      box.attributes.position.array[v] *= data.thickness;
      box.attributes.position.array[v + 2] *= data.thickness;

      // Apply zPosition.
      box.attributes.position.array[v + 2] += zPosition;

      // Apply height to Y.
      box.attributes.position.array[v + 1] *= data.height / 2;

      // Change X vertex positions for mirroring.
      box.attributes.position.array[v] += flip * data.mirror;
    }
  },

  /**
   * Set Y of vertices.
   */
  setBoxHeight: function (boxNumber, height) {
    const boxIndex = boxNumber * NUM_VALUES_PER_BOX;
    for (let i = boxIndex; i < boxIndex + NUM_VALUES_PER_BOX; i += 3) {
      // Set Y.
      let yValue = this.geometry.attributes.position.array[i + 1];
      this.geometry.attributes.position.array[i + 1] = yValue >= 0
        ? height
        : -1 * height;
    }
  }
});
