var svg = document.getElementById("svg");
var removeScroll = function(e) {
    var scrollTo = null;

    if (e.type == 'mousewheel') {
        scrollTo = (e.wheelDelta * -1);
    }
    else if (e.type == 'DOMMouseScroll') {
        scrollTo = 40 * e.detail;
    }

    if (scrollTo) {
        e.preventDefault();
        svg.scrollTop = scrollTo + svg .scrollTop;
    }
};
svg.addEventListener("mousewheel", removeScroll, false);
svg.addEventListener("DOMMouseScroll", removeScroll, false);

var zoomSpeed = 1.1;
var currentZoom = 1;

var s = Snap("#svg");

var paper = document.getElementById("svg");
if (paper.addEventListener) {
	paper.addEventListener("mousewheel", mouseWheelHandler, false);
	paper.addEventListener("DOMMouseScroll", mouseWheelHandler, false);
}
else {
	paper.attachEvent("onmousewheel", mouseWheelHandler);
}

var svgX = paper.getBoundingClientRect().left;
var svgY = paper.getBoundingClientRect().top;

var superGroup, mapGroup, legendGroup, timelineGroup, tempGroup;
var layerNullMap, layerNullLegend;
var svgFileContent = null;
var timelineContent = null;
var id = 0;
var customId = 0;

var allModifiers = [];
var allElements = [];
var elementsData = {};

var layerNames = [];
var layers = {};
var timelineLayers = [];
var isTimelineActive = true;
var elementsInNullLayer = false;
var layerCountInTimeline = 0;
var selectedLayer = null;

var infoPanelNames = [];
var infoPanels = {};

var selectedElement = null;
var currentModifier = null;

var areGroupsFinal = true;

var config = {
	cameraMovementEnabled : true,
	cameraMovementStyle : "standard"
};

loadImportedSvgFile(data);
loadImportedTimeline(data);
infoPanelNames = data.infoPanelNames;
infoPanels = data.infoPanels;
allModifiers = data.allModifiers;
elementsData = data.elementsData;
config = data.config;

var allElemsWithModifiers = [];
for (var i = 0; i < allModifiers.length; i++) {
	allModifiers[i].activated = false;
	var element = superGroup.select("#" + allModifiers[i].element);
	element.data("modifiers").push(allModifiers[i]);
	allElemsWithModifiers.push(element);
}

for (var i = 0; i < allElemsWithModifiers.length; i++) {
	checkModifiers(allElemsWithModifiers[i]);
}

//------------------------------------------------------------------------
//Utils
//------------------------------------------------------------------------

function isElementFinal(element) {
	if (areGroupsFinal) {
		return element.children().length == 0 || (element.type == "g" && !(element.attr("id") && (element.attr("id").startsWith("layer") || element.attr("id") == "tempGroup" || element.attr("id") == "mapGroup" || element.attr("id") == "timelineGroup")));
	}
	else {
		return element.children().length == 0;
	}
	
}

function isElementReal(element) {
	return !(element.type.includes("#") || element.type.includes(":") || element.type == "defs");
}

function isLayer(element) {
	return element.type == "g" && element.attr("id") && element.attr("id").startsWith("layer");
}

function setFillColor(colorFill, element) {
	element.attr({
		fill : colorFill
	});
	
	element.children().forEach(function(child) {
		if (child.type != "#text") {
			setFillColor(colorFill, child);
		}
	});
}

function animateFillColor(colorFill, element) {
	var currentColor = Snap.color(element.attr("fill"))
	
	var fromFill;
	if (currentColor.hex == "none") {
		fromFill = Snap.color("black");
	}
	else {
		fromFill = currentColor;
	}
	
	var toFill = Snap.color(colorFill)
	
	Snap.animate([fromFill.r, fromFill.g, fromFill.b], [toFill.r, toFill.g, toFill.b], function (val) {
		var fillColor = "rgb(" + Math.round(val[0]) + "," + Math.round(val[1]) + "," + Math.round(val[2]) + ")";
		
		element.attr({ //removes the old value
			fill : fillColor
		});
		
		element.attr({
			style :  element.attr("style") + ";fill: " + fillColor
		});
	}, 200);
	
	element.children().forEach(function(child) {
		if (child.type != "#text") {
			animateFillColor(colorFill, child);
		}
	});
}

