
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 superGroup, mapGroup, legendGroup, timelineGroup, interfaceGroup, tempGroup;
var layerNullMap, layerNullLegend;
var svgFileContent = null;
var timelineContent = null;
var smallTimelineContent = null;
var id = 0;
var customId = 0;

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

var layerNames = [];
var layers = {};
var timelineLayers = [];
var isTimelineActive = true;
var isTimelineSmall = false;
var elementsInNullLayer = false;
var layerCountInTimeline = 0;
var selectedLayer = null;
var selectedLayerIndex = null;
var timelineType = "chevron";
var largeTimelineWidth = 0;

var variables = {};

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

var selectedElement = null;
var currentModifier = null;

var areGroupsFinal = true;

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

var fullScreenIcon = Snap.parse('<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>');
var exitFullScreenIcon = Snap.parse('<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>')



//------------------------------------------------------------------------
//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;
	}
}

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;
}

//From https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
function isMobile() {
  var check = false;
  (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
  return check;
}



//------------------------------------------------------------------------
// 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 = superGroup.g();
	mapGroup.attr({
		id: "mapGroup"
	});
	
	if (!isMobile()) {
		mapGroup.drag();
	}

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

	timelineGroup = superGroup.g();
	timelineGroup.attr({
		id: "timelineGroup"
	});
	
	interfaceGroup = superGroup.g();
	interfaceGroup.attr({
		id: "interfaceGroup"
	});
	
	tempGroup = s.g();
	tempGroup.attr({
		id: "tempGroup"
	});
	
	layerNames = [];
	layers = {};
	timelineLayers = [];
	selectedLayer = null;
	isTimelineActive = false;
	isTimelineSmall = false;
	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 = {};

	variables = {};
	
	reconstructFullScreenIcon(false);
}

function loadSvgFragment(f, isImported, fileName) {
	tempGroup.append(f);
	
	if (!isImported) {
		initializeID(tempGroup);
	}
	
	
	
	initializeElementsLayer(tempGroup, null);
	initializeLayers(tempGroup);
	tempGroup.remove();
	
	initializeElements(mapGroup);
	
	isTimelineActive = false;
	if (!isImported && ((elementsInNullLayer && layerNames.length > 1) || (!elementsInNullLayer && layerNames.length > 2))) {
		initNewTimelineModal();
	}
	else {
		timelineContent = null;
		smallTimelineContent = null;
		centerMap();
	
		
	}
	
	
}



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(layerIndex, isFirstSelection) {
	var layer = timelineLayers[layerIndex];
	if (!isFirstSelection && selectedLayer == layer) {
		return;
	}
	
	if (selectedElement) {
		deselectElement(selectedElement);
	}
	
	var oldSelectedLayer = selectedLayer;
	selectedLayer = layer;
	selectedLayerIndex = layerIndex;
	
	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() {
	
	if (!isTimelineSmall) {
		timelineContent = timelineGroup.innerSVG();
	}
	
	if (timelineLayers.length != layerCountInTimeline) {
		console.log("ERROR : timelineLayers.length != layerCountInTimeline");
	}
	
	var possibleTimelineSelectors = collectTimelineSelectors();
	
	if (isTimelineSmall || timelineType == "chevron") {
		if (possibleTimelineSelectors.length != 4) {
			console.log("ERROR : default timeline is wrong");
		}
		else {
			var left = possibleTimelineSelectors[0];
			var right = possibleTimelineSelectors[2];
			var leftDisabled = possibleTimelineSelectors[1];
			var rightDisabled = possibleTimelineSelectors[3];
			
			var animate05 = function() {
				this.animate({
					opacity: 0.5
				}, 200);
			};
			var animate1 = function() {
				this.animate({
					opacity: 1
				}, 200);
			};
			
			left.mouseover(animate05);
			left.mouseout(animate1);
			
			right.mouseover(animate05);
			right.mouseout(animate1);
			
			left.node.style.display = "none";
			rightDisabled.node.style.display = "none";
			
			left.click(function(){
				if (selectedLayerIndex > 0) {
					selectLayer(selectedLayerIndex - 1), false;
					
					if (selectedLayerIndex == 0) {
						left.node.style.display = "none";
						leftDisabled.node.style.display = "";
					}
					
					right.node.style.display = "";
					rightDisabled.node.style.display = "none";
				}
			});
			
			right.click(function(){
				if (selectedLayerIndex < layerCountInTimeline - 1) {
					selectLayer(selectedLayerIndex + 1), false;
					
					if (selectedLayerIndex == layerCountInTimeline - 1) {
						right.node.style.display = "none";
						rightDisabled.node.style.display = "";
					}
					
					left.node.style.display = "";
					leftDisabled.node.style.display = "none";
				}
			});
			
		}
	}
	else 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("timelineLayerIndex", count);
			
			element.attr({
				opacity:0.6
			});
			
			if (count == 0) {
				element.attr({
					opacity:1
				});
			}
			
			element.click(timelineClickCallbackOpacity);
			element.mouseover(timelineHoverCallbackOpacity);
			element.mouseout(timelineOutCallbackOpacity);
			
			count++;
		}
	}
	
	centerTimeline();
	
	selectLayer(0, true);
	centerMap();
	
	
}

