morpheus.HClusterTool = function() {
};
morpheus.HClusterTool.PRECOMPUTED = 'Matrix values (for a precomputed distance matrix)';
morpheus.HClusterTool.Functions = [ morpheus.Euclidean, morpheus.Jaccard,
		new morpheus.OneMinusFunction(morpheus.Cosine),
		new morpheus.OneMinusFunction(morpheus.Pearson),
		new morpheus.OneMinusFunction(morpheus.Spearman),
		morpheus.HClusterTool.PRECOMPUTED ];
morpheus.HClusterTool.Functions.fromString = function(s) {
	for (var i = 0; i < morpheus.HClusterTool.Functions.length; i++) {
		if (morpheus.HClusterTool.Functions[i].toString() === s) {
			return morpheus.HClusterTool.Functions[i];
		}
	}
	throw new Error(s + ' not found');
};

morpheus.HClusterTool.createLinkageMethod = function(linkageString) {
	var linkageMethod;
	if (linkageString === 'Average') {
		linkageMethod = morpheus.AverageLinkage;
	} else if (linkageString === 'Complete') {
		linkageMethod = morpheus.CompleteLinkage;
	} else if (linkageString === 'Single') {
		linkageMethod = morpheus.SingleLinkage;
	} else {
		throw new Error('Unknown linkage method ' + linkageString);
	}
	return linkageMethod;
};