function setStrokeColor(colorStroke, element) {
	element.attr({
		stroke : colorStroke
	});
	
	element.children().forEach(function(child) {
		if (child.type != "#text") {
			setStrokeColor(colorStroke, child);
		}
	});
}

function animateStrokeColor(colorStroke, element) {
	var currentColor = Snap.color(element.attr("stroke"))
	
	var fromStroke;
	if (currentColor.hex == "none") {
		fromStroke = Snap.color("black");
	}
	else {
		fromStroke = currentColor;
	}
	
	var toStroke = Snap.color(colorStroke)
	
	Snap.animate([fromStroke.r, fromStroke.g, fromStroke.b], [toStroke.r, toStroke.g, toStroke.b], function (val) {
		var strokeColor = "rgb(" + Math.round(val[0]) + "," + Math.round(val[1]) + "," + Math.round(val[2]) + ")";
		
		element.attr({ //removes the old value
			stroke : strokeColor
		});
		
		element.attr({
			style :  element.attr("style") + ";stroke: " + strokeColor
		});
	}, 200);
	
	element.children().forEach(function(child) {
		if (child.type != "#text") {
			animateStrokeColor(colorStroke, child);
		}
	});
}

function centerCamera(element) {
	var ebox = element.getBBox();
	
	var box = ebox;
	if (element.data("infoPanelsGroup")) {
		var ibox = element.data("infoPanelsGroup").getBBox();
		
		if (ibox.w != 0 || ibox.h != 0) {
			box.x = Math.min(box.x, ibox.x);
			box.y = Math.min(box.y, ibox.y);
			
			box.x2 = Math.max(box.x2, ibox.x2);
			box.y2 = Math.max(box.y2, ibox.y2);
			
			box.w = box.x2 - box.x;
			box.h = box.y2 - box.y;
			
			box.cx = box.x + box.w / 2;
			box.cy = box.y + box.h / 2;
		}
	}
	
	switch (config.cameraMovementStyle) {
		default :
			var finalMatrix = new Snap.Matrix();
			finalMatrix.translate(-box.cx + paper.getBoundingClientRect().width / 2,
								  -box.cy + paper.getBoundingClientRect().height / 2);
			
			var scaleX = 0.8 * paper.getBoundingClientRect().width / box.w;
			var scaleY = 0.8 * paper.getBoundingClientRect().height / box.h;
			var scale = Math.min(scaleX, scaleY);
			finalMatrix.scale(scale, scale, box.cx, box.cy);
			
			mapGroup.stop();
			mapGroup.animate({ transform: finalMatrix}, 700, mina.easeinout);
		break;
		case 'dezoom' :
			var finalMatrix = new Snap.Matrix();
			finalMatrix.translate(-box.cx + paper.getBoundingClientRect().width / 2,
								  -box.cy + paper.getBoundingClientRect().height / 2);
			
			var scaleX = 0.8 * paper.getBoundingClientRect().width / box.w;
			var scaleY = 0.8 * paper.getBoundingClientRect().height / box.h;
			var scale = Math.min(scaleX, scaleY);
			finalMatrix.scale(scale, scale, box.cx, box.cy);
			
			
			var current = mapGroup.transform().localMatrix;
			
			var half = new Snap.Matrix();
			half.scale(0.8, 0.8, paper.getBoundingClientRect().width / 2, paper.getBoundingClientRect().height / 2);
			half.add(0.5 * current.a + 0.5 * finalMatrix.a,
					 0.5 * current.b + 0.5 * finalMatrix.b,
					 0.5 * current.c + 0.5 * finalMatrix.c,
					 0.5 * current.d + 0.5 * finalMatrix.d,
					 0.5 * current.e + 0.5 * finalMatrix.e,
					 0.5 * current.f + 0.5 * finalMatrix.f);
			
			mapGroup.stop();
			mapGroup.animate({ transform: half}, 700, ease1, function() {
				mapGroup.animate({ transform: finalMatrix}, 700, ease2);
			});
		break;
		case 'fast' :
			var finalMatrix = new Snap.Matrix();
			finalMatrix.translate(-box.cx + paper.getBoundingClientRect().width / 2,
								  -box.cy + paper.getBoundingClientRect().height / 2);
			
			var scaleX = 0.7 * paper.getBoundingClientRect().width / box.w;
			var scaleY = 0.7 * paper.getBoundingClientRect().height / box.h;
			var scale = Math.min(scaleX, scaleY);
			finalMatrix.scale(scale, scale, box.cx, box.cy);
			
			mapGroup.stop();
			mapGroup.animate({ transform: finalMatrix}, 600, ease1);
		break;
	}
	
	
	
	/*var first = new Snap.Matrix();
	first.scale(0.8, 0.8, paper.getBoundingClientRect().width / 2, paper.getBoundingClientRect().height / 2);
	first.add(0.7 * current.a + 0.3 * finalMatrix.a,
		     0.7 * current.b + 0.3 * finalMatrix.b,
		     0.7 * current.c + 0.3 * finalMatrix.c,
		     0.7 * current.d + 0.3 * finalMatrix.d,
		     0.7 * current.e + 0.3 * finalMatrix.e,
		     0.7 * current.f + 0.3 * finalMatrix.f);
			 
	var second = new Snap.Matrix();
	second.scale(0.8, 0.8, paper.getBoundingClientRect().width / 2, paper.getBoundingClientRect().height / 2);
	second.add(0.3 * current.a + 0.7 * finalMatrix.a,
		     0.3 * current.b + 0.7 * finalMatrix.b,
		     0.3 * current.c + 0.7 * finalMatrix.c,
		     0.3 * current.d + 0.7 * finalMatrix.d,
		     0.3 * current.e + 0.7 * finalMatrix.e,
		     0.3 * current.f + 0.7 * finalMatrix.f);
	
	mapGroup.stop();
	mapGroup.animate({ transform: first}, 700, ease1, function() {
		mapGroup.animate({ transform: second}, 700, mina.linear, function() {
			mapGroup.animate({ transform: finalMatrix}, 700, ease2);
		});
	});*/
}

