/**
 * EGroupware eTemplate2 - JS Favorite widget
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package etemplate
 * @subpackage api
 * @link https://www.egroupware.org
 * @author Nathan Gray
 * @copyright Nathan Gray 2013
 */

/*egw:uses
	et2_dropdown_button;
	et2_extension_nextmatch;
*/

import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {et2_INextmatchHeader} from "./et2_extension_nextmatch";
import {et2_dropdown_button} from "./et2_widget_dropdown_button";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {egw, egw_getFramework} from "../jsapi/egw_global";
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';

/**
 * Favorites widget, designed for use with a nextmatch widget
 *
 * The primary control is a split/dropdown button.  Clicking on the left side of the button filters the
 * nextmatch list by the user's default filter.  The right side of the button gives a list of
 * saved filters, pulled from preferences.  Clicking a filter from the dropdown list sets the
 * filters as saved.
 *
 * Favorites can also automatically be shown in the sidebox, using the special ID favorite_sidebox.
 * Use the following code to generate the sidebox section:
 *  display_sidebox($appname,lang('Favorites'),array(
 *	array(
 *		'no_lang' => true,
 *		'text'=>'<span id="favorite_sidebox"/>',
 *		'link'=>false,
 *		'icon' => false
 *	)
 * ));
 * This sidebox list will be automatically generated and kept up to date.
 *
 *
 * Favorites are implemented by saving the values for [column] filters.  Filters are stored
 * in preferences, with the name favorite_<name>.  The favorite favorite used for clicking on
 * the filter button is stored in nextmatch-<columnselection_pref>-favorite.
 *
 * @augments et2_dropdown_button
 */
export class et2_favorites extends et2_dropdown_button implements et2_INextmatchHeader
{
	static readonly _attributes : any = {
		"default_pref": {
			"name": "Default preference key",
			"type": "string",
			"description": "The preference key where default favorite is stored (not the value)"
		},
		"sidebox_target": {
			"name": "Sidebox target",
			"type": "string",
			"description": "ID of element to insert favorite list into",
			"default": "favorite_sidebox"
		},
		"app": {
			"name": "Application",
			"type": "string",
			"description": "Application to show favorites for"
		},
		"filters": {
			"name": "Extra filters",
			"type": "any",
			"description": "Array of extra filters to include in the saved favorite"
		},

		// These are particular to favorites
		id: {"default": "favorite"},
		label: {"default": ""},
		label_updates: { "default": false},
		image: {"default": egw().image('fav_filter')},
		statustext: {"default": "Favorite queries", "type": "string"}
	};

	// Some convenient variables, used in closures / event handlers
	header = null;
	nextmatch = null;
	public static readonly PREFIX = "favorite_";
	private stored_filters: {};
	private favSortedList : any = null;
	private sidebox_target : JQuery = null;
	private preferred;
	static is_admin : boolean;
	private filters : any;

	// If filter was set server side, we need to remember it until nm is created
	nm_filter = false;