morpheus.HClusterTool.execute = function(dataset, input) {
	var linkageMethod = morpheus.HClusterTool
			.createLinkageMethod(input.linkage_method);
	var f = morpheus.HClusterTool.Functions.fromString(input.metric);
	if ((typeof f) === 'string') {
		f = null; // precomputed
	}
	var rows = input.cluster == 'Rows' || input.cluster == 'Rows and columns';
	var columns = input.cluster == 'Columns'
			|| input.cluster == 'Rows and columns';
	var doCluster = function(d, groupByFields) {
		return (groupByFields && groupByFields.length > 0) ? new morpheus.HClusterGroupBy(
				d, groupByFields, f, linkageMethod)
				: new morpheus.HCluster(morpheus.HCluster
						.computeDistanceMatrix(d, f), linkageMethod);
	};

	var rowsHcl;
	var columnsHcl;

	if (rows) {
		rowsHcl = doCluster(
				input.selectedColumns ? new morpheus.SlicedDatasetView(dataset,
						null, input.selectedColumns) : dataset,
				input.group_rows_by);
	}
	if (columns) {
		columnsHcl = doCluster(
				morpheus.DatasetUtil
						.transposedView(input.selectedRows ? new morpheus.SlicedDatasetView(
								dataset, input.selectedRows, null)
								: dataset), input.group_columns_by);

	}
	return {
		rowsHcl : rowsHcl,
		columnsHcl : columnsHcl
	};
};
morpheus.HClusterTool.prototype = {
	toString : function() {
		return 'Hierarchical Clustering';
	},
	init : function(project, form) {
		form.setOptions('group_rows_by', morpheus.MetadataUtil
				.getMetadataNames(project.getFullDataset().getRowMetadata()));
		form
				.setOptions('group_columns_by', morpheus.MetadataUtil
						.getMetadataNames(project.getFullDataset()
								.getColumnMetadata()));
		form.setVisible('group_rows_by', false);
		form
				.setVisible('cluster_rows_in_space_of_selected_columns_only',
						false);
		form.$form.find('[name=cluster]').on(
				'change',
				function(e) {
					var val = $(this).val();
					var showGroupColumns = false;
					var showGroupRows = false;
					if (val === 'Columns') {
						showGroupColumns = true;
					} else if (val === 'Rows') {
						showGroupRows = true;
					} else {
						showGroupColumns = true;
						showGroupRows = true;
					}
					form.setVisible('group_columns_by', showGroupColumns);
					form.setVisible('group_rows_by', showGroupRows);
					form.setVisible(
							'cluster_columns_in_space_of_selected_rows_only',
							showGroupColumns);
					form.setVisible(
							'cluster_rows_in_space_of_selected_columns_only',
							showGroupRows);
				});
	},
	gui : function() {
		return [ {
			name : 'metric',
			options : morpheus.HClusterTool.Functions,
			value : morpheus.HClusterTool.Functions[3].toString(),
			type : 'select'
		}, {
			name : 'cluster',
			options : [ 'Columns', 'Rows', 'Rows and columns' ],
			value : 'Columns',
			type : 'select'
		}, {
			name : 'linkage_method',
			options : [ 'Average', 'Complete', 'Single' ],
			value : 'Average',
			type : 'select'
		}, {
			name : 'group_columns_by',
			options : [],
			type : 'bootstrap-select',
			multiple : true
		}, {
			name : 'group_rows_by',
			options : [],
			type : 'bootstrap-select',
			multiple : true
		}, {
			name : 'cluster_columns_in_space_of_selected_rows_only',
			type : 'checkbox'
		}, {
			name : 'cluster_rows_in_space_of_selected_columns_only',
			type : 'checkbox'
		} ];
	},
	execute : function(options) {

		var project = options.project;
		var controller = options.controller;
		var selectedRows = options.input.cluster_columns_in_space_of_selected_rows_only ? project
				.getRowSelectionModel().getViewIndices().values()
				: null;
		var selectedColumns = options.input.cluster_rows_in_space_of_selected_columns_only ? project
				.getColumnSelectionModel().getViewIndices().values()
				: null;
		var rows = options.input.cluster == 'Rows'
				|| options.input.cluster == 'Rows and columns';
		var columns = options.input.cluster == 'Columns'
				|| options.input.cluster == 'Rows and columns';
		options.input.selectedRows = selectedRows;
		options.input.selectedColumns = selectedColumns;
		var dataset = project.getSortedFilteredDataset();
	
		if (options.input.background === false) {
			var result = morpheus.HClusterTool.execute(dataset, options.input);
			if (result.rowsHcl) {
				controller.setDendrogram(result.rowsHcl.tree, false,
						result.rowsHcl.reorderedIndices);
			}
			if (result.columnsHcl) {
				controller.setDendrogram(result.columnsHcl.tree, true,
						result.columnsHcl.reorderedIndices);
			}
		} else {
			var subtitle = [ 'clustering ' ];
			if (rows) {
				subtitle.push(dataset.getRowCount() + ' row'
						+ morpheus.Util.s(dataset.getRowCount()));
			}
			if (columns) {
				subtitle.push(rows ? ', ' : '');
				subtitle.push(dataset.getColumnCount() + ' column'
						+ morpheus.Util.s(dataset.getColumnCount()));
			}

			var blob = new Blob(
					[ 'self.onmessage = function(e) {'
							+ 'importScripts(e.data.scripts);'
							+ 'self.postMessage(morpheus.HClusterTool.execute(morpheus.Dataset.fromJson(e.data.dataset), e.data.input));'
							+ '}' ]);

			var url = window.URL.createObjectURL(blob);
			var worker = new Worker(url);

			worker.postMessage({
				scripts : morpheus.Util.getScriptPath(),
				dataset : morpheus.Dataset.toJson(dataset, {
					columnFields : options.input.group_columns_by || [],
					rowFields : options.input.group_rows_by || []
				}),
				input : options.input
			});

			worker.onmessage = function(e) {
				var result = e.data;
				if (result.rowsHcl) {
					controller.setDendrogram(result.rowsHcl.tree, false,
							result.rowsHcl.reorderedIndices);
				}
				if (result.columnsHcl) {
					controller.setDendrogram(result.columnsHcl.tree, true,
							result.columnsHcl.reorderedIndices);
				}
				worker.terminate();
				window.URL.revokeObjectURL(url);
			};
			return worker;
		}

	}
};