function centerMap() {
	var box = mapGroup.getBBox(true);
	var m = new Snap.Matrix();
	m.translate(-box.cx + paper.getBoundingClientRect().width / 2,
				-box.cy + paper.getBoundingClientRect().height / 2);
	
	var scaleX = 0.9 * paper.getBoundingClientRect().width / box.w;
	var scaleY = 0.9 * paper.getBoundingClientRect().height / box.h;
	var scale = Math.min(scaleX, scaleY);
	m.scale(scale, scale, box.cx, box.cy);
	mapGroup.transform(m);
}

function animatePath(element, effect) {
	//Inspired by
	//https://github.com/maxwellito/vivus
	
	if (element.type == "g") {
		element.children().forEach(function(child) {
			animatePath(element, effect);
		});
	}
	else if (element.type == "path") {
		var mask = element.clone();
		
		mask.attr({
			stroke: "#ffffff" //for whatever reason, the mask has to be white
		});

		if (element.attr("mask") != "none") {
			//remove the old mask to improve performance
			Snap.select(element.attr("mask").slice(5, -2)).remove();
		}
		
		element.attr({
			mask: mask
		});
		
		var length = Math.ceil(element.node.getTotalLength());
		var dashGap = 2;
		
		mask.node.style.strokeDasharray  = length + ' ' + (length + dashGap * 2);
		
		var anim = Snap.animate((length + dashGap) * (100 - effect.start) / 100, (length + dashGap) * (100 - effect.end) / 100, function (val) {		
			mask.node.style.strokeDashoffset = val;
		}, effect.time, mina.linear);
	}
}

function showElement(element) {
	element.node.style.display = "";
}


function hideElement(element) {
	element.node.style.display = "none";
}


function ease1(x) {
	return Math.sqrt(1 - (x-1) * (x-1));
}

function ease2(x) {
	return (1 - Math.sqrt(1 - (x * 0.9) * (x * 0.9))) / 0.564110;
}

//------------------------------------------------------------------------
// Change svg file
//------------------------------------------------------------------------

function loadImportedSvgFile(data) {
	initLoad();
	loadSvgFragment(Snap.parse(data.map), true);
}

