'use strict';

var UILayoutModule = angular.module('ui.layout', []);

UILayoutModule.controller('UILayoutCtrl',	['$scope', '$attrs', '$element', UILayoutCtrl]);

function UILayoutCtrl($scope, $attrs, $element)
{
	var _options = angular.extend({}, 
			$scope.$eval($attrs.uiLayout), 
			$scope.$eval($attrs.options));
	
	$scope.layoutInfo = new LayoutInfo($element, _options);
	
	return {
		options : _options,
		element : $element,
	};
}

UILayoutModule.directive('uiLayout', ['$parse', '$window', 
function UILayoutDirective($parse, $window) 
{
	var i = 0;
	
	return {
		restrict: 'E',
		
		scope: true,	// we need each directive having its own controller with its own scope
		
		compile: function compile(tElement, tAttrs){
			var options = angular.extend({},
					$parse(tAttrs.uiLayout)(), 
					$parse(tAttrs.options)());
			
			new LayoutInfo(tElement, options).initOnce();
			
			return {
				post : function(scope, iElement, iAttrs)
				{
					var elem = angular.element($window);
					elem.on("resize", onResize);
					
					function onResize()
					{
						scope.layoutInfo.onResize();
					}
				}
			};
		},
		
		controller : 'UILayoutCtrl'
	};
} ])

.directive('uiSplitbar', UISplitBar);

function UISplitBar()
{
	var htmlElement = angular.element(document.body.parentElement);
	return {
		require : '^uiLayout',
		restrict : 'EAC',
		link : function(scope, iElement, iAttrs, parentUILayoutCtrl) 
		{
			var mouseProp = scope.layoutInfo.m_bIsColumns ? 'clientX' : 'clientY';
			var animationFrameRequested, mousePos;
			
			iElement.on('mousedown touchstart', function(e) {
				e.preventDefault();
				e.stopPropagation();
				htmlElement.on('mousemove touchmove', _resize1);
				return false;
			});
			
			htmlElement.on('mouseup touchend', function() {
				htmlElement.off('mousemove touchmove');
			});
			
			function _resize1(mouseEvent) 
			{
				mousePos = mouseEvent[mouseProp];
				if (mousePos == null && mouseEvent.originalEvent != null)
					mousePos = mouseEvent.originalEvent[mouseProp];
				
				if (mousePos == null)
					return;
				
				if (animationFrameRequested) {
					window.cancelAnimationFrame(animationFrameRequested);
				}
				animationFrameRequested = window.requestAnimationFrame(_resize2);
			}
			
			function _resize2()
			{
				scope.layoutInfo.updateSplitBarPos(iElement[0], mousePos);
			}
		}
	};
}

UISplitBar.SIZE = 7;


