/**
 * PowerTip PlacementCalculator
 *
 * @fileoverview  PlacementCalculator object that computes tooltip position.
 * @link          https://stevenbenner.github.io/jquery-powertip/
 * @author        Steven Benner (https://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Creates a new Placement Calculator.
 * @private
 * @constructor
 */
function PlacementCalculator() {
	/**
	 * Compute the CSS position to display a tooltip at the specified placement
	 * relative to the specified element.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @param {number} tipWidth Width of the tooltip element in pixels.
	 * @param {number} tipHeight Height of the tooltip element in pixels.
	 * @param {number} offset Distance to offset tooltips in pixels.
	 * @return {CSSCoordinates} A CSSCoordinates object with the position.
	 */
	function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
		var placementBase = placement.split('-')[0], // ignore 'alt' for corners
			coords = new CSSCoordinates(),
			position;

		if (isSvgElement(element)) {
			position = getSvgPlacement(element, placementBase);
		} else {
			position = getHtmlPlacement(element, placementBase);
		}

		// calculate the appropriate x and y position in the document
		switch (placement) {
			case 'n':
				coords.set('left', position.left - (tipWidth / 2));
				coords.set('bottom', session.windowHeight - position.top + offset);
				break;
			case 'e':
				coords.set('left', position.left + offset);
				coords.set('top', position.top - (tipHeight / 2));
				break;
			case 's':
				coords.set('left', position.left - (tipWidth / 2));
				coords.set('top', position.top + offset);
				break;
			case 'w':
				coords.set('top', position.top - (tipHeight / 2));
				coords.set('right', session.windowWidth - position.left + offset);
				break;
			case 'nw':
				coords.set('bottom', session.windowHeight - position.top + offset);
				coords.set('right', session.windowWidth - position.left - 20);
				break;
			case 'nw-alt':
				coords.set('left', position.left);
				coords.set('bottom', session.windowHeight - position.top + offset);
				break;
			case 'ne':
				coords.set('left', position.left - 20);
				coords.set('bottom', session.windowHeight - position.top + offset);
				break;
			case 'ne-alt':
				coords.set('bottom', session.windowHeight - position.top + offset);
				coords.set('right', session.windowWidth - position.left);
				break;
			case 'sw':
				coords.set('top', position.top + offset);
				coords.set('right', session.windowWidth - position.left - 20);
				break;
			case 'sw-alt':
				coords.set('left', position.left);
				coords.set('top', position.top + offset);
				break;
			case 'se':
				coords.set('left', position.left - 20);
				coords.set('top', position.top + offset);
				break;
			case 'se-alt':
				coords.set('top', position.top + offset);
				coords.set('right', session.windowWidth - position.left);
				break;
		}

		return coords;
	}

	/**
	 * Finds the tooltip attachment point in the document for a HTML DOM element
	 * for the specified placement.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {Object} An object with the top,left position values.
	 */
	function getHtmlPlacement(element, placement) {
		var objectOffset = element.offset(),
			objectWidth = element.outerWidth(),
			objectHeight = element.outerHeight(),
			left,
			top;

		// calculate the appropriate x and y position in the document
		switch (placement) {
			case 'n':
				left = objectOffset.left + objectWidth / 2;
				top = objectOffset.top;
				break;
			case 'e':
				left = objectOffset.left + objectWidth;
				top = objectOffset.top + objectHeight / 2;
				break;
			case 's':
				left = objectOffset.left + objectWidth / 2;
				top = objectOffset.top + objectHeight;
				break;
			case 'w':
				left = objectOffset.left;
				top = objectOffset.top + objectHeight / 2;
				break;
			case 'nw':
				left = objectOffset.left;
				top = objectOffset.top;
				break;
			case 'ne':
				left = objectOffset.left + objectWidth;
				top = objectOffset.top;
				break;
			case 'sw':
				left = objectOffset.left;
				top = objectOffset.top + objectHeight;
				break;
			case 'se':
				left = objectOffset.left + objectWidth;
				top = objectOffset.top + objectHeight;
				break;
		}

		return {
			top: top,
			left: left
		};
	}

	/**
	 * Finds the tooltip attachment point in the document for a SVG element for
	 * the specified placement.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {Object} An object with the top,left position values.
	 */
	function getSvgPlacement(element, placement) {
		var svgElement = element.closest('svg')[0],
			domElement = element[0],
			point = svgElement.createSVGPoint(),
			boundingBox = domElement.getBBox(),
			matrix = domElement.getScreenCTM(),
			halfWidth = boundingBox.width / 2,
			halfHeight = boundingBox.height / 2,
			placements = [],
			placementKeys = [ 'nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w' ],
			coords,
			rotation,
			steps,
			x;

		/**
		 * Transform and append the current points to the placements list.
		 * @private
		 */
		function pushPlacement() {
			placements.push(point.matrixTransform(matrix));
		}

		// get bounding box corners and midpoints
		point.x = boundingBox.x;
		point.y = boundingBox.y;
		pushPlacement();
		point.x += halfWidth;
		pushPlacement();
		point.x += halfWidth;
		pushPlacement();
		point.y += halfHeight;
		pushPlacement();
		point.y += halfHeight;
		pushPlacement();
		point.x -= halfWidth;
		pushPlacement();
		point.x -= halfWidth;
		pushPlacement();
		point.y -= halfHeight;
		pushPlacement();

		// determine rotation
		if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
			rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
			steps = Math.ceil(((rotation % 360) - 22.5) / 45);
			if (steps < 1) {
				steps += 8;
			}
			while (steps--) {
				placementKeys.push(placementKeys.shift());
			}
		}

		// find placement
		for (x = 0; x < placements.length; x++) {
			if (placementKeys[x] === placement) {
				coords = placements[x];
				break;
			}
		}

		return {
			top: coords.y + session.scrollTop,
			left: coords.x + session.scrollLeft
		};
	}

	// expose methods
	this.compute = computePlacementCoords;
}