function initLoad() {
	
	if (selectedElement) {
		deselectElement(selectedElement);
	}
	
	if (superGroup) {
		superGroup.remove();
	}
	
	superGroup = s.g();
	superGroup.attr({
		id: "superGroup"
	});

	mapGroup = s.g();
	mapGroup.attr({
		id: "mapGroup"
	});
	mapGroup.drag();
	superGroup.append(mapGroup);

	legendGroup = s.g();
	legendGroup.attr({
		id: "legendGroup"
	});
	superGroup.append(legendGroup);

	timelineGroup = s.g();
	timelineGroup.attr({
		id: "timelineGroup"
	});
	superGroup.append(timelineGroup);
	
	tempGroup = s.g();
	tempGroup.attr({
		id: "tempGroup"
	});
	
	layerNames = [];
	layers = {};
	timelineLayers = [];
	selectedLayer = null;
	isTimelineActive = true;
	elementsInNullLayer = false;
	
	layerNullMap = s.g();
	layerNullMap.attr({
		id: "layerNullMap"
	});
	mapGroup.append(layerNullMap);
	
	layerNullLegend = s.g();
	layerNullLegend.attr({
		id: "layerNullLegend"
	});
	legendGroup.append(layerNullLegend);
	
	layerNames.push("layerNull");
	layers["layerNull"] = {
		map : layerNullMap,
		legend : layerNullLegend,
		timelineActivated : false
	};
	
	allModifiers = [];
	allElements = [];
	elementsData = {};
}

function loadSvgFragment(f, isImported) {
	tempGroup.append(f);
	
	if (!isImported) {
		initializeID(tempGroup);
	}
	
	svgFileContent = tempGroup.innerSVG();
	
	initializeElementsLayer(tempGroup, null);
	initializeLayers(tempGroup);
	tempGroup.remove();
	
	initializeElements(mapGroup);
	
	isTimelineActive = false;
	if (!isImported && ((elementsInNullLayer && layerNames.length > 1) || (!elementsInNullLayer && layerNames.length > 2))) {
		initNewTimelinePopUp();
	}
	else {
		timelineContent = null;
		centerMap();;
	}
	/*if (!isImported && isTimelineActive && layerNames.length > 2) { //at least layerNull (which is empty) + 2 others
		createDefaultTimeline();
	}*/
}

function initializeElementsLayer(element, layer) {
	if (isElementFinal(element)) {
		if (layer == null) {
			element.data("layer", "layerNull");
		}
		else {
			element.data("layer", layer);
		}
	}
	else if (isLayer(element)) {
		element.children().forEach(function(child) {
			initializeElementsLayer(child, element.attr("id"));
		});
	}
	else {
		element.children().forEach(function(child) {
			initializeElementsLayer(child, null);
		});
	}
}

function initializeLayers(element) {
	if (isElementFinal(element)) {
		layerNullMap.append(element);
		
		if (isElementReal(element)) {
			isTimelineActive = false;
			elementsInNullLayer = true;
		}
	}
	else if (isLayer(element)) {
		var layerLegend = s.g();
		layerLegend.attr({
			id: element.attr("id") + "Legend"
		});
		legendGroup.append(layerLegend);
		
		layerNames.push(element.attr("id"));
		layers[element.attr("id")] = {
			map : element,
			legend : layerLegend,
			timelineActivated : false
		};
		
		element.attr({
			id: "" + element.attr("id") + "Map"
		});
		mapGroup.append(element);
		
		
	}
	else {
		element.children().forEach(function(child) {
			initializeLayers(child);
		});
	}
}

function initializeElements(element) {
	if (isElementFinal(element)) {
		
		if (element.attr("id")) {
			var elementParent = s.g();
			elementParent.attr({
				id: element.attr("id") + "Parent"
			});
			element.parent().append(elementParent);
			elementParent.append(element);
			
			element.data("parent", elementParent);
			element.data("isSelected", false);
			element.data("isHovered", false);
			element.data("modifiers", []);
			element.data("isLegend", false);
			element.data("firstCheck", true);
			
			elementsData[element.attr("id")] = {
				firstSelection : true
			}
			
			element.click(clickCallback);
			element.mouseover(hoverCallback);
			element.mouseout(outCallback);
			
			allElements.push(element);
		}
	}
	else {
		element.children().forEach(function(child) {
			initializeElements(child);
		});
	}
}

