/*
PiAnoS (Picture Annotation System), A fingerprint examiner training tool
Copyright (C) 2011-2012 University of Lausanne, Switzerland
https://ips-labs.unil.ch/pianos

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

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/.
*/

/**
 * @constructor
 */
function ImageEditor(params)
{
	// ------------------------------
	// Constructor
	// ------------------------------
	
	params = params || {};
	if (!params) params = new Object();
	if (!params.divid) params.divid = 'imageeditor';
	if (!params.idPrefix) params.idPrefix = params.divid+'_';
	if (!params.cssPrefix) params.cssPrefix = 'imageeditor_';
	if (!params.image) alert('No image given !');
	if (!params.serverScript) params.serverScript = 'php/imageeditor.php';
	
	// ------------------------------
	// Public methods
	// ------------------------------
	
	this.init = function(startFilters)
	{
		// This is not the first time we are init()ialized, so first unbind the
		// previous events (the instance pointed to by the previous handler(s) 
		// is probably not the same as 'self', as it makes no sense to init()
		// twice the same ImageEditor instance).
		if (ImageEditor.resizeEventHandler)
		{
			$(window).unbind('resize', ImageEditor.resizeEventHandler);
			ImageEditor.resizeEventHandler = null;
		}

		var toolsParams = {
			cssPrefix: params.cssPrefix,
		};
		
		toolsToInstall = [
			[new ImageEditor_BrightnessContrast(toolsParams), ['common']],
			[new ImageEditor_Invert(toolsParams), ['common']],
		];

		buildUI();

		addFilter({type:'original', label:'Original image', url: $('#'+params.divid+' .'+mycss('image_view')+' img').get(0).src});
		
		if (startFilters)
		{
			for (var i in startFilters)
			{
				// The first filter is the original image, and it's been inserted already
				if (i == 0)
					continue;

				var filter = $.extend({}, startFilters[i]);

				// The filter is OK for its data part.
				assert(filter.type in tools && !filter.dom);

				// The binding to the tools is not correct OTOH, so we need to recreate it. 
				// Note that the anonymous function that is triggered on click on the 
				// 'add filter' button does exactly this (duplicate code?)
				filter.tool = tools[filter.type];
				filter.label = filter.tool.getLabel();
			
				// Trick the system into believing this is the active tool
				activeTool = filter.type;
				// And create the filter
				addFilter(filter);
				
				// Note: as a (nasty) side-effect of doing addFilter(), selectFilterByIndex()
				// will be called, thus selecting the filter AND the associated options panel.
				// If this is not desired, some refactoring must take place. I don't care now.
			}
		}
		
		//self.setActiveTool('brightness_contrast');

		layout();
		
		assert(ImageEditor.resizeEventHandler === null);
		ImageEditor.resizeEventHandler = function() {
			log('imgedit Window resize event');
			layout();
		};
		
		$(window).bind('resize', ImageEditor.resizeEventHandler);
	}

	this.setActiveTool = function(id, isLayerSelection)
	{
		var pane = $('.'+mycss('tools_options'));
		var buttons = views.tools.find('.'+mycss('tools_actions')+' a');
		
		if (activeTool)
		{
			 assert(activeTool in tools);
			 tools[activeTool].getPanel().detach();
			 pane.html('');
		}
		
		//log('Setting active tool to '+id);
		
		activeTool = id;

		if (activeTool)
		{
			assert(id in tools);
			
			var tool = tools[activeTool];
		
			var content = $('<div />');
			
			var helpMsg = tool.getHelp();
			var help1 = $('<div />')
				.addClass(mycss('tool_help')+' ui-state-default ui-corner-top ui-corner-bottom')
				.text(helpMsg[0])
				.append($('<a href="#">[more]</a>')
					.click(function(e) {
						e.preventDefault();
						var extended = $(this).parent().next();
						extended.toggle();
						if (extended.is(':visible'))
						{
							$(this).text('[less]');
							help1.removeClass('ui-corner-bottom');
						}
						else
						{
							$(this).text('[more]');
							help1.addClass('ui-corner-bottom');
						}
					})
				)
			;
			
			var help2 = $('<div />').addClass(mycss('tool_exthelp')+' ui-widget-content ui-corner-bottom').css('display','none');
			help2.append($('<span />').text(helpMsg[1]));
			content.append(help1).append(help2).append(tool.getPanel());
			
			pane.html(content);

			if (isLayerSelection)
			{
				buttons.filter('a[href=#add]').css('visibility','hidden');
				//buttons.filter('a[href=#update]').removeCSS('visibility');
			}
			else
			{
				buttons.filter('a[href=#add]').removeCSS('visibility');
				//buttons.filter('a[href=#update]').css('visibility','hidden');
			}
		}
		else
		{
			views.tools.find('.'+mycss('tools_actions')+' a').css('visibility','hidden');
			
			var showImageInfo = function()
			{
				assert(imageInfo && !activeTool && 'panel' in imageInfo);
				pane.html(imageInfo.panel);
			}
			
			var updateImageInfo = function()
			{
				assert(imageInfo && 'success' in imageInfo && !('panel' in imageInfo));
				//log('Updating image info');
				//log(imageInfo);
				var panel;
				if (imageInfo.success)
				{
					panel = $('<pre />').text(imageInfo.ret);
				}
				else
				{
					panel = $('<div />');
					panel.append($('<h3 />').text('Could not retrieve detailed information about the image'));
					panel.append($('<p />').text(imageInfo.message));
				}
				imageInfo.panel = panel;
				
				if (!activeTool)
					showImageInfo();
			}
			
			if (imageInfo)
			{
				showImageInfo();
			}
			else
			{
				var absImageUrl = $('#'+params.divid+' .'+mycss('image_view')+' img').get(0).src;
				$.ajax({
					type: 'get',
					url: params.serverScript+'?action=info&url='+encodeURIComponent(absImageUrl),
					dataType: 'json',
					success: function(data,a,b) {
						imageInfo = data;
						updateImageInfo();
					},
					error: function(data) {
						imageInfo = { success: false, message: 'HTTP error' };
						updateImageInfo();
					},
				});
			}
		}
	}
	
	this.getActiveTool = function()
	{
		return activeTool;
	}
	
	this.onFilterChanged = function(tool)
	{
		if (!activeTool || tools[activeTool].getName() != tool.getName())
			return;
			
		var active = views.filters.find('.'+mycss('filter')+'.ui-state-highlight');
		if (active.length != 1)
			return;
			
		var index = filters.length-1-views.filters.find('.'+mycss('filter')).index(active);
		assert(index >= 0 && filters.length);
		var filter = filters[index];
		
		if ('tool' in filter && tools[activeTool].getName() == filter.tool.getName())
			updateCurrentFilter();
	}
	
	this.pubLayout = function()
	{
		layout();
	}
	
	this.getFilters = function()
	{
		// We must return only the data part of the filter, not the complete one with
		// its bindings - the client does not need to know that, and further, we do not
		// want those objects to persist after the editor object is destroyed.
		var ret = [];
		for (var i in filters)
		{
			var filter = $.extend({}, filters[i]);
			delete filter.tool;
			delete filter.label;
			delete filter.dom;
			
			ret.push(filter);
		}
		return ret;
	}
	
	// ------------------------------
	// Private variables
	// ------------------------------
	
	var self = this;
	var views = {};
	var splitters = {};
	var filters = [];
	var tools = {};
	var toolsToInstall = null;
	var activeTool = null;
	var imageDirty = 1;
	var imageLoading = true;
	var imageInfo = null;
	
	// ------------------------------
	// Private functions
	// ------------------------------
	
	var divid = function(suffix)
	{
		var ret = '';
		if (params.idPrefix) ret += params.idPrefix;
		ret += suffix;
		return ret;
	}
	
	var mycss = function(suffix)
	{
		return params.cssPrefix + suffix;
	}

	var buildUI = function()
	{
		var mainDiv = $('#'+params.divid);
		assert(mainDiv.length == 1);
		mainDiv.addClass(mycss('main'));
		
		views.image = $('<div/>').addClass(mycss('image_view'));
		
		var img = $('<img />');
		img.attr('alt', 'image loading ...')
			.bind('load', function(e) { onImageLoad(true, e); } )
			.bind('error', function(e) { onImageLoad(false, e); })
			.attr('src', params.image.href);
		
		views.image.append(img);
		
		
		views.tools = $('<div />').addClass(mycss('tools_view'));
		var tabs = $('<div />').appendTo(views.tools);
		
		var ul = $('<ul>').appendTo(tabs);
		
		var addTab = function(id, title)
		{
			var a = $('<a />').attr('href', '#'+divid(id)).text(title).data(divid('data'), id);
			ul.append($('<li/>').append(a));
			var content = $('<div />').attr('id', divid(id)).data(divid('data'), id);
			tabs.append(content);
			return content;
		}
		
		var addTool = function(tabid, tool)
		{
			assert(tabid in allTabs);
			var p = allTabs[tabid];
			
			var icon = $('<div />').addClass('ui-icon');
			var label = $('<div />')
				.addClass('ui-label')
				.append($('<span />').text(tool.getLabel()))
	//			.text(tool.getLabel())
				.disableSelection()
			;
			
			var button = $('<div/>')
				.addClass(mycss('tool_button'))
				.append(icon)
				.append(label)
			;
			button.button();
			
			button.click(function() {
				self.setActiveTool(tool.getName());
			});
			
			p.append(button);
		}
		
		var allTabsList = [
			['common', 'Common'],
		];
		var allTabs = {};
		for (var i in allTabsList)
		{
			var t = addTab(allTabsList[i][0], allTabsList[i][1]);
			allTabs[allTabsList[i][0]] = t;
		}
			
		for (var i in toolsToInstall)
		{
			var tool = toolsToInstall[i][0];
			tool.init(self);
			tools[tool.getName()] = tool;
			
			for (var j in toolsToInstall[i][1])
			{
				addTool(toolsToInstall[i][1][j], tool);
			}
		}

		tabs.tabs();
		
		var toolPane = $('<div />')
			.addClass(mycss('tools_options'))
			.addClass('ui-widget ui-widget-content ui-corner-top')
			.appendTo(views.tools);

		var addfilter = $('<a href="#add" />').text('Add filter').button().click(function(e) {
			e.preventDefault();
			assert(activeTool);
			var tool = tools[activeTool];
			var filter = $.extend({type:tool.getName(),label:tool.getLabel(),tool:tool},tool.getFilter());
			addFilter(filter);
		}).css('visibility','hidden');
		
		// var updatefilter = $('<a href="#update" />').text('Update').button().click(function(e) {
			// e.preventDefault();
			// updateCurrentFilter();
		// }).css('visibility','hidden');
		
		/*var delfilter = $('<span />').text('Remove').button().click(function() {
			removeFilter(tools[activeTool].getFilter());
		});*/
		
		var toolActions = $('<div />')
			.addClass(mycss('tools_actions'))
			.addClass('ui-widget ui-widget-content ui-corner-bottom')
			//.text('Commands')
			.append(addfilter)
			// .append(updatefilter)
			//.append(delfilter)
			.appendTo(views.tools);
		
		views.filters = $('<div />').addClass(mycss('filters_view')+' ui-widget');
		views.filters.append($('<div class="ui-widget-header ui-corner-top" />').text('Filters'));
		views.filters.append($('<div class="ui-widget-content ui-corner-bottom" />').sortable({
			items: '>div:not(._original)',
		}));
		//views.filters.append('<div style="clear:both" />');
		
		mainDiv.append(views.image);
		mainDiv.append(views.tools);
		mainDiv.append(views.filters);
		
		toolsToInstall = null;
	}
	
	var layout = function()
	{
		//log('doing layout');
		var mainDiv = $('#'+params.divid);
		
		var dims = [mainDiv.width(), mainDiv.height()];
		//log(dims);
		// var docdims = [$(document).width(), $(document).height()];
		// var windims = [$(window).width(), $(window).height()];
		// log(docdims);
		// log(windims);
		
		var imageDiv = mainDiv.find('.'+mycss('image_view'));

		var imageDivDims = [imageDiv.outerWidth(), imageDiv.height()];
		//log(imageDivDims);
		//views.image.css('width', imageDivDims[0]+'px').css('height', imageDivDims[1]+'px');
		
		var imgProp = params.image.width/params.image.height;
		var viewProp = imageDivDims[0]/imageDivDims[1];
		var imageDims = [0,0];
		var imageOffset = [0,0];
		if (imgProp > viewProp)
		{
			imageDims[0] = imageDivDims[0];
			imageDims[1] = imageDivDims[0]/imgProp;
			imageOffset[1] = 0.5*(imageDivDims[1]-imageDims[1]);
		}
		else
		{
			imageDims[0] = imageDivDims[1]*imgProp;
			imageDims[1] = imageDivDims[1];
			imageOffset[0] = 0.5*(imageDivDims[0]-imageDims[0]);
		}
		views.image.find('img')
			.css('width', imageDims[0]+'px').css('height', imageDims[1]+'px')
			.css('left', imageOffset[0]+'px').css('top', imageOffset[1]+'px')
		;
		
		views.tools.css('left', imageDivDims[0]+'px');
		
		views.filters.css('left', imageDivDims[0]+'px').css('top', views.tools.outerHeight()+'px');
		
		var optionsH = views.tools.outerHeight() - views.tools.find('.'+mycss('tools_actions')).outerHeight() - views.tools.find('>div.ui-tabs').outerHeight();
		views.tools.find('.'+mycss('tools_options')).outerHeight(optionsH);
		
		var filterH = views.filters.outerHeight() - views.filters.find('div.ui-widget-header').outerHeight();
		views.filters.find('div.ui-widget-content').outerHeight(filterH);
		
	}
	
	var addFilter = function(filter)
	{
		if (!('active' in filter))
			filter.active = true;
			
		var item = $('<div />').addClass(mycss('filter'));
		
		var txt = $('<div />').text(filter.label);
		
		if (filter.type != 'original')
		{
			var vis = $('<div class="ui-icon '+(filter.active?'ui-icon-bullet':'ui-icon-radio-on')+' ui-inline-icon" />');
			vis.click(function() {
				var item = $(this).parent('.'+mycss('filter'));
				var domIndex = views.filters.find('.'+mycss('filter')).index(item);
				var arrIndex = filters.length-1-domIndex;
				assert(arrIndex >= 0 && arrIndex < filters.length);
				var filter = filters[arrIndex];
				if (filter.active)
				{
					$(this).removeClass('ui-icon-bullet').addClass('ui-icon-radio-on');
					filter.active = false;
				}
				else
				{
					$(this).removeClass('ui-icon-radio-on').addClass('ui-icon-bullet');
					filter.active = true;
				}
				dirty();
			});
			
			var del = $('<div class="ui-icon ui-icon-closethick ui-inline-icon" />')
			del.click(function() {
				var item = $(this).parent('.'+mycss('filter'));
				var domIndex = views.filters.find('.'+mycss('filter')).index(item);
				var arrIndex = filters.length-1-domIndex;
				assert(arrIndex >= 0 && arrIndex < filters.length);
				var wasSelected = item.hasClass('ui-state-highlight');
				item.remove();
				filters.splice(arrIndex,1);
				dirty();
				if (wasSelected)
				{
					var newSel = Math.max(0, arrIndex-1);
					selectFilterByIndex(newSel);
				}
			});
			
			item.append(vis);
			item.append(del);
		}
		item.append(txt);
		
		item.data(divid('data', filter));
		filter.dom = item;
		
		filters.push(filter);

		if (filter.type == 'original')
		{
			item.addClass('_original');
		}
		else
		{
			dirty();
		}
		
		views.filters.find('div.ui-widget-content').prepend(item);

		selectFilterByIndex(filters.length-1);
		
		item.bind('click', function() {
			selectFilterByIndex(filters.length-1-views.filters.find('.'+mycss('filter')).index(this));
		});
	}
	
	var dirty = function()
	{
		//log('Marking image dirty');
		imageDirty++;
		if (!imageLoading)
			sync();
	}
	
	var sync = function()
	{
		assert(imageDirty && !imageLoading);

		var url = params.serverScript+'?'+ImageEditor.buildQueryString(filters);
		
		//log('Loading '+url);

		// Just to see the request
		/*$.ajax({
			type: 'get',
			url: url,
		});*/
		
		var img = $('#'+params.divid+' .'+mycss('image_view')+' img');
		assert(img.length == 1);
		
		imageLoading = true;
		img.attr('src', url);
	}
	
	var onImageLoad = function(success, event)
	{
		assert(imageLoading && imageDirty >= 1);
		//log('Image load done. Success ? '+success);
		imageLoading = false;
		imageDirty--;
		
		if (imageDirty)
			sync();
	}
	
	var selectFilterByIndex = function(index)
	{
		assert(index >= 0 && index < filters.length);
		var filter = filters[index];
		
		var dompos = filters.length-1-index;

		// Highlight the selected filter
		views.filters.find('.'+mycss('filter'))
			.removeClass('ui-state-highlight')
			.addClass('ui-state-default')
			.eq(dompos)
				.addClass('ui-state-highlight');
		
		if ('tool' in filter)
		{
			// Show the options panel for the corresponding tool,
			// showing the correct tab at the same time.
			self.setActiveTool(filter.tool.getName(), true);
		
			// Update the panel's values to match the filter's
			filter.tool.setFilter(filter);
		}
		else
			self.setActiveTool(null);
	}
	
	var updateCurrentFilter = function()
	{
		assert(activeTool);
		var tool = tools[activeTool];

		var newfilter = $.extend({type:tool.getName(),label:tool.getLabel(),tool:tool,active:true},tool.getFilter());
		var active = views.filters.find('.'+mycss('filter')+'.ui-state-highlight');
		assert(active.length == 1);

		var index = filters.length-1-views.filters.find('.'+mycss('filter')).index(active);
		assert(index >= 0 && index < filters.length);

		var oldfilter = filters[index];
		assert(oldfilter.type == newfilter.type);

		delete filters[index];
		filters[index] = newfilter;
		
		dirty();
	}
	
}

ImageEditor.resizeEventHandler = null;

ImageEditor.buildQueryString = function(filters)
{
	var data = [];
	for (var i in filters)
	{
		var f = $.extend({}, filters[i]);
		if (!f.active)
			continue;
		delete f.label;
		delete f.tool;
		delete f.active;
		delete f.dom;
		data.push(f);
	}
	var serialized = JSON.stringify(data);
	var qs = 'action=get&filters='+encodeURIComponent(serialized);
	
	return qs;
}
	