function LayoutInfo(hLayoutElem, options)
{
	this.m_bIsColumns = false;
	
	this.m_hLayoutElem = hLayoutElem;
	
	this.m_panels = [];
	
	/**
	 * we use this "private" method to internalize var-s, which otherwise will 
	 * become visible to all inner methods
	 */
	this._constructor = function(options)
	{
		options = options || {};
		options.flow = options["flow"] || "row";
		
		this.m_bIsColumns = options.flow === "column";
		
		if (!this.m_bIsColumns && (options.flow != "row"))
			Utils.assertFailed("Cannot interpret option <flow = '" + options.flow + "'>. Only 'row' (default) and 'column' are acceptable");
		
		this.m_hLayoutElem.addClass('ui-layout-' + (options.flow));
		
		var hChildrenElems = this.m_hLayoutElem.children();
		var nChildren = hChildrenElems.length; 
			
		options.sizes = options.sizes ? options.sizes : [];
		options.sizes.length = nChildren;
		
		var nPanelIndex = 0;
		for (var i = 0; i < nChildren; ++i)
		{
			// we must make this method idempotent; it is called once at directive compile, when it creates bars; 
			// but we cannot preserve it at that time. Thus we call it again in the controller to keep the layout
			// info. But we should not re-create the split-bars
			if (angular.element(hChildrenElems[i]).hasClass("ui-splitbar"))
				continue;
			var panel = new LayoutPanel(hChildrenElems[i], options.sizes[nPanelIndex++]); 
			this.m_panels.push(panel);
		}
	}
	
	this._constructor(options);
	
	/**
	 * this method must be called once per each layout (=UILayoutDirective). 
	 * This is because it adds one time the split-bars 
	 */
	this.initOnce = function()
	{
		this._initCSS();
	}
		
	var hSplitBarTemplate = '<div class="stretch ui-splitbar"></div>';
	var sStartProp = this.m_bIsColumns ? "left" : "top";
	var sEndProp = this.m_bIsColumns ? "right" : "bottom";
	var sSizeProp = this.m_bIsColumns ? "width" : "height";
	
	this._calculateFixSizes = function()
	{
		var nFixSizes = 0;
		var nPercentSizes = 0;
		var nChildren = this.m_panels.length;
		for (var i = 0; i < nChildren; ++i)
		{
			var panel = this.m_panels[i];
			if (panel.isElastic)
				nPercentSizes += panel.percentSize;
			else
				nFixSizes += panel.size;
		}
//		if (nPercentSizes > 0 && Math.round(nPercentSizes) != 100)
//			Utils.assertFailed("Expecting specification of percentage sizes to add up to 100%, instead it is <" + Math.round(nPercentSizes) + ">");
		return nFixSizes;
	}
	
	this._iterate = function(operFn)
	{
		var layoutRect = this.m_hLayoutElem[0].getBoundingClientRect();
		var nLayoutSize = layoutRect[sSizeProp];
		var nLayoutOrigin = layoutRect[sStartProp]; 
		
		var nFixSizes = this._calculateFixSizes();
		var nElasticSize = nLayoutSize - nFixSizes;
		
		var nLayoutPos = 0;
		var nChildren = this.m_panels.length;		
		for (var i = 0; i < nChildren; ++i)
		{
			var panel = this.m_panels[i];
			panel.start = nLayoutPos;
			var bLastPanel = i == nChildren - 1;

			operFn(panel, nLayoutOrigin, nLayoutSize, nFixSizes, bLastPanel);
			
			if (i > 0)
				panel.start += 8;
			
			panel.pstart = panel.start * 100 / nLayoutSize
			nLayoutPos = panel.end;
			panel.pend = panel.end * 100 / nLayoutSize;
			panel.size = panel.end - panel.start;
			panel.percentSize = panel.isElastic ? panel.size * 100 / nElasticSize : 0; 
		}
	}
	
	this._initSizes = function()
	{
		this._iterate(function(panel, nLayoutOrigin, nLayoutSize, nFixSizes){
			if (panel.isElastic)
			{
				var nSize = Math.round(panel.percentSize * (nLayoutSize - nFixSizes) / 100);
				panel.end = panel.start + nSize;
			}
			else{
				// we use the variable conveyed via options
				if (!panel["size"])
					Utils.asserFailed("asser-failed: a fix size panel needs a size specification");
				panel.end = panel.start + panel.size;
			}
		});
	}
	
	this._initSizes();
	
	this._initCSS = function()
	{
		this._iterate(function(panel, nLayoutOrigin, nLayoutSize, nFixSize, bLastPanel)
		{
			var htmlPanel = angular.element(panel.hPanel);
			htmlPanel.css(sStartProp, panel.pstart + "%");
			htmlPanel.css(sEndProp, (100 - panel.pend) + "%");
			if (!bLastPanel){
				var htmlSplitBar  = angular.element(hSplitBarTemplate);
				htmlSplitBar.css(sStartProp, panel.pend + "%");
				htmlPanel.after(htmlSplitBar);
			}
		});
	}
	
	/**
	 * this is needed to prevent proportional resizing of the split-bars. 
	 */
	this.onResize = function()
	{
		
	}
	
		
	this.updateSplitBarPos = function(hSplitBar, mousePos)
	{
		this._iterate(function(panel, nLayoutOrigin, nLayoutSize, nFixSize, bLastPanel)
		{
			var clientRect = panel.hPanel.getBoundingClientRect();
			panel.start = clientRect[sStartProp] - nLayoutOrigin;
			panel.end = panel.start + clientRect[sSizeProp];
			
			mousePos -= (UISplitBar.SIZE >> 1)
			if (mousePos < nLayoutOrigin)
				mousePos = nLayoutOrigin;
			if (mousePos >= nLayoutOrigin + nLayoutSize - UISplitBar.SIZE)
				mousePos = nLayoutOrigin + nLayoutSize - UISplitBar.SIZE - 1;

			if (panel.hPanel.nextSibling == hSplitBar){
				panel.end = mousePos - nLayoutOrigin;
			}
			else if (panel.hPanel.previousElementSibling == hSplitBar){
				panel.start = mousePos - nLayoutOrigin;
			}
		});
				
		this._iterate(function(panel, nLayoutOrigin, nLayoutSize, nFixSize, bLastPanel)
		{
			if (panel.hPanel.nextSibling === hSplitBar){
				panel.hPanel.style[sEndProp] = (100 - panel.pend) + "%";
				hSplitBar.style[sStartProp] = panel.pend + "%";
			}
			else if (panel.hPanel.previousElementSibling === hSplitBar)
			{
				panel.hPanel.style[sStartProp] = panel.pstart + "%";
			}
		});
	}
}

function LayoutPanel(hPanel, sSize)
{
	this.isElastic = false;
	this.size = 0;
	this.percentSize = 0;
	this.hPanel = hPanel;
	
	angular.element(hPanel).addClass("stretch viewport");
	
	this._readSize = function(sSize)
	{
		if (typeof sSize === "number"){
			this.size = sSize;
			return;
		}
		if (typeof sSize !== 'string')
			Utils.assertFailed("Expecting number or string but got instead <" + typeof sSize + ">");
		
		var nPos = sSize.indexOf("%");
		if (nPos < 0)
			Utils.assertFailed("Expecting either a number or a number with a % specifier but got instead <" + sSize + ">");
		if (nPos != sSize.length - 1)
			Utils.assertFailed(sSize + " is not a valid size specification. '%' is expected as the last character of the size");
		
		this.isElastic = true;
		this.percentSize = Utils.parseInt(sSize);
	}
	
	if (sSize != null)
		this._readSize(sSize);
}


var lastTime = 0;
var vendors = [ 'ms', 'moz', 'webkit', 'o' ];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
	window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
	window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame']
			|| window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
	window.requestAnimationFrame = function(callback) {
		
		var currTime = new Date().getTime();
		var timeToCall = Math.max(0, 16 - (currTime - lastTime));
		var id = window.setTimeout(function() {
			callback(currTime + timeToCall);
		}, timeToCall);
		lastTime = currTime + timeToCall;
		return id;
	};
}
if (!window.cancelAnimationFrame) {
	window.cancelAnimationFrame = function(id) {
		clearTimeout(id);
	};
}