//------------------------------------------------------------------------
//Timeline & Layers
//------------------------------------------------------------------------

function selectLayer(layer, isFirstSelection) {
	if (!isFirstSelection && selectedLayer == layer) {
		return;
	}
	
	if (selectedElement) {
		deselectElement(selectedElement);
	}
	
	var oldSelectedLayer = selectedLayer;
	selectedLayer = layer;
	
	if (!layers[selectedLayer].timelineActivated) {
		console.log("ERROR : selectedLayer is not in timeline");
	}
	
	if (isFirstSelection) {
		for (var i = 0; i < layerNames.length; i++) {
			if (layers[layerNames[i]].timelineActivated) {
				layers[layerNames[i]].map.node.style.display = "none";
				layers[layerNames[i]].legend.node.style.display = "none";
				
				layers[layerNames[i]].map.attr({opacity: 0});
				layers[layerNames[i]].legend.attr({opacity: 0});
				
				layers[layerNames[i]].map.node.style.pointerEvents = "none";
				layers[layerNames[i]].legend.node.style.pointerEvents = "none";
			}
		}		
		layers[selectedLayer].map.attr({opacity: 1});
		layers[selectedLayer].legend.attr({opacity: 1});
	}
	else {
		if (!layers[oldSelectedLayer].timelineActivated) {
			console.log("ERROR : oldSelectedLayer is not in timeline");
		}
	
		layers[oldSelectedLayer].map.node.style.pointerEvents = "none";
		layers[oldSelectedLayer].legend.node.style.pointerEvents = "none";
		
		layers[oldSelectedLayer].map.animate({
			opacity: 0
		}, 1000);
		layers[oldSelectedLayer].legend.animate({
			opacity: 0
		}, 1000);
		
		layers[selectedLayer].map.animate({
			opacity: 1
		}, 1000);
		layers[selectedLayer].legend.animate({
			opacity: 1
		}, 1000);
	}
	layers[selectedLayer].map.node.style.display = "";
	layers[selectedLayer].legend.node.style.display = "";
		
	layers[selectedLayer].map.node.style.pointerEvents = "";
	layers[selectedLayer].legend.node.style.pointerEvents = "";
	
	checkAllModifiers();
}

function initTimeline() {
	timelineGroup.clear();
	timelineGroup.transform(new Snap.Matrix());
}

function finishTimeline() {
	timelineContent = timelineGroup.innerSVG();
	
	if (timelineLayers.length != layerCountInTimeline) {
		console.log("ERROR : timelineLayers.length != layerCountInTimeline");
	}
	
	var possibleTimelineSelectors = collectTimelineSelectors();
	
	if (possibleTimelineSelectors.length < layerCountInTimeline) {
		createDefaultTimeline();
		alert("There isn't enough elements on this timeline to function correctly");
	}
	else {
		var count = 0;
		for (var i = (possibleTimelineSelectors.length - layerCountInTimeline); i < possibleTimelineSelectors.length; i++) {
			var element = possibleTimelineSelectors[i];
			
			var layerName = timelineLayers[count];
			
			layers[layerName].selector = element;
			element.data("timelineLayerName", layerName);
			
			element.attr({
				opacity:0.6
			});
			
			if (count == 0) {
				element.attr({
					opacity:1
				});
			}
			
			element.click(timelineClickCallbackOpacity);
			element.mouseover(timelineHoverCallbackOpacity);
			element.mouseout(timelineOutCallbackOpacity);
			
			count++;
		}
	}
	
	selectLayer(timelineLayers[0], true);
	centerMap();
}

function loadImportedTimeline(data) {
	isTimelineActive = (data.timeline != null);
	if (isTimelineActive) {
		timelineLayers = data.timelineLayers;
		layerCountInTimeline = timelineLayers.length;
		
		for (var i = 0; i < layerNames; i++) {
			layers[layerNames[i]].timelineActivated = false;
		}
		for (var i = 0; i < layerCountInTimeline; i++) {
			layers[timelineLayers[i]].timelineActivated = true;
		}
		
		loadFragmentTimeline(Snap.parse(data.timeline));
	}
}

