// Modified from o3djs

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// A shout out to Terrance J. Grant at tatewake.com for his tutorial on arcball
// implementations.

/**
 * @fileoverview This file contains functions for implementing an arcball
 * calculation.  It puts them in the "arcball" module on the o3djs object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

quaternionToRotation = function(q) {
  var qX = q[0];
  var qY = q[1];
  var qZ = q[2];
  var qW = q[3];

  var qWqW = qW * qW;
  var qWqX = qW * qX;
  var qWqY = qW * qY;
  var qWqZ = qW * qZ;
  var qXqW = qX * qW;
  var qXqX = qX * qX;
  var qXqY = qX * qY;
  var qXqZ = qX * qZ;
  var qYqW = qY * qW;
  var qYqX = qY * qX;
  var qYqY = qY * qY;
  var qYqZ = qY * qZ;
  var qZqW = qZ * qW;
  var qZqX = qZ * qX;
  var qZqY = qZ * qY;
  var qZqZ = qZ * qZ;

  var d = qWqW + qXqX + qYqY + qZqZ;

  return [
    [(qWqW + qXqX - qYqY - qZqZ) / d,
     2 * (qWqZ + qXqY) / d,
     2 * (qXqZ - qWqY) / d, 0],
    [2 * (qXqY - qWqZ) / d,
     (qWqW - qXqX + qYqY - qZqZ) / d,
     2 * (qWqX + qYqZ) / d, 0],
    [2 * (qWqY + qXqZ) / d,
     2 * (qYqZ - qWqX) / d,
     (qWqW - qXqX - qYqY + qZqZ) / d, 0],
    [0, 0, 0, 1]];
};
cross = function(a, b) {
  return [a[1] * b[2] - a[2] * b[1],
          a[2] * b[0] - a[0] * b[2],
          a[0] * b[1] - a[1] * b[0]];
};

dot = function(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }

copyVector = function(v) {
  var r = [];
  for (var i = 0; i < v.length; i++)
    r[i] = v[i];
  return r;
};

lengthSquared = function(a) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r += a[i] * a[i];
  return r;
};

normalize = function(a) {
  var r = [];
  var n = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    n += a[i] * a[i];
  n = Math.sqrt(n);
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] / n;
  return r;
};

length = function(a) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r += a[i] * a[i];
  return Math.sqrt(r);
};

quaternionToRotation = function(q) {
  var qX = q[0];
  var qY = q[1];
  var qZ = q[2];
  var qW = q[3];

  var qWqW = qW * qW;
  var qWqX = qW * qX;
  var qWqY = qW * qY;
  var qWqZ = qW * qZ;
  var qXqW = qX * qW;
  var qXqX = qX * qX;
  var qXqY = qX * qY;
  var qXqZ = qX * qZ;
  var qYqW = qY * qW;
  var qYqX = qY * qX;
  var qYqY = qY * qY;
  var qYqZ = qY * qZ;
  var qZqW = qZ * qW;
  var qZqX = qZ * qX;
  var qZqY = qZ * qY;
  var qZqZ = qZ * qZ;

  var d = qWqW + qXqX + qYqY + qZqZ;

  return [
    [(qWqW + qXqX - qYqY - qZqZ) / d,
     2 * (qWqZ + qXqY) / d,
     2 * (qXqZ - qWqY) / d, 0],
    [2 * (qXqY - qWqZ) / d,
     (qWqW - qXqX + qYqY - qZqZ) / d,
     2 * (qWqX + qYqZ) / d, 0],
    [2 * (qWqY + qXqZ) / d,
     2 * (qYqZ - qWqX) / d,
     (qWqW - qXqX - qYqY + qZqZ) / d, 0],
    [0, 0, 0, 1]];
};

cssMatrixFromListMatrix = function(listMatrix) {
	// Note that CSS matrices are apparently from decades ago in Fortran-land and are column-major. >_<
	var matrix = new WebKitCSSMatrix();
	matrix.m11 = listMatrix[0][0];
	matrix.m12 = listMatrix[1][0];
	matrix.m13 = listMatrix[2][0];
	matrix.m14 = listMatrix[3][0];
	matrix.m21 = listMatrix[0][1];
	matrix.m22 = listMatrix[1][1];
	matrix.m23 = listMatrix[2][1];
	matrix.m24 = listMatrix[3][1];
	matrix.m31 = listMatrix[0][2];
	matrix.m32 = listMatrix[1][2];
	matrix.m33 = listMatrix[2][2];
	matrix.m34 = listMatrix[3][2];
	matrix.m41 = listMatrix[0][3];
	matrix.m42 = listMatrix[1][3];
	matrix.m43 = listMatrix[2][3];
	matrix.m44 = listMatrix[3][3];
	return matrix;
}

/**
 * A class that implements an arcball.
 * @constructor
 * @param {number} areaWidth width of area arcball should cover.
 * @param {number} areaHeight height of area arcball should cover.
 * @see o3djs.arcball
 */
ArcBall = function(areaWidth, areaHeight) {
  this.startVector = [0, 0, 0];
  this.endVector = [0, 0, 0];
  this.areaWidth = areaWidth;
  this.areaHeight = areaHeight;
};


/**
 * Sets the size of the arcball.
 * @param {number} areaWidth width of area arcball should cover.
 * @param {number} areaHeight height of area arcball should cover.
 */
ArcBall.prototype.setAreaSize = function(areaWidth, areaHeight) {
  this.areaWidth = areaWidth;
  this.areaHeight = areaHeight;
};

/**
 * Converts a 2d point to a point on the sphere of radius 1 sphere.
 * @param {!o3djs.math.Vector2} newPoint A point in 2d.
 * @return {!o3djs.math.Vector3} A point on the sphere of radius 1.
 */
ArcBall.prototype.mapToSphere = function(newPoint) {
  // Copy parameter into temp
  var tempPoint = copyVector(newPoint);

  // Scale to -1.0 <-> 1.0
  tempPoint[0] = 1.0 - tempPoint[0] / this.areaWidth * 2.0;
  tempPoint[1] = 1.0 - tempPoint[1] / this.areaHeight * 2.0;

  // Compute square of length from center
  var lSquared = lengthSquared(tempPoint);

  // If the point is mapped outside of the sphere... (length > radius squared)
  if (lSquared > 1.0) {
    return normalize(tempPoint).concat(0);
  } else {
    // Otherwise it's on the inside.
    return tempPoint.concat(Math.sqrt(1.0 - lSquared));
  }
};

/**
 * Records the starting point on the sphere.
 * @param {!o3djs.math.Vector2} newPoint point in 2d.
 */
ArcBall.prototype.click = function(newPoint) {
  this.startVector = this.mapToSphere(newPoint);
};



ArcBall.prototype.drag = function(end) {
  this.endVector = this.mapToSphere(end);
  var result = quaternionToRotation(cross(this.startVector, this.endVector).concat(dot(this.startVector, this.endVector)));
  	for (var i = 0; i < result.length; i++) // Work around a high-larious bug where the DOM chokes on things like 3.4e-10
  		for (var j = 0; j < result[i].length; j++)
  			if (result[i][j] > 0 && result[i][j] < 0.000001)
  				result[i][j] = 0.000001;
	return cssMatrixFromListMatrix(result);
};
