import * as CellSet from Geometry.CellSet;
import * as Strategy from Solver.Strategy;
import {getColumn, getRow} from Geometry.Vector;
import Set;

return {
	find: find
};

function find(solvingState) {
	var inclusion = findInclusion(cellDimensionsByname(solvingState));
	return Strategy.tryEach([
		inclusion('rows', 'colors'),
		inclusion('columns', 'colors'),
		inclusion('colors', 'rows'),
		inclusion('colors', 'columns')
	]);
}

function cellDimensionsByname(solvingState) {
	return {
		rows: {sets: solvingState.rows, property: getRow},
		columns: {sets: solvingState.columns, property: getColumn},
		colors: {sets: solvingState.colorZones, property: solvingState.getColor}
	};
}

function getAll(property) {
	return function(cellSet) {return new Set.Int(cellSet.map(property));};
}

function getSet(namedSet) {
	return function(i) {return namedSet.sets[i];};
}

function findInclusion(cellDimensions) {
	return function(subName, superName) {
		var subDimension = cellDimensions[subName];
		var superDimension = cellDimensions[superName];
		var diff = difference(getSet(subDimension), getSet(superDimension));
		return Strategy.map(
			stepOfInclusion(subName, superName),
			Strategy.tryEach(
				group(subDimension.sets.map(getAll(superDimension.property))).map(diff)
			)
		);
	}
}

function difference(getSubset, getSuperset) {
	return function(group) {
		return function() {
			if(group.indices.length == group.value.size()) {
				var empty = CellSet.union(group.value.map(getSuperset))
											.difference(CellSet.union(group.indices.map(getSubset)));
				return empty.size() > 0 ? {empty: empty, group: group}  : null;
			}
		};
	};
}

function stepOfInclusion(subName, superName) {
	return function(diffedGroup) {
		var step = {reason: 'inclusion', empty: diffedGroup.empty};
		step[subName] = diffedGroup.group.indices;
		step['in_' + superName] = diffedGroup.group.value.toList();
		return step;
	};
}

function group(sets) {
	var groups = [];
	sets.forEach(function(set, i) {
		for(var g = 0; g < groups.length; g++) {
			if(groups[g].value.equals(set)) {
				groups[g].indices.push(i);
				return;
			}
		}
		groups.push({value: set, indices: [i]});
	});
	return groups;
}