function centerTimeline() {
	if (!isTimelineActive) {
		return;
	}
	
	var w = paper.getBoundingClientRect().width;
	var h = paper.getBoundingClientRect().height;
	
	var bbox = timelineGroup.getBBox(true);
	if (!isTimelineSmall && bbox.w > 0.8 * w) { //the timeline is too large
		if (config.timelineTooLarge == "scale") {
			
			var m = new Snap.Matrix();
			m.scale(0.8 * w / bbox.w);
			timelineGroup.transform(m);
		}
		else if (config.timelineTooLarge == "replace") {
			largeTimelineWidth = bbox.w;
			isTimelineSmall = true;
			loadFragmentTimeline(Snap.parse(smallTimelineContent));
		}
	}
	else if (isTimelineSmall && largeTimelineWidth < 0.8 * w) {
		isTimelineSmall = false;
		loadFragmentTimeline(Snap.parse(timelineContent));
	}
	
	
	bbox = timelineGroup.getBBox();
	m = new Snap.Matrix();
	m.translate(-bbox.cx + w / 2,
				-bbox.y2 + h);
				
	if (!isTimelineSmall && timelineType != "chevron") {
		m.translate(0, -bbox.h / 2);
	}
	m.add(timelineGroup.transform().localMatrix);
	timelineGroup.transform(m);
}



function loadImportedTimeline(data) {
	isTimelineActive = (data.timeline != null);
	if (isTimelineActive) {
		smallTimelineContent = data.smallTimeline;
		timelineLayers = data.timelineLayers;
		timelineType = data.timelineType;
		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);
	
	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("timelineLayerIndex"), 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);
	}
}


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



function activateInfoPanel(element, effect) {
	var f = Snap.parse(infoPanels[effect.style].svg);
	f.node.getElementsByClassName("infoPanel")[0].innerHTML = effect.text;
	
	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);
	}
	
	//first scale to get the right bbox
	var m = new Snap.Matrix();
	m.scale(effect.horizontalScale, effect.verticalScale);
	infoPanel.transform(m);
	
	
	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);
	
	
}

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();
	}
	
	
}

//------------------------------------------------------------------------
//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);
	
	
	
	reconstructModifiersUI(element);
	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]);
		}
	}
	if (element.data("variableUpdate")) {
		element.data("variableUpdate", false);
		checkAllModifiers();
	}
	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;
		case 'variable':
			return Boolean(variables[condition.variable]);
		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;
		case 'setVariable':
			if (!variables[effect.variable]) {
				variables[effect.variable] = true;
				element.data("variableUpdate", true);
			}
		break;
		case 'unsetVariable':
			if (variables[effect.variable]) {
				variables[effect.variable] = false;
				element.data("variableUpdate", true);
			}
		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;
	}
}

				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]);
				}
				
//------------------------------------------------------------------------
//FullScreen
//------------------------------------------------------------------------

function reconstructFullScreenIcon(isExit) {
	interfaceGroup.clear();
	interfaceGroup.attr({
		opacity: 1
	});
	
	var g = s.g();
	if (isExit) {
		g.add(exitFullScreenIcon);
	}
	else {
		g.add(fullScreenIcon);
	}
	
	interfaceGroup.add(g);
	
	var w = paper.getBoundingClientRect().width;
	var h = paper.getBoundingClientRect().height;
	
	var bbox = g.getBBox();
	
	var m = new Snap.Matrix();
	m.translate(-bbox.x2 + w,
				-bbox.y2 + h);
	m.scale(3, 3, bbox.w, bbox.h);
	g.transform(m);
	
	var bbox = g.getBBox();
	var rect = s.rect(bbox.x, bbox.y, bbox.w, bbox.h);
	
	rect.attr({
		opacity: 0
	});
	
	rect.mouseover(function() {
		interfaceGroup.animate({
			opacity: 0.5
		}, 200);
	});
	rect.mouseout(function() {
		interfaceGroup.animate({
			opacity: 1
		}, 200);
	});
	
	if (isExit) {
		rect.click(function() {
			unsetFullScreen();
		});
	}
	else {
		rect.click(function() {
			setFullScreen();
		});
	}
	
	
	interfaceGroup.add(rect);
}