function loadFragmentTimeline(f) {
	initTimeline();
	
	timelineGroup.add(f);
	
	var w = paper.getBoundingClientRect().width;
	var h = paper.getBoundingClientRect().height;
	
	var bbox = timelineGroup.getBBox();
	
	var m = new Snap.Matrix();
	m.translate(-bbox.cx + w / 2,
				-bbox.cy + h * (19/20));
	timelineGroup.transform(m);
	
	finishTimeline();
}

function collectTimelineSelectors() {
	var queue = [];
	queue.push(timelineGroup);
	
	var possibleTimelineSelectors = [];
	
	while (queue.length > 0) {
		var element = queue.shift();
		
		if (isElementFinal(element) && isElementReal(element)) {
			possibleTimelineSelectors.push(element);
		}
		else {
			for (var i = 0; i < element.children().length; i++) {
				queue.push(element.children()[i]);
			}
		}
	}
	
	return possibleTimelineSelectors;
}

function timelineClickCallbackOpacity() {
	layers[selectedLayer].selector.animate({
		opacity:0.6
	}, 200);
		
	selectLayer(this.data("timelineLayerName"), false);
	
	this.animate({
		opacity:1
	}, 200);
}

function timelineHoverCallbackOpacity() {
	this.animate({
		opacity:0.8
	}, 200);
}

function timelineOutCallbackOpacity() {
	if (selectedLayer == this.data("timelineLayerName")) {
		this.animate({
			opacity:1
		}, 200);
	}
	else {
		this.animate({
			opacity:0.6
		}, 200);
	}
}

//------------------------------------------------------------------------
//Config
//------------------------------------------------------------------------


//------------------------------------------------------------------------
//Info panels
//------------------------------------------------------------------------

function activateInfoPanel(element, effect) {
	var f = Snap.parse(infoPanels[effect.style].svg);
	f.node.getElementsByClassName("foreignInfoPanel")[0].innerHTML = '<div xmlns="http://www.w3.org/1999/xhtml" class="infoPanel">' + effect.text + '</div>';
	
	var infoPanel = s.g();
	infoPanel.append(f);
	
	infoPanel.attr({
		id : "infoPanel" + (id++)
	});
	
	if (!element.data("infoPanelsGroup")) {
		var infoPanelsGroup = s.g();
		
		infoPanelsGroup.attr({
			id : "infoPanelsGroup" + element.attr("id")
		});
		
		if (element.data("isLegend")) {
			layers[element.data("layer")].legend.append(infoPanelsGroup);
		}
		else {
			layers[element.data("layer")].map.append(infoPanelsGroup);
		}
		element.data("infoPanelsGroup", infoPanelsGroup);
	}
	
	var m = new Snap.Matrix();
	
	//as we're putting the info panel in a layer, we need to erase the transform on this layer to avoid applying them twice
	m.add(element.data("infoPanelsGroup").transform().globalMatrix.invert());
	m.add(element.transform().globalMatrix);
	var bbox = element.getBBox(true);
	var infoBbox = infoPanel.getBBox();
	switch (effect.position) {
		default : //right
			m.translate((bbox.x2 - infoBbox.x), 
						 bbox.cy - infoBbox.cy);
		break;
		case 'top' :
			m.translate((bbox.cx - infoBbox.cx), 
						 bbox.y - infoBbox.y2);
		break;
		case 'left' :
			m.translate((bbox.x - infoBbox.x2), 
						 bbox.cy - infoBbox.cy);
		break;
		case 'bottom' :
			m.translate((bbox.cx - infoBbox.cx), 
						 bbox.y2 - infoBbox.y);
		break;
	}
	m.translate(effect.horizontalOffset, effect.verticalOffset);
	m.scale(effect.horizontalScale, effect.verticalScale);
	
	infoPanel.transform(m);
	
	var textElement = infoPanel.select(".foreignInfoPanel");
	m = new Snap.Matrix();
	m.scale(1/effect.horizontalScale, 1/effect.verticalScale);
	textElement.transform(m);
	
	element.data("infoPanelsGroup").append(infoPanel);
	effect.infoPanel = infoPanel.attr("id");
}

//------------------------------------------------------------------------
//Legend
//------------------------------------------------------------------------

