'use strict';

var VJS = VJS || {};
VJS.core = VJS.core || {};

/**
 * @constructor
 * @class
 * @memberOf VJS.core
 * @public
*/
VJS.core.intersections = VJS.core.intersections || {};

/**
 * Compute intersection between oriented bounding box and a plane.
 * Returns intersection in plane's space (toOBBSpaceInvert applied).
 * Should return at least 3 intersections. If not, the plane and the box do not
 * intersect.
 *
 * @memberOf VJS.core.intersections
 * @public
 *
 * @param {Object} obb - Oriented Bounding Box representation.
 * @param {THREE.Vector3} obb.halfDimensions - Half dimensions of the box.
 * @param {THREE.Vector3<THREE.Vector3>} obb.orientation - Orientation of the edges of the box.
 * @param {THREE.Vector3} obb.center - Center of the box.
 * @param {THREE.Matrix4} obb.toOBBSpace - Transform to go from plane space to box space.
 * @param {THREE.Matrix4} obb.toOBBSpaceInvert - Transform to go from box space to plane space.
 * @param {Object} plane - Plane representation
 * @param {THREE.Vector3} plane.position - position of normal which describes the plane.
 * @param {THREE.Vector3} plane.direction - Direction of normal which describes the plane.
 *
 * @returns {Array<THREE.Vector3>} List of all intersections, in plane's space.
 *
 * @todo toOBBSpace and toOBBSpaceInvert might be redundent.
 * @todo find best way to deal with different spaces.
 */

VJS.core.intersections.obbPlane = function(obb, plane) {

  //
  // obb = { halfDimensions, orientation, center, toOBBSpace }
  // plane = { position, direction }
  //
  //
  // LOGIC:
  //
  // Test intersection of each edge of the Oriented Bounding Box with the Plane
  // 
  // ALL EDGES 
  //
  //      .+-------+  
  //    .' |     .'|  
  //   +---+---+'  |  
  //   |   |   |   |  
  //   |  ,+---+---+  
  //   |.'     | .'   
  //   +-------+'     
  //
  // SPACE ORIENTATION
  //
  //       +
  //     j |
  //       |
  //       |   i 
  //   k  ,+-------+  
  //    .'
  //   +
  //
  //
  // 1- Move Plane position and orientation in IJK space
  // 2- Test Edges/ IJK Plane intersections
  // 3- Return intersection Edge/ IJK Plane if it touches the Oriented BBox

  var intersections = [];

  var t1 = plane.direction.clone().applyMatrix4(obb.toOBBSpace);
  var t0 = new THREE.Vector3(0, 0, 0).applyMatrix4(obb.toOBBSpace);

  var planeOBB = {
    position: plane.position.clone().applyMatrix4(obb.toOBBSpace),
    direction: new THREE.Vector3(t1.x - t0.x, t1.y - t0.y, t1.z - t0.z).normalize()
  };

  var bboxMin = new THREE.Vector3(
      obb.center.x - obb.halfDimensions.x,
      obb.center.y - obb.halfDimensions.y,
      obb.center.z - obb.halfDimensions.z);
  var bboxMax = new THREE.Vector3(
      obb.center.x + obb.halfDimensions.x,
      obb.center.y + obb.halfDimensions.y,
      obb.center.z + obb.halfDimensions.z);

  // 12 edges (i.e. ray)/plane intersection tests

  // RAYS STARTING FROM THE FIRST CORNER (0, 0, 0)
  //
  //       +
  //       |
  //       |
  //       | 
  //      ,+---+---+
  //    .'   
  //   +   

  var ray = {
    'position': new THREE.Vector3(obb.center.x - obb.halfDimensions.x, obb.center.y - obb.halfDimensions.y, obb.center.z - obb.halfDimensions.z),
    'direction': obb.orientation.x
  };

  var intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.y;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.z;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  // RAYS STARTING FROM THE LAST CORNER
  //
  //               +
  //             .'
  //   +-------+'
  //           |
  //           |
  //           |
  //           +
  //

  ray = {
    'position': new THREE.Vector3(obb.center.x + obb.halfDimensions.x, obb.center.y + obb.halfDimensions.y, obb.center.z + obb.halfDimensions.z),
    'direction': obb.orientation.x
  };

  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.y;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.z;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  // RAYS STARTING FROM THE SECOND CORNER
  //
  //               +
  //               |
  //               |
  //               |
  //               +
  //             .'
  //           +'

  ray = {
    'position': new THREE.Vector3(obb.center.x + obb.halfDimensions.x, obb.center.y - obb.halfDimensions.y, obb.center.z - obb.halfDimensions.z),
    'direction': obb.orientation.y
  };

  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.z;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  // RAYS STARTING FROM THE THIRD CORNER
  //
  //      .+-------+  
  //    .'
  //   +
  //   
  //   
  //   
  //   

  ray = {
    'position': new THREE.Vector3(obb.center.x - obb.halfDimensions.x, obb.center.y + obb.halfDimensions.y, obb.center.z - obb.halfDimensions.z),
    'direction': obb.orientation.x
  };

  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.z;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  // RAYS STARTING FROM THE FOURTH CORNER
  //
  //   
  //   
  //   +
  //   |
  //   |  
  //   |
  //   +-------+

  ray = {
    'position': new THREE.Vector3(obb.center.x - obb.halfDimensions.x, obb.center.y - obb.halfDimensions.y, obb.center.z + obb.halfDimensions.z),
    'direction': obb.orientation.x
  };

  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  ray.direction = obb.orientation.y;
  intersection = this.rayPlane(ray, planeOBB);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection.applyMatrix4(obb.toOBBSpaceInvert));
  }

  return intersections;
};