	/**
	 * Constructor
	 *
	 * @memberOf et2_favorites
	 */
	constructor(_parent?, _attrs? : WidgetConfig, _child? : object)
	{
		super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_favorites._attributes, _child || {}));
		this.sidebox_target = jQuery("#"+this.options.sidebox_target);
		if(this.sidebox_target.length == 0 && egw_getFramework() != null)
		{
			let egw_fw = egw_getFramework();
			this.sidebox_target = jQuery("#"+this.options.sidebox_target,egw_fw.sidemenuDiv);
		}
		// Store array of sorted items
		this.favSortedList = ['blank'];

		let apps = egw().user('apps');
		et2_favorites.is_admin = (typeof apps['admin'] != "undefined");

		// Make sure we have an app
		if(!this.options.app)
		{
			this.options.app = this.getInstanceManager().app;
		}

		this.stored_filters = this.load_favorites(this.options.app);

		this.preferred = egw.preference(this.options.default_pref,this.options.app);
		if(!this.preferred || typeof this.stored_filters[this.preferred] == "undefined")
		{
			this.preferred = "blank";
		}

		// It helps to have the ID properly set before we get too far
		this.set_id(this.id);

		this.init_filters(this);

		this.menu.addClass("favorites");

		// Set the default (button) value
		this.set_value(this.preferred,true);

		let self = this;

		// Add a listener on the radio buttons to set default filter
		jQuery(this.menu).on("click","input:radio", function(event){
			// Don't do the menu
			event.stopImmediatePropagation();

			// Save as default favorite - used when you click the button
			self.egw().set_preference(self.options.app,self.options.default_pref,jQuery(this).val());
			self.preferred = jQuery(this).val();

			// Update sidebox, if there
			if(self.sidebox_target.length)
			{
				jQuery("div.ui-icon-heart", self.sidebox_target)
					.replaceWith("<div class='sideboxstar'/>");
				jQuery("li[data-id='"+self.preferred+"'] div.sideboxstar",self.sidebox_target)
					.replaceWith("<div class='ui-icon ui-icon-heart'/>");
			}

			// Close the menu
			self.menu.hide();

			// Some user feedback
			self.button.addClass("ui-state-active", 500,"swing",function(){
				self.button.removeClass("ui-state-active",2000);
			});
		});

		//Sort DomNodes of sidebox fav. menu
		let sideBoxDOMNodeSort = function (_favSList) {
			let favS = jQuery.isArray(_favSList)?_favSList.slice(0).reverse():[];

			for (let i=0; i < favS.length;i++)
			{
				self.sidebox_target.children().find('[data-id$="' + favS[i] + '"]').prependTo(self.sidebox_target.children());
			}
		};

		/**
		 * todo (@todo-jquery-ui): the sorting does not work at the moment becuase of jquery-ui menu being used in order to create dropdown
		 * buttons menu. Once we replace the et2_widget_dropdown_button with web component this should be adapted
		 * and working again.
		 **/
		let sortablejs = Sortable.create(this.menu[0], {
			ghostClass: 'ui-fav-sortable-placeholder',
			draggable: 'li:not([data-id$="add"])',
			delay: 25,
			dataIdAttr:'data-id',
			onSort: function(event){
				self.favSortedList  = sortablejs.toArray();
				self.egw.set_preference(self.options.app,'fav_sort_pref', self.favSortedList );
				sideBoxDOMNodeSort(self.favSortedList);
			}
		});

		// Add a listener on the delete to remove
		this.menu.on("click","div.ui-icon-trash", app[self.options.app], function() {
			// App instance might not be ready yet, so don't bind directly
			app[self.options.app].delete_favorite.apply(this,arguments);
		})
			// Wrap and unwrap because jQueryUI styles use a parent, and we don't want to change the state of the menu item
			// Wrap in a span instead of a div because div gets a border
			.on("mouseenter","div.ui-icon-trash", function() {jQuery(this).wrap("<span class='ui-state-active'/>");})
			.on("mouseleave","div.ui-icon-trash", function() {jQuery(this).unwrap();});

		// Trigger refresh of menu options now that events are registered
		// to update sidebox
		if(this.sidebox_target.length > 0)
		{
			this.init_filters(this);
		}
	}

	/**
	 * Load favorites from preferences
	 *
	 * @param app String Load favorites from this application
	 */
	load_favorites(app)
	{

		// Default blank filter
		let stored_filters : any = {
			'blank': {
				name: this.egw().lang("No filters"),
				state: {}
			}
		};

		// Load saved favorites
		let preferences : any = egw.preference("*",app);
		for(let pref_name in preferences)
		{
			if(pref_name.indexOf(et2_favorites.PREFIX) == 0 && typeof preferences[pref_name] == 'object')
			{
				let name = pref_name.substr(et2_favorites.PREFIX.length);
				stored_filters[name] = preferences[pref_name];
				// Keep older favorites working - they used to store nm filters in 'filters',not state
				if(preferences[pref_name]["filters"])
				{
					stored_filters[pref_name]["state"] = preferences[pref_name]["filters"];
				}
			}
			if (pref_name == 'fav_sort_pref')
			{
				this.favSortedList = preferences[pref_name];
				//Make sure sorted list is always an array, seems some old fav are not array
				if (!jQuery.isArray(this.favSortedList)) this.favSortedList = this.favSortedList.split(',');
			}
		}
		if(typeof stored_filters == "undefined" || !stored_filters)
		{
			stored_filters = {};
		}
		else
		{
			for(let name in stored_filters)
			{
				if (this.favSortedList.indexOf(name) < 0)
				{
					this.favSortedList.push(name);
				}
			}
			this.egw().set_preference (this.options.app,'fav_sort_pref',this.favSortedList);
			if (this.favSortedList.length > 0)
			{
				let sortedListObj = {};

				for (let i=0; i < this.favSortedList.length; i++)
				{
					if (typeof stored_filters[this.favSortedList[i]] != 'undefined')
					{
						sortedListObj[this.favSortedList[i]] = stored_filters[this.favSortedList[i]];
					}
					else
					{
						this.favSortedList.splice(i,1);
						this.egw().set_preference (this.options.app,'fav_sort_pref',this.favSortedList);
					}
				}
				stored_filters = jQuery.extend(sortedListObj,stored_filters);
			}
		}
		return stored_filters;
	}

	// Create & set filter options for dropdown menu
	init_filters(widget, filters?)
	{
		if(typeof filters == "undefined")
		{
			filters = this.stored_filters;
		}

		let options = {};
		for(let name in filters)
		{
			options[name] = "<input type='radio' name='"+this.internal_ids.menu+"[button][favorite]' value='"+name+"' title='" +
				this.egw().lang('Set as default') + "'/>"+
				(filters[name].name != undefined ? filters[name].name : name) +
				(filters[name].group != false && !et2_favorites.is_admin || name == 'blank' ? "" :
					"<div class='ui-icon ui-icon-trash' title='" + this.egw().lang('Delete') + "'/>");
		}

		// Only add 'Add current' if we have a nextmatch
		if(this.nextmatch)
		{
			options["add"] = "<img src='"+this.egw().image("new") +"'/>"+this.egw().lang('Add current');
		}
		widget.set_select_options.call(widget,options);

		// Set radio to current value
		jQuery("input[value='"+ this.preferred +"']:radio", this.menu).attr("checked",1);
	}

	set_nm_filters(filters)
	{
		if(this.nextmatch)
		{
			this.nextmatch.applyFilters(filters);
		}
		else
		{
			console.log(filters);
		}
	}

	onclick(node)
	{
		// Apply preferred filter - make sure it's an object, and not a reference
		if(this.preferred && this.stored_filters[this.preferred])
		{
			// use app[appname].setState if available to allow app to overwrite it (eg. change to non-listview in calendar)
			if (typeof app[this.options.app] != 'undefined')
			{
				app[this.options.app].setState(this.stored_filters[this.preferred]);
			}
			else
			{
				this.set_nm_filters(jQuery.extend({},this.stored_filters[this.preferred].state));
			}
		}
		else
		{
			alert(this.egw().lang("No default set"));
		}
	}

	// Apply the favorite when you pick from the list
	change(selected_node)
	{
		this.value = jQuery(selected_node).attr("data-id");
		if(this.value == "add" && this.nextmatch)
		{
			// Get current filters
			let current_filters = jQuery.extend({},this.nextmatch.activeFilters);

			// Add in extras
			for(let extra in this.options.filters)
			{
				// Don't overwrite what nm has, chances are nm has more up-to-date value
				if(typeof current_filters == 'undefined')
				{
					current_filters[extra] = this.nextmatch.options.settings[extra];
				}
			}

			// Skip columns for now
			delete current_filters.selcolumns;

			// Add in application's settings
			if(this.filters != true)
			{
				for(let i = 0; i < this.filters.length; i++)
				{
					current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]];
				}
			}

			// Call framework
			app[this.options.app].add_favorite(current_filters);

			// Reset value
			this.set_value(this.preferred,true);
		}
		else if (this.value == 'blank')
		{
			// Reset filters when select no filters
			this.set_nm_filters({});
		}
	}

	set_value(filter_name, parent? : boolean) : void | boolean
	{
		if(parent)
		{
			return super.set_value(filter_name);
		}

		if(filter_name == 'add') return false;

		app[this.options.app].setState(this.stored_filters[filter_name]);
		return false;
	}

	getValue()
	{
		return null;
	}

	/**
	 * Set the nextmatch to filter
	 * From et2_INextmatchHeader interface
	 *
	 * @param {et2_nextmatch} nextmatch
	 */
	setNextmatch(nextmatch)
	{
		this.nextmatch = nextmatch;

		if(this.nm_filter)
		{
			this.set_value(this.nm_filter);
			this.nm_filter = false;
		}

		// Re-generate filter list so we can add 'Add current'
		this.init_filters(this);
	}
}
et2_register_widget(et2_favorites, ["favorites"]);