/**
 * elFinder command prototype
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.command = function(fm) {
	"use strict";
	/**
	 * elFinder instance
	 *
	 * @type  elFinder
	 */
	this.fm = fm;
	
	/**
	 * Command name, same as class name
	 *
	 * @type  String
	 */
	this.name = '';
	
	/**
	 * Dialog class name
	 *
	 * @type  String
	 */
	this.dialogClass = '';

	/**
	 * Command icon class name with out 'elfinder-button-icon-'
	 * Use this.name if it is empty
	 *
	 * @type  String
	 */
	this.className = '';

	/**
	 * Short command description
	 *
	 * @type  String
	 */
	this.title = '';
	
	/**
	 * Linked(Child) commands name
	 * They are loaded together when tthis command is loaded.
	 * 
	 * @type  Array
	 */
	this.linkedCmds = [];
	
	/**
	 * Current command state
	 *
	 * @example
	 * this.state = -1; // command disabled
	 * this.state = 0;  // command enabled
	 * this.state = 1;  // command active (for example "fullscreen" command while elfinder in fullscreen mode)
	 * @default -1
	 * @type  Number
	 */
	this.state = -1;
	
	/**
	 * If true, command can not be disabled by connector.
	 * @see this.update()
	 *
	 * @type  Boolen
	 */
	this.alwaysEnabled = false;
	
	/**
	 * Do not change dirctory on removed current work directory
	 * 
	 * @type  Boolen
	 */
	this.noChangeDirOnRemovedCwd = false;
	
	/**
	 * If true, this means command was disabled by connector.
	 * @see this.update()
	 *
	 * @type  Boolen
	 */
	this._disabled = false;
	
	/**
	 * If true, this command is disabled on serach results
	 * 
	 * @type  Boolean
	 */
	this.disableOnSearch = false;
	
	/**
	 * Call update() when event select fired
	 * 
	 * @type  Boolean
	 */
	this.updateOnSelect = true;
	
	/**
	 * Sync toolbar button title on change
	 * 
	 * @type  Boolean
	 */
	this.syncTitleOnChange = false;

	/**
	 * Keep display of the context menu when command execution
	 * 
	 * @type  Boolean
	 */
	this.keepContextmenu = false;
	
	/**
	 * elFinder events defaults handlers.
	 * Inside handlers "this" is current command object
	 *
	 * @type  Object
	 */
	this._handlers = {
		enable  : function() { this.update(void(0), this.value); },
		disable : function() { this.update(-1, this.value); },
		'open reload load sync'    : function() { 
			this._disabled = !(this.alwaysEnabled || this.fm.isCommandEnabled(this.name));
			this.update(void(0), this.value);
			this.change(); 
		}
	};
	
	/**
	 * elFinder events handlers.
	 * Inside handlers "this" is current command object
	 *
	 * @type  Object
	 */
	this.handlers = {};
	
	/**
	 * Shortcuts
	 *
	 * @type  Array
	 */
	this.shortcuts = [];
	
	/**
	 * Command options
	 *
	 * @type  Object
	 */
	this.options = {ui : 'button'};
	
	/**
	 * Callback functions on `change` event
	 * 
	 * @type  Array
	 */
	this.listeners = [];

	/**
	 * Prepare object -
	 * bind events and shortcuts
	 *
	 * @return void
	 */
	this.setup = function(name, opts) {
		var self = this,
			fm   = this.fm,
			setCallback = function(s) {
				var cb = s.callback || function(e) {
							fm.exec(self.name, void(0), {
							_userAction: true,
							_currentType: 'shortcut'
						});
					};
				s.callback = function(e) {
					var enabled, checks = {};
					if (self.enabled()) {
						if (fm.searchStatus.state < 2) {
							enabled = fm.isCommandEnabled(self.name);
						} else {
							jQuery.each(fm.selected(), function(i, h) {
								if (fm.optionsByHashes[h]) {
									checks[h] = true;
								} else {
									jQuery.each(fm.volOptions, function(id) {
										if (!checks[id] && h.indexOf(id) === 0) {
											checks[id] = true;
											return false;
										}
									});
								}
							});
							jQuery.each(checks, function(h) {
								enabled = fm.isCommandEnabled(self.name, h);
								if (! enabled) {
									return false;
								}
							});
						}
						if (enabled) {
							self.event = e;
							cb.call(self);
							delete self.event;
						}
					}
				};
			},
			i, s, sc;

		this.name      = name;
		this.title     = fm.messages['cmd'+name] ? fm.i18n('cmd'+name)
		               : ((this.extendsCmd && fm.messages['cmd'+this.extendsCmd]) ? fm.i18n('cmd'+this.extendsCmd) : name);
		this.options   = Object.assign({}, this.options, opts);
		this.listeners = [];
		this.dialogClass = 'elfinder-dialog-' + name;

		if (opts.shortcuts) {
			if (typeof opts.shortcuts === 'function') {
				sc = opts.shortcuts(this.fm, this.shortcuts);
			} else if (Array.isArray(opts.shortcuts)) {
				sc = opts.shortcuts;
			}
			this.shortcuts = sc || [];
		}

		if (this.updateOnSelect) {
			this._handlers.select = function() { this.update(void(0), this.value); };
		}

		jQuery.each(Object.assign({}, self._handlers, self.handlers), function(cmd, handler) {
			fm.bind(cmd, jQuery.proxy(handler, self));
		});

		for (i = 0; i < this.shortcuts.length; i++) {
			s = this.shortcuts[i];
			setCallback(s);
			!s.description && (s.description = this.title);
			fm.shortcut(s);
		}

		if (this.disableOnSearch) {
			fm.bind('search searchend', function() {
				self._disabled = this.type === 'search'? true : ! (this.alwaysEnabled || fm.isCommandEnabled(name));
				self.update(void(0), self.value);
			});
		}

		this.init();
	};

	/**
	 * Command specific init stuffs
	 *
	 * @return void
	 */
	this.init = function() {};

	/**
	 * Exec command
	 *
	 * @param  Array         target files hashes
	 * @param  Array|Object  command value
	 * @return jQuery.Deferred
	 */
	this.exec = function(files, opts) { 
		return jQuery.Deferred().reject(); 
	};
	
	this.getUndo = function(opts, resData) {
		return false;
	};
	
	/**
	 * Return true if command disabled.
	 *
	 * @return Boolen
	 */
	this.disabled = function() {
		return this.state < 0;
	};
	
	/**
	 * Return true if command enabled.
	 *
	 * @return Boolen
	 */
	this.enabled = function() {
		return this.state > -1;
	};
	
	/**
	 * Return true if command active.
	 *
	 * @return Boolen
	 */
	this.active = function() {
		return this.state > 0;
	};
	
	/**
	 * Return current command state.
	 * Must be overloaded in most commands
	 *
	 * @return Number
	 */
	this.getstate = function() {
		return -1;
	};
	
	/**
	 * Update command state/value
	 * and rize 'change' event if smth changed
	 *
	 * @param  Number  new state or undefined to auto update state
	 * @param  mixed   new value
	 * @return void
	 */
	this.update = function(s, v) {
		var state = this.state,
			value = this.value;

		if (this._disabled && this.fm.searchStatus === 0) {
			this.state = -1;
		} else {
			this.state = s !== void(0) ? s : this.getstate();
		}

		this.value = v;
		
		if (state != this.state || value != this.value) {
			this.change();
		}
	};
	
	/**
	 * Bind handler / fire 'change' event.
	 *
	 * @param  Function|undefined  event callback
	 * @return void
	 */
	this.change = function(c) {
		var cmd, i;
		
		if (typeof(c) === 'function') {
			this.listeners.push(c);			
		} else {
			for (i = 0; i < this.listeners.length; i++) {
				cmd = this.listeners[i];
				try {
					cmd(this.state, this.value);
				} catch (e) {
					this.fm.debug('error', e);
				}
			}
		}
		return this;
	};
	

	/**
	 * With argument check given files hashes and return list of existed files hashes.
	 * Without argument return selected files hashes.
	 *
	 * @param  Array|String|void  hashes
	 * @return Array
	 */
	this.hashes = function(hashes) {
		return hashes
			? jQuery.grep(Array.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) ? true : false; })
			: fm.selected();
	};
	
	/**
	 * Return only existed files from given fils hashes | selected files
	 *
	 * @param  Array|String|void  hashes
	 * @return Array
	 */
	this.files = function(hashes) {
		var fm = this.fm;
		
		return hashes
			? jQuery.map(Array.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) || null; })
			: fm.selectedFiles();
	};

	/**
	 * Wrapper to fm.dialog()
	 *
	 * @param  String|DOMElement  content
	 * @param  Object             options
	 * @return Object             jQuery element object
	 */
	this.fmDialog = function(content, options) {
		if (options.cssClass) {
			options.cssClass += ' ' + this.dialogClass;
		} else {
			options.cssClass = this.dialogClass;
		}
		return this.fm.dialog(content, options);
	};
};