/**
 * Compute intersection between a ray and a plane.
 *
 * @memberOf VJS.core.intersections
 * @public
 *
 * @param {Object} ray - Ray representation.
 * @param {THREE.Vector3} ray.position - position of normal which describes the ray.
 * @param {THREE.Vector3} ray.direction - Direction of normal which describes the ray.
 * @param {Object} plane - Plane representation
 * @param {THREE.Vector3} plane.position - position of normal which describes the plane.
 * @param {THREE.Vector3} plane.direction - Direction of normal which describes the plane.
 *
 * @returns {THREE.Vector3|null} Intersection between ray and plane or null.
 */
VJS.core.intersections.rayPlane = function(ray, plane) {
  // ray: {position, direction}
  // plane: {position, direction}

  if (ray.direction.dot(plane.direction) !== 0) {
    //
    // not parallel, move forward
    //
    // LOGIC:
    //
    // Ray equation: P = P0 + tV
    // P = <Px, Py, Pz>
    // P0 = <ray.position.x, ray.position.y, ray.position.z>
    // V = <ray.direction.x, ray.direction.y, ray.direction.z>
    //
    // Therefore:
    // Px = ray.position.x + t*ray.direction.x
    // Py = ray.position.y + t*ray.direction.y
    // Pz = ray.position.z + t*ray.direction.z
    //
    //
    //
    // Plane equation: ax + by + cz + d = 0
    // a = plane.direction.x
    // b = plane.direction.y
    // c = plane.direction.z
    // d = -( plane.direction.x*plane.position.x +
    //        plane.direction.y*plane.position.y +
    //        plane.direction.z*plane.position.z )
    //
    //
    // 1- in the plane equation, we replace x, y and z by Px, Py and Pz
    // 2- find t
    // 3- replace t in Px, Py and Pz to get the coordinate of the intersection
    //
    var t = (plane.direction.x * (plane.position.x - ray.position.x) + plane.direction.y * (plane.position.y - ray.position.y) + plane.direction.z * (plane.position.z - ray.position.z)) /
        (plane.direction.x * ray.direction.x + plane.direction.y * ray.direction.y + plane.direction.z * ray.direction.z);

    var intersection = new THREE.Vector3(
        ray.position.x + t * ray.direction.x,
        ray.position.y + t * ray.direction.y,
        ray.position.z + t * ray.direction.z);

    return intersection;

  }

  return null;

};

VJS.core.intersections.rayBox = function(ray, box) {
  // ray: {position, direction}
  // box: {halfDimensions, center}

  var intersections = [];
  var plane = {
    position: null,
    direction: null
  };

  var bboxMin = new THREE.Vector3(
    box.center.x - box.halfDimensions.x,
    box.center.y - box.halfDimensions.y,
    box.center.z - box.halfDimensions.z);
  var bboxMax = new THREE.Vector3(
      box.center.x + box.halfDimensions.x,
      box.center.y + box.halfDimensions.y,
      box.center.z + box.halfDimensions.z);

  // X min
  plane.direction = new THREE.Vector3(-1, 0, 0);
  plane.position = new THREE.Vector3(
    bboxMin.x,
    box.center.y,
    box.center.z);
  var intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }

  // X max
  plane.direction = new THREE.Vector3(1, 0, 0);
  plane.position = new THREE.Vector3(
    bboxMax.x,
    box.center.y,
    box.center.z);
  intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }

  // Y min
  plane.direction = new THREE.Vector3(0, -1, 0);
  plane.position = new THREE.Vector3(
    box.center.x,
    bboxMin.y,
    box.center.z);
  intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }

  // Y max
  plane.direction = new THREE.Vector3(0, 1, 0);
  plane.position = new THREE.Vector3(
    box.center.x,
    bboxMax.y,
    box.center.z);
  intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }

  // Z min
  plane.direction = new THREE.Vector3(0, 0, -1);
  plane.position = new THREE.Vector3(
    box.center.x,
    box.center.y,
    bboxMin.z);
  intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }

  // Z max
  plane.direction = new THREE.Vector3(0, 0, 1);
  plane.position = new THREE.Vector3(
    box.center.x,
    box.center.y,
    bboxMax.z);
  intersection = this.rayPlane(ray, plane);
  if (this.inBBox(intersection, bboxMin, bboxMax)) {
    intersections.push(intersection);
  }
  
  return intersections;
};

VJS.core.intersections.inBBox = function(point, bboxMin, bboxMax) {
  if (point &&
  point.x >= bboxMin.x && point.y >= bboxMin.y && point.z >= bboxMin.z &&
  point.x <= bboxMax.x && point.y <= bboxMax.y && point.z <= bboxMax.z) {
    return true;
  }
  return false;
};

/*** Exports ***/

var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
  module.exports = VJS.core.intersections;
}