/**
 * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
 * This file is licensed under the Affero General Public License version 3 or later.
 * See the COPYING-README file.
 */

OCA = OCA || {};

(function() {

	/**
	 * @classdesc main view class. It takes care of tab-unrelated control
	 * elements (status bar, control buttons) and does or requests configuration
	 * checks. It also manages the separate tab views.
	 *
	 * @constructor
	 */
	var WizardView = function() {};

	WizardView.prototype = {
		/** @constant {number} */
		STATUS_ERROR: 0,
		/** @constant {number} */
		STATUS_INCOMPLETE: 1,
		/** @constant {number} */
		STATUS_SUCCESS: 2,
		/** @constant {number} */
		STATUS_UNTESTED: 3,

		/**
		 * initializes the instance. Always call it after creating the instance.
		 */
		init: function () {
			this.tabs = {};
			this.tabs.server = new OCA.LDAP.Wizard.WizardTabElementary();
			this.$settings = $('#ldapSettings');
			this.$saveSpinners = $('.ldap_saving');
			this.saveProcesses = 0;
			_.bindAll(this, 'onTabChange', 'onTestButtonClick');
		},

		/**
		 * applies click events to the forward and backward buttons
		 */
		initControls: function() {
			var view = this;
			$('.ldap_action_continue').click(function(event) {
				event.preventDefault();
				view._controlContinue(view);
			});

			$('.ldap_action_back').click(function(event) {
				event.preventDefault();
				view._controlBack(view);
			});

			$('.ldap_action_test_connection').click(this.onTestButtonClick);
		},

		/**
		 * registers a tab
		 *
		 * @param {OCA.LDAP.Wizard.WizardTabGeneric} tabView
		 * @param {string} index
		 * @returns {boolean}
		 */
		registerTab: function(tabView, index) {
			if( _.isUndefined(this.tabs[index])
				&& tabView instanceof OCA.LDAP.Wizard.WizardTabGeneric
			) {
				this.tabs[index] = tabView;
				this.tabs[index].setModel(this.configModel);
				return true;
			}
			return false;
		},

		/**
		 * checks certain config values for completeness and depending on them
		 * enables or disables non-elementary tabs.
		 */
		basicStatusCheck: function(view) {
			var host  = view.configModel.configuration.ldap_host;
			var port  = view.configModel.configuration.ldap_port;
			var base  = view.configModel.configuration.ldap_base;
			var agent = view.configModel.configuration.ldap_dn;
			var pwd   = view.configModel.configuration.ldap_agent_password;

			if((host && port  && base) && ((!agent && !pwd) || (agent && pwd))) {
				view.enableTabs();
			} else {
				view.disableTabs();
			}
		},

		/**
		 * if the configuration is sufficient the model is being request to
		 * perform a configuration test. Otherwise, the status indicator is
		 * being updated with the status "incomplete"
		 */
		functionalityCheck: function() {
			// this method should be called only if necessary, because it may
			// cause an LDAP request!
			var host        = this.configModel.configuration.ldap_host;
			var port        = this.configModel.configuration.ldap_port;
			var base        = this.configModel.configuration.ldap_base;
			var userFilter  = this.configModel.configuration.ldap_userlist_filter;
			var loginFilter = this.configModel.configuration.ldap_login_filter;

			if(host && port && base && userFilter && loginFilter) {
				this.configModel.requestConfigurationTest();
			} else {
				this._updateStatusIndicator(this.STATUS_INCOMPLETE);
			}
		},

		/**
		 * will request a functionality check if one of the related configuration
		 * settings was changed.
		 *
		 * @param {ConfigSetPayload|Object} [changeSet]
		 */
		considerFunctionalityCheck: function(changeSet) {
			var testTriggers = [
				'ldap_host', 'ldap_port', 'ldap_dn', 'ldap_agent_password',
				'ldap_base', 'ldap_userlist_filter', 'ldap_login_filter'
			];
			for(var key in changeSet) {
				if($.inArray(key, testTriggers) >= 0) {
					this.functionalityCheck();
					return;
				}
			}
		},

		/**
		 * keeps number of running save processes and shows a spinner if
		 * necessary
		 *
		 * @param {WizardView} [view]
		 * @listens ConfigModel#setRequested
		 */
		onSetRequested: function(view) {
			view.saveProcesses += 1;
			if(view.saveProcesses === 1) {
				view.showSaveSpinner();
			}
		},

		/**
		 * keeps number of running save processes and hides the spinner if
		 * necessary. Also triggers checks, to adjust tabs state and status bar.
		 *
		 * @param {WizardView} [view]
		 * @param {ConfigSetPayload} [result]
		 * @listens ConfigModel#setCompleted
		 */
		onSetRequestDone: function(view, result) {
			if(view.saveProcesses > 0) {
				view.saveProcesses -= 1;
				if(view.saveProcesses === 0) {
					view.hideSaveSpinner();
				}
			}

			view.basicStatusCheck(view);
			var param = {};
			param[result.key] = 1;
			view.considerFunctionalityCheck(param);
		},

		/**
		 * Base DN test results will arrive here
		 *
		 * @param {WizardTabElementary} view
		 * @param {FeaturePayload} payload
		 */
		onDetectionTestCompleted: function(view, payload) {
			if(payload.feature === 'TestBaseDN') {
				if(payload.data.status === 'success') {
					var objectsFound = parseInt(payload.data.changes.ldap_test_base, 10);
					if(objectsFound > 0) {
						view._updateStatusIndicator(view.STATUS_SUCCESS);
						return;
					}
				}
				view._updateStatusIndicator(view.STATUS_ERROR);
				OC.Notification.showTemporary(t('user_ldap', 'The Base DN appears to be wrong'));
			}
		},

		/**
		 * updates the status indicator based on the configuration test result
		 *
		 * @param {WizardView} [view]
		 * @param {ConfigTestPayload} [result]
		 * @listens ConfigModel#configurationTested
		 */
		onTestCompleted: function(view, result) {
			if(result.isSuccess) {
				view.configModel.requestWizard('ldap_test_base');
			} else {
				view._updateStatusIndicator(view.STATUS_ERROR);
			}
		},

		/**
		 * triggers initial checks upon configuration loading to update status
		 * controls
		 *
		 * @param {WizardView} [view]
		 * @listens ConfigModel#configLoaded
		 */
		onConfigLoaded: function(view) {
			view._updateStatusIndicator(view.STATUS_UNTESTED);
			view.basicStatusCheck(view);
			view.functionalityCheck();
		},

		/**
		 * reacts on attempts to switch to a different tab
		 *
		 * @param {object} event
		 * @param {object} ui
		 * @returns {boolean}
		 */
		onTabChange: function(event, ui) {
			if(this.saveProcesses > 0) {
				return false;
			}

			var newTabID = ui.newTab[0].id;
			if(newTabID === '#ldapWizard1') {
				newTabID = 'server';
			}
			var oldTabID = ui.oldTab[0].id;
			if(oldTabID === '#ldapWizard1') {
				oldTabID = 'server';
			}
			if(!_.isUndefined(this.tabs[newTabID])) {
				this.tabs[newTabID].isActive = true;
				this.tabs[newTabID].onActivate();
			} else {
				console.warn('Unreferenced activated tab ' + newTabID);
			}
			if(!_.isUndefined(this.tabs[oldTabID])) {
				this.tabs[oldTabID].isActive = false;
			} else {
				console.warn('Unreferenced left tab ' + oldTabID);
			}

			if(!_.isUndefined(this.tabs[newTabID])) {
				this._controlUpdate(this.tabs[newTabID].tabIndex);
			}
		},

		/**
		 * triggers checks upon configuration updates to keep status controls
		 * up to date
		 *
		 * @param {WizardView} [view]
		 * @param {object} [changeSet]
		 * @listens ConfigModel#configUpdated
		 */
		onConfigUpdated: function(view, changeSet) {
			view.basicStatusCheck(view);
			view.considerFunctionalityCheck(changeSet);
		},

		/**
		 * requests a configuration test
		 */
		onTestButtonClick: function() {
			this.configModel.requestWizard('ldap_action_test_connection', {ldap_serverconfig_chooser: this.configModel.configID});
		},

		/**
		 * sets the model instance and registers event listeners
		 *
		 * @param {OCA.LDAP.Wizard.ConfigModel} [configModel]
		 */
		setModel: function(configModel) {
			/** @type {OCA.LDAP.Wizard.ConfigModel} */
			this.configModel = configModel;
			for(var i in this.tabs) {
				this.tabs[i].setModel(configModel);
			}

			// make sure this is definitely run after tabs did their work, order is important here
			// for now this works, because tabs are supposed to register their listeners in their
			// setModel() method.
			// alternative: make Elementary Tab a Publisher as well.
			this.configModel.on('configLoaded', this.onConfigLoaded, this);
			this.configModel.on('configUpdated', this.onConfigUpdated, this);
			this.configModel.on('setRequested', this.onSetRequested, this);
			this.configModel.on('setCompleted', this.onSetRequestDone, this);
			this.configModel.on('configurationTested', this.onTestCompleted, this);
			this.configModel.on('receivedLdapFeature', this.onDetectionTestCompleted, this);
		},

		/**
		 * enables tab and navigation buttons
		 */
		enableTabs: function() {
			//do not use this function directly, use basicStatusCheck instead.
			if(this.saveProcesses === 0) {
				$('.ldap_action_continue').removeAttr('disabled');
				$('.ldap_action_back').removeAttr('disabled');
				this.$settings.tabs('option', 'disabled', []);
			}
		},

		/**
		 * disables tab and navigation buttons
		 */
		disableTabs: function() {
			$('.ldap_action_continue').attr('disabled', 'disabled');
			$('.ldap_action_back').attr('disabled', 'disabled');
			this.$settings.tabs('option', 'disabled', [1, 2, 3, 4, 5]);
		},

		/**
		 * shows a save spinner
		 */
		showSaveSpinner: function() {
			this.$saveSpinners.removeClass('hidden');
			$('#ldap *').addClass('save-cursor');
		},

		/**
		 * hides the save spinner
		 */
		hideSaveSpinner: function() {
			this.$saveSpinners.addClass('hidden');
			$('#ldap *').removeClass('save-cursor');
		},

		/**
		 * performs a config load request to the model
		 *
		 * @param {string} [configID]
		 * @private
		 */
		_requestConfig: function(configID) {
			this.configModel.load(configID);
		},

		/**
		 * bootstraps the visual appearance and event listeners, as well as the
		 * first config
		 */
		render: function () {
			$('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'});
			this.$settings.tabs({});
			$('#ldapSettings button:not(.icon-default-style):not(.ui-multiselect)').button();
			$('#ldapSettings').tabs({ beforeActivate: this.onTabChange });

			this.initControls();
			this.disableTabs();

			this._requestConfig(this.tabs.server.getConfigID());
		},

		/**
		 * updates the status indicator / bar
		 *
		 * @param {number} [state]
		 * @private
		 */
		_updateStatusIndicator: function(state) {
			var $indicator = $('.ldap_config_state_indicator');
			var $indicatorLight = $('.ldap_config_state_indicator_sign');

			switch(state) {
				case this.STATUS_UNTESTED:
					$indicator.text(t('user_ldap',
						'Testing configuration…'
					));
					$indicator.addClass('ldap_grey');
					$indicatorLight.removeClass('error');
					$indicatorLight.removeClass('success');
					break;
				case this.STATUS_ERROR:
					$indicator.text(t('user_ldap',
						'Configuration incorrect'
					));
					$indicator.removeClass('ldap_grey');
					$indicatorLight.addClass('error');
					$indicatorLight.removeClass('success');
					break;
				case this.STATUS_INCOMPLETE:
					$indicator.text(t('user_ldap',
						'Configuration incomplete'
					));
					$indicator.removeClass('ldap_grey');
					$indicatorLight.removeClass('error');
					$indicatorLight.removeClass('success');
					break;
				case this.STATUS_SUCCESS:
					$indicator.text(t('user_ldap', 'Configuration OK'));
					$indicator.addClass('ldap_grey');
					$indicatorLight.removeClass('error');
					$indicatorLight.addClass('success');
					if(!this.tabs.server.isActive) {
						this.configModel.set('ldap_configuration_active', 1);
					}
					break;
			}
		},

		/**
		 * handles a click on the Back button
		 *
		 * @param {WizardView} [view]
		 * @private
		 */
		_controlBack: function(view) {
			var curTabIndex = view.$settings.tabs('option', 'active');
			if(curTabIndex == 0) {
				return;
			}
			view.$settings.tabs('option', 'active', curTabIndex - 1);
			view._controlUpdate(curTabIndex - 1);
		},

		/**
		 * handles a click on the Continue button
		 *
		 * @param {WizardView} [view]
		 * @private
		 */
		_controlContinue: function(view) {
			var curTabIndex = view.$settings.tabs('option', 'active');
			if(curTabIndex == 3) {
				return;
			}
			view.$settings.tabs('option', 'active', 1 + curTabIndex);
			view._controlUpdate(curTabIndex + 1);
		},

		/**
		 * updates the controls (navigation buttons)
		 *
		 * @param {number} [nextTabIndex] - index of the tab being switched to
		 * @private
		 */
		_controlUpdate: function(nextTabIndex) {
			if(nextTabIndex == 0) {
				$('.ldap_action_back').addClass('invisible');
				$('.ldap_action_continue').removeClass('invisible');
			} else
			if(nextTabIndex == 1) {
				$('.ldap_action_back').removeClass('invisible');
				$('.ldap_action_continue').removeClass('invisible');
			} else
			if(nextTabIndex == 2) {
				$('.ldap_action_continue').removeClass('invisible');
				$('.ldap_action_back').removeClass('invisible');
			} else
			if(nextTabIndex == 3) {
				$('.ldap_action_back').removeClass('invisible');
				$('.ldap_action_continue').addClass('invisible');
			}
		}
	};

	OCA.LDAP.Wizard.WizardView = WizardView;
})();