function setFullScreen() {
	var fullScreenDiv = document.createElement('div');
	fullScreenDiv.id = "fullScreenDiv";
	
	fullScreenDiv.style.position = "fixed";
	fullScreenDiv.style.left = "0px";
	fullScreenDiv.style.top = "0px";
	fullScreenDiv.style.width = "100%";
	fullScreenDiv.style.height = "100%";
	fullScreenDiv.style.background = document.getElementById('paper').style.background;
	fullScreenDiv.style.zIndex = "2147483647";
	
	fullScreenDiv.append(paper);
	
	var body = document.getElementsByTagName("body")[0];
	body.append(fullScreenDiv);
	
	reconstructFullScreenIcon(true);
	centerTimeline();
}

function unsetFullScreen() {
	var fullScreenDiv = document.getElementById('fullScreenDiv');
	
	document.getElementById('paper').append(paper);
	
	document.getElementsByTagName("body")[0].removeChild(fullScreenDiv);
	
	reconstructFullScreenIcon(false);
	centerTimeline();
}

//------------------------------------------------------------------------
//Wheel & Pinch & Drag
//------------------------------------------------------------------------

function zoom(speed, x, y) {
	var svgX = paper.getBoundingClientRect().left;
	var svgY = paper.getBoundingClientRect().top;
	
	var t = new Snap.Matrix();
	t.scale(speed, speed, x-svgX, y-svgY);
	t.add(mapGroup.transform().localMatrix);
	mapGroup.transform(t);
}

function mouseWheelHandler(event) {
	var scrollTo = null;

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

    if (scrollTo) {
        event.preventDefault();
        paper.scrollTop = scrollTo + paper.scrollTop;
    }
	
	var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
	
	
	
	if ((event.wheelDelta || -event.detail) > 0) {
        zoom(zoomSpeed, event.clientX, event.clientY);
	}
	else {
		zoom(1/zoomSpeed, event.clientX, event.clientY);
	}
}

if (isMobile()) {
	paper.onpointerdown = pointerDownHandler;
	paper.onpointermove = pointerMoveHandler;
	paper.onpointerup = pointerUpHandler;
	paper.onpointercancel = pointerUpHandler;
	paper.onpointerout = pointerUpHandler;
	paper.onpointerleave = pointerUpHandler;
}

var pointers = [];
var oldDist = -1;
var isDrag = true;
function pointerDownHandler(event) {
	pointers.push(event);
}

function pointerMoveHandler(event) {
	var oldPointers = [];
	for (var i = 0; i < pointers.length; i++) {
		oldPointers.push(pointers[i]);
		if (event.pointerId == pointers[i].pointerId) {
			pointers[i] = event;
		}
	}

	if (pointers.length == 2) { //pinch
		var dist = Math.abs(pointers[0].clientX - pointers[1].clientX);

		if (oldDist > 0) {
			 if (dist > oldDist) {
				zoom(zoomSpeed, event.clientX, event.clientY);
			 }
			 else if (dist < oldDist) {
				zoom(1/zoomSpeed, event.clientX, event.clientY);1
			}
		}
		oldDist = dist;
	}
	else if (pointers.length == 1) { //drag
		var diffX = pointers[0].clientX - oldPointers[0].clientX;
		var diffY = pointers[0].clientY - oldPointers[0].clientY;
		
		var t = new Snap.Matrix();
		t.translate(diffX, diffY);
		t.add(mapGroup.transform().localMatrix);
		mapGroup.transform(t);
	}
}

function pointerUpHandler(event) {
	
	for (var i = 0; i < pointers.length; i++) {
		if (pointers[i].pointerId == event.pointerId) {
			pointers.splice(i, 1);
			break;
		}
	}
	
	if (pointers.length < 2) {
		oldDist = -1;
	}
}

//------------------------------------------------------------------------
//On resize
//------------------------------------------------------------------------

function onResize() {
	reconstructFullScreenIcon(false); //TODO : add right icon
	centerTimeline();
	
	for (var i = 0; i < allModifiers.length; i++) {
		var isLegend = false;
		
		for (var j = 0; j < allModifiers[i].effects.length; j++) {
			if (allModifiers[i].effects[j].type == "legend") {
				isLegend = true;
				break;
			}
		}
		
		if (isLegend) {
			allModifiers[i].modified = true;
		}
	}
	
	checkAllModifiers();
}

window.onresize = onResize;
window.onload = onResize;