function activateLegend(element, effect) {
	if (element.data("isLegend")) {
		return;
	}
	
	element.data("legendUpdate", true);
	element.data("isLegend", true);
	
	if (element.data("infoPanelsGroup")) {
		layers[element.data("layer")].legend.append(element.data("infoPanelsGroup"));
	}
	
	var legendGroup = s.g();
	legendGroup.attr({
		id : "legendGroup" + element.attr("id")
	});
	layers[element.data("layer")].legend.append(legendGroup);
	legendGroup.append(element);
	
	//First, scale the element to get its new bbox
	var m = new Snap.Matrix();
	m.scale(effect.scale);
	legendGroup.transform(m);
	var bbox = element.getBBox();
	
	//the bbox of the group is not used, as it is not well computed with some paths
	
	var localMatrix = legendGroup.transform().localMatrix;
	var x = localMatrix.x(bbox.x, bbox.y);
	var y = localMatrix.y(bbox.x, bbox.y);
	var x2 = localMatrix.x(bbox.x2, bbox.y2);
	var y2 = localMatrix.y(bbox.x2, bbox.y2);
	
	//Then, translate it (independantly of the scale)
	m = new Snap.Matrix();
	switch (effect.position) {
		default : //bottom right
			m.translate(-x2 + paper.getBoundingClientRect().width, 
						-y2 + paper.getBoundingClientRect().height);
		break;
		case 'bottom-left' :
			m.translate(-x, 
						-y2 + paper.getBoundingClientRect().height);
		break;
		case 'top-right' :
			m.translate(-x2 + paper.getBoundingClientRect().width, 
						-y);
		break;
		case 'top-left' :
			m.translate(-x, 
						-y);
		break;
	}
	m.scale(effect.scale); //and scale it again (as the previous scale will be erased)
	legendGroup.transform(m)
	
	element.data("legendGroup", legendGroup);
	
	if (element.data("isSelected")) {
		reconstructSelector(element);
	}
}

function deactivateLegend(element, effect) {
	if (!element.data("isLegend")) {
		return;
	}
	
	element.data("legendUpdate", true);
	element.data("isLegend", false);
	
	if (element.data("infoPanelsGroup")) {
		layers[element.data("layer")].map.append(element.data("infoPanelsGroup"));
	}
	
	element.data("parent").append(element);
	
	if (element.data("legendGroup")) {
		element.data("legendGroup").remove();
	}
	
	if (element.data("isSelected")) {
		reconstructSelector(element);
	}
}

//------------------------------------------------------------------------
//Callbacks & UI
//------------------------------------------------------------------------

function clickCallback(event) {
	var element = this;
	
	if (element.data("isSelected")) {
		deselectElement(element);
	}
	else {
		selectElement(element);
	}
}

function hoverCallback(event) {
	var element = this;
	
	element.data("isHovered", true);
	checkModifiers(element);
}

function outCallback(event) {
	var element = this;
	
	element.data("isHovered", false);
	checkModifiers(element);
}

function selectElement(element) {
	if (selectedElement != null) {
		deselectElement(selectedElement);
	}
		
	selectedElement = element;
	element.data("isSelected", true);
	
	checkModifiers(element);
	
	if (!element.data("isLegend") && config.cameraMovementEnabled) {
		centerCamera(element);
	}
}

function deselectElement(element) {
	selectedElement = null;
	element.data("isSelected", false);
	
	checkModifiers(element);
}

//------------------------------------------------------------------------
//Modifiers
//------------------------------------------------------------------------

function checkAllModifiers() {
	for (var i = 0; i < allElements.length; i++) {
		checkModifiers(allElements[i]);
	}
}

function checkModifiers(element) {
	var modifiers = element.data("modifiers");
	for (var i = 0; i < modifiers.length; i++) {
		checkModifier(element, modifiers[i]);
	}
	
	if (element.data("legendUpdate")) {
		element.data("legendUpdate", false);
		
		for (var i = 0; i < modifiers.length; i++) {
			legendCheckModifier(element, modifiers[i]);
		}
	}
	element.data("firstCheck", false);
}

