'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

exports.default = aframeFrustumLockComponent;

var _lodash = require('lodash.throttle');

var _lodash2 = _interopRequireDefault(_lodash);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var COMPONENT_NAME = 'frustum-lock';

/**
 * @param aframe {Object} The Aframe instance to register with
 * @param componentName {String} The component name to use. Default:
 * 'frustum-lock'
 */
function aframeFrustumLockComponent(aframe) {
  var componentName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : COMPONENT_NAME;


  // From https://github.com/ngokevin/aframe-animation-component/blob/master/index.js#L176
  function getPropertyType(el, property) {
    var _property$split = property.split('.');

    var _property$split2 = _slicedToArray(_property$split, 2);

    var compName = _property$split2[0];
    var propName = _property$split2[1];

    var component = el.components[compName] || aframe.components[compName];

    // Raw attributes
    if (!component) {
      return null;
    }

    if (propName) {
      return component.schema[propName].type;
    }

    return component.schema.type;
  }

  function isValidTypeOrThrow(element, propertyName, type) {
    var widthPropType = getPropertyType(element, propertyName);

    // null is a valid value (for raw attributes)
    if (widthPropType && widthPropType !== type) {
      throw new Error('unable to update property ' + propertyName + '; not a ' + type);
    }
  }

  function calculateFrustrumSize(cameraEl, depth) {
    var threeCamera = cameraEl.getObject3D('camera');
    var height = 2 * Math.tan(threeCamera.fov * aframe.THREE.Math.DEG2RAD / 2) * depth;
    var width = height * threeCamera.aspect;
    return { width: width, height: height };
  }

  /**
   * Frustum Lock component for A-Frame.
   */
  aframe.registerComponent(componentName, {
    schema: {

      /**
       * @param {string} [widthProperty=width] - Once frustum width is
       * calculated, this property on the element will be given the value.
       */
      widthProperty: { default: 'width' },

      /**
       * @param {string} [heightProperty=width] - Once frustum height is
       * calculated, this property on the element will be given the value.
       */
      heightProperty: { default: 'height' },

      /**
       * @param {number} [depth=10] - Distance along the z-index to position the
       * entity once frustum size calculated.
       */
      depth: { default: 10 },

      /**
       * @param {number} [throttleTimeout=100] - Frustum calculations are
       * performed on resize and enter/exit vr. This throttles the calculations
       * to every throttleTimeout milliseconds.
       */
      throttleTimeout: { default: 100 }
    },

    /**
     * Set if component needs multiple instancing.
     */
    multiple: false,

    /**
     * Called once when component is attached. Generally for initial setup.
     */
    init: function init() {
      var _this = this;

      this._attachedEventListener = false;
      this._waitingForCameraInit = false;

      this._doFrustumCalcs = function (camera) {
        var _calculateFrustrumSiz = calculateFrustrumSize(camera, _this.data.depth);

        var width = _calculateFrustrumSiz.width;
        var height = _calculateFrustrumSiz.height;


        aframe.utils.entity.setComponentProperty(_this.el, _this.data.widthProperty, width);
        aframe.utils.entity.setComponentProperty(_this.el, _this.data.heightProperty, height);
        _this.el.setAttribute('position', '0 0 -' + _this.data.depth);
      };

      this._activeCameraListener = function (event) {
        _this._waitingForCameraInit = false;
        _this.el.sceneEl.removeEventListener('camera-set-active', _this._activeCameraListener);
        _this._doFrustumCalcs(event.detail.cameraEl);
      };

      this._attachEventListeners = function () {
        if (_this._attachedEventListener) {
          return;
        }
        _this._attachedEventListener = (0, _lodash2.default)(_this._resizeEventListener, _this.data.throttleTimeout);
        window.addEventListener('resize', _this._attachedEventListener);
        _this.el.sceneEl.addEventListener('enter-vr', _this._attachedEventListener);
        _this.el.sceneEl.addEventListener('exit-vr', _this._attachedEventListener);
      };

      this._removeEventListeners = function () {
        if (!_this._attachedEventListener) {
          return;
        }
        window.removeEventListener('resize', _this._attachedEventListener);
        _this.el.sceneEl.removeEventListener('enter-vr', _this._attachedEventListener);
        _this.el.sceneEl.removeEventListener('exit-vr', _this._attachedEventListener);
        _this._attachedEventListener.cancel();
        _this._attachedEventListener = undefined;
      };

      this._resizeEventListener = function () {
        if (_this._waitingForCameraInit) {
          return;
        }

        var cameraEl = _this.el.sceneEl.systems.camera.activeCameraEl;

        // no active camera available. Let the update() function handle doing the
        // calculations in the future.
        if (!cameraEl) {
          return;
        }

        _this._doFrustumCalcs(cameraEl);
      };
    },


    /**
     * Called when component is attached and when component data changes.
     * Generally modifies the entity based on the data.
     */
    update: function update(oldData) {

      if (oldData.widthProperty !== this.data.widthProperty) {
        isValidTypeOrThrow(this.el, this.data.widthProperty, 'number');
      }

      if (oldData.heightProperty !== this.data.heightProperty) {
        isValidTypeOrThrow(this.el, this.data.heightProperty, 'number');
      }

      // Check the waiting flag so we don't accidentally do the calculations
      // over and over again
      if (!aframe.utils.deepEqual(oldData, this.data) && !this._waitingForCameraInit) {

        if (this.el.sceneEl.systems.camera.activeCameraEl) {

          // Active camera available, go straight to the calculations
          this._doFrustumCalcs(this.el.sceneEl.systems.camera.activeCameraEl);
        } else {

          // no active camera, so let's wait
          this._waitingForCameraInit = true;
          this.el.sceneEl.addEventListener('camera-set-active', this._activeCameraListener);
        }
      }
    },
    play: function play() {
      this._attachEventListeners();
    },
    stop: function stop() {
      this._removeEventListeners();
    }
  });
}