/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module('axelor.ui');

/**
 * The Form widget.
 *
 */
ui.formWidget('Form', {

	priority: 100,
	
	css: "dynamic-form",
	
	scope: false,
	
	compile: function(element, attrs) {

		element.hide();
		element.find('[x-field],[data-field]').each(function(){

			var elem = $(this),
				name = elem.attr('x-field') || elem.attr('data-field');

			if (name && elem.attr('ui-button') === undefined) {
				if (!elem.attr('ng-model')) {
					elem.attr('ng-model', 'record.' + name);
				}
				if (!elem.attr('ng-required')) {
					// always attache a required validator to make
					// dynamic `required` attribute change effective
					elem.attr('ng-required', false);
				}
			}
		});

		return ui.formCompile.apply(this, arguments);
	},
	
	link: function(scope, element, attrs, controller) {
		
		element.on('submit', function(e) {
			e.preventDefault();
		});

		scope.$watch('record', function(rec, old) {
			if (element.is(':visible')) {
				return;
			}
			scope.ajaxStop(function() {
				element.show();
				axelor.$adjustSize();
			});
		});
	}
});

/**
 * This directive is used filter $watch on scopes of inactive tabs.
 *
 */
ui.directive('uiTabGate', function() {

	return {
		
		compile: function compile(tElement, tAttrs) {
			
			return {
				pre: function preLink(scope, element, attrs) {
					scope.$watchChecker(function(current) {
						if (current.tabSelected === undefined) {
							return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
						}
						return current.tabSelected;
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes of hidden forms.
 *
 */
ui.directive('uiFormGate', function() {

	return {
		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					var parent = null;
					scope.$watchChecker(function(current) {
						if (scope.tabSelected === false) {
							return false;
						}
						if (parent === null) {
							parent = element.parents('[ui-show]:first');
						}
						// hack for hidden nested editors (#2173)
						if (scope.$$forceWatch) {
							return true;
						}
						return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes based on some condition.
 *
 */
ui.directive('uiWatchIf', ['$parse', function($parse) {
	
	return {
		compile: function compile(tElement, tAttrs) {
			return {
				pre: function preLink(scope, element, attrs) {
					var value = false,
						expression = $parse(attrs.uiWatchIf);
					
					scope.$watchChecker(function (current) {
						if (current === scope) {
							return value = expression(scope);
						}
						return value;
					});
				}
			};
		}
	};
}]);

function toBoolean(value) {
	if (value && value.length !== 0) {
		var v = angular.lowercase("" + value);
		value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
	} else {
		value = false;
	}
	return value;
}

/**
 * This directive is used to speedup uiFormGate.
 */
ui.directive('uiShow', function() {

	return {
		scope: true, // create new scope to always watch the expression
		link: function link(scope, element, attrs) {
			scope.$$shouldWatch = true;
			scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
				element.css('display', toBoolean(value) ? '' : 'none').toggleClass('ui-hide', !toBoolean(value));
			});
		}
	};
});

ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {

	function isValid(scope, name) {
		if (!name) return scope.isValid();
		var ctrl = scope.form;
		if (ctrl) {
			ctrl = ctrl[name];
		}
		if (ctrl) {
			return ctrl.$valid;
		}
	}
	
	function withContext(scope, record) {
		var values = _.extend({}, record);
		if (scope._context) {
			values = _.extend({}, scope._context, values);
		}
		return _.extend(values, {
			$user: axelor.config['user.login'],
			$group: axelor.config['user.group']
		});
	}
	
	function handleCondition(scope, field, attr, condition, negative) {

		if (!condition) {
			return;
		}

		scope.$on("on:record-change", function(e, rec, force) {
			if (rec === scope.record || force) {
				handle(rec);
			}
		});
		scope.$on("on:grid-selection-change", function(e, context) {
			handle(context);
		});

		scope.$watch("isReadonly()", watcher);
		scope.$watch("isRequired()", watcher);
		scope.$watch("isValid()", watcher);
		
		var expr = $parse(condition);

		function watcher(current, old) {
			var rec = scope.record;
			if (rec === undefined && current === old) return;
			if (rec === undefined && scope.getContext) {
				rec = scope.getContext();
			}
			handle(rec);
		}

		function handle(rec) {
			var value;
			try {
				value = !!axelor.$eval(scope, expr, withContext(scope, rec));
			} catch (e) {}
			scope.attr(attr, negative ? !value : value);
		}
	}
	
	function handleHilites(scope, field) {
		if (!field || _.isEmpty(field.hilites)) {
			return;
		}
		
		var hilites = field.hilites || [];
		var exprs = _.map(_.pluck(hilites, 'condition'), $parse);

		function handle(rec) {
			for (var i = 0; i < hilites.length; i++) {
				var hilite = hilites[i];
				var expr = exprs[i];
				var value = false;
				try {
					value = axelor.$eval(scope, expr, withContext(scope, rec));
				} catch (e) {}
				if (value) {
					return scope.attr('highlight', {
						hilite: hilite,
						passed: value
					});
				}
			}
			return scope.attr('highlight', {});
		}
		
		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}
	
	function handleBind(scope, field) {
		
		if (!field.bind || !field.name) {
			return;
		}
		
		var expr = $interpolate(field.bind);
		var last = null;

		function handle(rec) {
			var value;
			try {
				value = expr(withContext(scope, rec));
				if (value.length === 0) {
					value = null;
				}
			} catch (e) {}

			if (scope.setValue && scope.record && last !== value) {
				scope.setValue(last = value);
			}
		}
		
		scope.$on("on:record-change", function(e, rec) {
			if (rec && rec === scope.record) {
				handle(rec);
			}
		});
	}
	
	function handleFor(scope, field, attr, conditional, negative) {
		if (field[conditional]) {
			handleCondition(scope, field, attr, field[conditional], negative);
		}
	}
	
	function handleForField(scope) {
		var field = scope.field;
		if (!field) return;
		handleFor(scope, field, "valid", "validIf");
		handleFor(scope, field, "hidden", "hideIf");
		handleFor(scope, field, "hidden", "showIf", true);
		handleFor(scope, field, "readonly", "readonlyIf");
		handleFor(scope, field, "required", "requiredIf");
		handleFor(scope, field, "collapse", "collapseIf");
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canView", "canView");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canRemove", "canRemove");
		handleFor(scope, field, "canSelect", "canSelect");
		handleHilites(scope, field);
		handleBind(scope, field);
	}
	
	function handleForView(scope) {
		var field = scope.schema;
		if (!field) return;
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canSave", "canSave");
		handleFor(scope, field, "canCopy", "canCopy");
		handleFor(scope, field, "canDelete", "canDelete");
		handleFor(scope, field, "canAttach", "canAttach");
	}
	
	return function(scope, element, attrs) {
		scope.$evalAsync(function() {
			if (element.is('[ui-form]')) {
				return handleForView(scope);
			}
			handleForField(scope);
		});
	};
}]);

})();