function checkModifier(element, modifier) {
	if (modifier.activated && modifier.modified) {
		deactivateModifier(element, modifier);
	}
	modifier.modified = false;
	
	var should = shouldBeActive(element, modifier);
	
	if (should) {
		setModifier(element, modifier);
	}
	
	if (should && !modifier.activated) {
		activateModifier(element, modifier);
	}
	else if (!should && modifier.activated) {
		deactivateModifier(element, modifier);
	}
}

function legendCheckModifier(element, modifier) {
	if (!modifier.activated) {
		return;
	}
	
	for (var i = 0; i < modifier.effects.length; i++) {
		var effect = modifier.effects[i];
		
		if (effect.type == "infoPanel") {
			deactivateEffect(element, effect);
			activateEffect(element, effect);
		}
	}
}

function shouldBeActive(element, modifier) {
	for (var i = 0; i < modifier.conditions.length; i++) {
		var condition = modifier.conditions[i]; 
		
		if (!isConditionSatisfied(element, condition)) {
			return false;
		}
	}

	return true;
}

function isConditionSatisfied(element, condition) {
	switch(condition.type) {
		case 'default':
			return !element.data("isSelected") && !element.data("isHovered")
		break;
		case 'selection':
			return element.data("isSelected")
		break;
		case 'hover':
			return element.data("isHovered")
		break;
		case 'layerSelection':
			return selectedLayer == condition.layer;
		break;
		default:
			console.log("UNKNOWN CONDITION TYPE : " + condition.type)
		break;
	}
}

function setModifier(element, modifier) {
	for (var i = 0; i < modifier.effects.length; i++) {
		var effect = modifier.effects[i]; 
		
		setEffect(element, effect);
	}
}

function setEffect(element, effect) {
	switch(effect.type) {
		case 'fill':
			if (element.data("firstCheck")) {
				setFillColor(effect.color, element);
			}
			else {
				animateFillColor(effect.color, element);
			}
		break;
		case 'stroke':
			if (element.data("firstCheck")) {
				setStrokeColor(effect.color, element);
			}
			else {
				animateStrokeColor(effect.color, element);
			}
		break;
		default:
			//this effect is only used during transitions
		break;
	}
}

function activateModifier(element, modifier) {
	modifier.activated = true;
	
	for (var i = 0; i < modifier.effects.length; i++) {
		var effect = modifier.effects[i]; 
		
		activateEffect(element, effect);
	}
}

function activateEffect(element, effect) {
	switch(effect.type) {
		case 'infoPanel':
			activateInfoPanel(element, effect);
		break;
		case 'legend':
			activateLegend(element, effect);
		break;
		case 'pathAnimation':
			animatePath(element, effect);
		break;
		case 'centerCamera':
			centerCamera(element);
		break;
		default:
			//this effect has already been set
		break;
	}
}

function deactivateModifier(element, modifier) {
	modifier.activated = false;
	
	for (var i = 0; i < modifier.effects.length; i++) {
		var effect = modifier.effects[i]; 
		
		deactivateEffect(element, effect);
	}
}

function deactivateEffect(element, effect) {
	switch(effect.type) {
		case 'infoPanel':
			var oldInfoPanel = superGroup.select("#" + effect.infoPanel);
			if (oldInfoPanel) {
				oldInfoPanel.remove();
				effect.infoPanel = null;
			}
		break;
		case 'legend':
			deactivateLegend(element, effect);
		break;
		case 'pathAnimation':
			//nothing
		break;
		default:
			//this effect has already been set and does not need deactivation
		break;
	}
}

//------------------------------------------------------------------------
//Wheel
//------------------------------------------------------------------------

function mouseWheelHandler(event) {
	var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
	
	if ((event.wheelDelta || -event.detail) > 0) {
        var t = new Snap.Matrix();
        t.scale(zoomSpeed, zoomSpeed, event.pageX-svgX, event.pageY-svgY);
        t.add(mapGroup.transform().localMatrix);
        mapGroup.transform(t);
		
		currentZoom *= zoomSpeed;
	}
	else {
		var t = new Snap.Matrix();
        t.scale(1/zoomSpeed, 1/zoomSpeed, event.pageX-svgX, event.pageY-svgY);
        t.add(mapGroup.transform().localMatrix);
        mapGroup.transform(t);
		
		currentZoom /= zoomSpeed;
	}
}