var dimensions = []; // definition of dimensions
var dimensionsValues = []; // current choice for each dimension
var previousDimValues = []; // previous choice of dimensions values (for rollback in case of error)
var MAX_DIM = 6; // maximum number of dimensions

// titles, labels etc. - strings used in the config controls
var EMPTY_DIM_LABEL_TEXT = "EMPTY";
var DIM_BUTTON_TEXT = "LEVEL";
var DEFAULT_TITLE = "Analytics";
var MODAL_TITLE = "";
 
var dataSourceURLFn;

//helper property definition for getting the d3 selection size (useful for debugging)
d3.selection.prototype.size = function() {
 var n = 0;
 this.each(function() { ++n; });
 return n;
};

// visualization dimensions
var w = 800,
	h = 500;

// scaling functions for x and y coordinates that maps the visualized data domain (values) to visualization viewport size
var x = d3.scale.linear().range([0, w]),
	y = d3.scale.linear().range([0, h]);
	
var kx, ky;

//visualization viewport 
var vis = null;

var g = null; // global variable containing the group of visualized 'svg:g' elements
var treeRoot = null; // root for visualized data
var isInChangeDimension = false; // flag indicating that the visualization is in fading out stage


//----- visualization config controls functions comes below -----------------------

//inits drop-downlists with the content based on the passed dimensions
function initConfigDropdowns(){
		
	for (i=1, len=dimensions.length; i<=len; i++)
		for (j=0; j<len; j++)
			$("div.input-group[dimensionId='" + i + "'] ul")
				.append("<li dimensionName='" + dimensions[j] + "'>" + 
						"<a href='#'>" + dimensions[j] + "</a></li>");
}

// on-click handlers for the config drop-down lists
function registerDimensionsOnClick(){ 
		$("li[dimensionName] > a").click(function(){
			var dimensionId = parseInt($(this).parents(".input-group").attr("dimensionId"));
			var dimensionValue = $(this).text();
			
			// disable this dimension value in this dropdown
			$(this).parent().addClass("disabled"); // disable this dimension value in this dropdown
			
			// remove link block on old value if any
			if (dimensionsValues[dimensionId-1] != ""){
				$("div.input-group[dimensionId='" + dimensionId + "'] li[dimensionName='" + dimensionsValues[dimensionId-1] + "']")
					.removeClass("disabled");
			}
			
			// update dimension label
			$("div.input-group[dimensionId='" + dimensionId + "'] > span")
				.removeClass("label-danger")
				.addClass("label-success")
				.text(dimensionValue);
			
			var index = dimensionsValues.indexOf(dimensionValue);
			
			if (index != -1){
				var updatingDimensionId = index + 1;
				$("div.input-group[dimensionId='" + updatingDimensionId + "'] li[dimensionName='" + dimensionValue + "']")
					.removeClass("disabled");
				$("div.input-group[dimensionId='" + updatingDimensionId + "'] > span")
					.removeClass("label-success")
					.addClass("label-danger")
					.text(EMPTY_DIM_LABEL_TEXT);
				dimensionsValues[index] = "";
			}
			
			dimensionsValues[dimensionId-1] = dimensionValue;
			
			if (dimensionsValues.indexOf("") == -1){
				$("#show-vis-button").prop('disabled', false);
			} 
			else
				$("#show-vis-button").prop('disabled', true);
		});
}

//creates the modal title based on the chosen dimensions
function buildTitleString(){
	var dimensionsOrder = " - ";
	
	for (i=0, len=dimensionsValues.length; i<len; i++){
		dimensionsOrder = dimensionsOrder.concat(dimensionsValues[i]);		
		if (i != len-1) dimensionsOrder = dimensionsOrder.concat(", ");
	}
	
	dimensionsOrder = dimensionsOrder.concat(".");
	
	$("#analytics-modal-label").text(MODAL_TITLE + dimensionsOrder);
}


// fires visualization
function registerShowOnClick(){
		
	$("#show-vis-button").click(function(){
		
		d3.json(dataSourceURLFn(dimensionsValues))
			.on("progress", function(){
				$("#analytics-modal-label").text("Loading data ...");
				$("#show-vis-button").text("Loading data ...");
				$("#show-vis-button").prop('disabled', true);
			})
			.get(function(error, root){
				if (typeof root === "undefined"){
					rollbackDimensions(previousDimValues);		
					$("#analytics-modal-no-data-alert").show();
					return;
				 }
				
				previousDimValues = dimensionsValues.slice(0);
				
				var data = d3.nest();

				function addKey(index) {
				    data.key(function(d) { return d[dimensionsValues[index]]; })
				}

				for(var i=0; i<dimensionsValues.length-1; i++) {
				    addKey(i);
				}
				
				var visObj = {"key": root.name, "values" : data.entries(root.data)};
				var statisticsName = root.statistics;
								
				if ($("div.chart g").length == 0){
					visualize(visObj, statisticsName);
				} else {
					fadeOut();
					
					d3.timer(function(){
						if ($("div.chart g").length == 0){
							visualize(visObj, statisticsName);
							return true;
						}
					});
				}
			}); // end of xhr.get	
	}); // end of on-click
}


// restores previously chosen dimensions in case of error (values, dropdown links' states as well as labels are rollbacked)
function rollbackDimensions(values){
	
	$("#analytics-modal-label").text(MODAL_TITLE);
	$("#show-vis-button").text("Show");
	
	dimensionsValues = values.slice(0);
	
	$("li[dimensionName]").removeClass("disabled");
	
	if (values.indexOf("") == -1)
		{
			$("#show-vis-button").prop('disabled', false);
			for (i=0, len=values.length; i<len; i++){
				$("div.input-group[dimensionId='" + (i+1) + "'] li[dimensionName='" + values[i] + "']")
					.addClass("disabled");
				$("div.input-group[dimensionId='" + (i+1) + "'] > span")
				.addClass("label-success")
				.text(values[i]);
			}
		}
	else{
		$("div.input-group[dimensionId] > span")
			.addClass("label-danger")
			.text(EMPTY_DIM_LABEL_TEXT);
		$("#show-vis-button").prop('disabled', true);
	}
}


// init all the text fields in the modal (label, dimensions buttons' texts and modal title)
function initModalTexts(){
	$("div.input-group[dimensionId] > span")
		.addClass("label-danger")
		.text(EMPTY_DIM_LABEL_TEXT);
	
	$("div.input-group-btn button:first-child", $("#analyticsModal"))
		.each(function(i){
			$(this)
				.html(DIM_BUTTON_TEXT 
						+ " "
						+ $(this).parents(".input-group").attr("dimensionId") 
						+ " <span class='caret'></span>");
		});
	
	$("#analytics-modal-label").html(MODAL_TITLE);
}


// this function creates dynamically the config row based on the passed dimensions
function initConfigRow(){
	
	var sideWidth = MAX_DIM - dimensions.length;
	
	$(".analytics-text-pattern")
		.clone()
		.removeClass("hide analytics-text-pattern")
		.addClass("col-sm-" + sideWidth)
		.appendTo($("#config-row", $("#analyticsModal")));
	
	for (i=0, len=dimensions.length; i<len; i++)
		$(".analytics-dropdown-pattern")
			.clone()
			.removeClass("hide analytics-dropdown-pattern") 
			.addClass("col-sm-2")
			.children(".input-group")
				.attr("dimensionId",i+1)
				.parent()
			.appendTo($("#config-row", $("#analyticsModal")));
	
	$(".analytics-show-btn-pattern")
		.clone()
		.removeClass("hide analytics-show-btn-pattern")
		.addClass("col-sm-" + sideWidth)
		.appendTo($("#config-row", $("#analyticsModal")));
}


 
// this function shall be called from the external code to open and init the analytics modal
// it expects the table of dimension names as strings e.g. ["dim1", "dim2", "dim3"]
// URL of data source and title of the analytics
function openVisualizationModal(dims, getDataSourceURL, title){
	
	if(typeof dims === "undefined" || !$.isArray(dims)){
		console.log("Wrong arguments passed to openning analytics modal: dimensions table incorrect");
		return false;
	}
	
	if(!getDataSourceURL){
		console.log("Wrong arguments passed to openning analytics modal: please provide function returning your data URL");
		return false;
	}
	
	dataSourceURLFn = getDataSourceURL;
	dimensions = dims;
	
	if (typeof title === "undefined") MODAL_TITLE = DEFAULT_TITLE
	else MODAL_TITLE = title;
	
	$("#analyticsModal").one("show.bs.modal", function() {
			var height = $(window).height() - 100;
			$(this).find(".modal-body").css("max-height", height);
			
			for(i=0,len=dimensions.length; i<len; i++){
				dimensionsValues[i] = "";
				previousDimValues[i] = "";
			}

			$("#show-vis-button")
				.prop('disabled', true)
				.text("Show");
			
			$("button[data-hide='alert']", $("#analyticsModal")).click(function(){
				$("#analytics-modal-no-data-alert").hide();
			});
			
			$("#analytics-modal-no-data-alert").hide();

			// creates the container and SVG placeholder for visualization
			vis = d3.select(".modal-body").append("div")
				.attr("class", "chart")
				.style("width", w + "px")
				.style("height", h + "px")
			.append("svg:svg")
				.attr("width", w)
				.attr("height", h);	
			
			initConfigRow();
			initModalTexts();
			initConfigDropdowns();
			registerDimensionsOnClick();
			registerShowOnClick();
		});
	
	// register handler for clearing things up after modal is closed
	$("#analyticsModal").one("hidden.bs.modal", function() {
			
			// removes list items for dimensions choice (and all the on-click handlers)
			$("div.input-group li", $("#analyticsModal")).remove();
			
			// remove chart container where visualization is displayed and all linked event handlers
			$("div.chart", $("#analyticsModal")).remove();
			
			// remove on-click handler for Show button
			$("#show-vis-button").off("click");
			
			//remove alert close on-click
			$("button[data-hide='alert']", $("#analyticsModal")).off("click");
			
			// empty config row
			$("#config-row > div", $("#analyticsModal")).remove();
			
			dimensions = [];
			dimensionsValues = [];
			previousDimValues = [];
			
		});
	
	// show the modal
	$("#analyticsModal").modal('show');
	return true;
}



// -----------------  Visualization code comes below -----------------------------

// helper function to apply callback at the end of all independent transitions scheduled on a group of elements
function endall(transition, callback) {
    var n = 0;
    transition
        .each(function() { ++n; })
        .each("end", function() { if (!--n) callback.apply(this, arguments); });
} 


// definition of the function visualizing data attached to the root of the JSON tree object
function visualize(root, statisticsName){
	
		isInChangeDimension = false;
		
		//d3.parition initialization, setting value and children accessors
		var partition = d3.layout.partition()
			.value(function(d) { return d[statisticsName]; })
			.children(function(d) { return d.values; });
		
		x = d3.scale.linear().range([0, w]);
	    y = d3.scale.linear().range([0, h]);
		
		 
		$("#analytics-modal-label").text("Rendering ...");
		$("#show-vis-button").text("Rendering ...");
		$("#show-vis-button").prop('disabled', true);
		
		 treeRoot = root;
		 
		 g = vis.selectAll("g")
	  	 	.data(partition.nodes(root))
	      	.enter().append("svg:g")
	      	.attr("transform", function(d) { return "translate(" + x(d.y) + ",  0)"; })
	      	.on("click", click);
 		 
	 	 kx = w / root.dx;
	  	 ky = h / 1;
	
		 g.append("svg:rect")
		 	.attr("width", root.dy * kx)
		 	.attr("height", 0)
		 	.attr("class", function(d){ return d.children ? "parent" : "child"; });	

	  	 g.append("svg:text")
	   		.attr("transform", "translate(8,0)")
	     	.attr("dy", ".35em")
	      	.style("opacity", 0)
	      	.text(function(d) { 
	      		return (d.children ? d.key : d[dimensionsValues[dimensionsValues.length-1]]) + ": " + d.value; 
	      	});
	
		var t = g.transition()
					.duration(750)
					.attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; })
					.call(endall, function(){
						buildTitleString();
						$("#show-vis-button").text("Show");
						$("#show-vis-button").prop('disabled', false);
					});
					
			t.select("rect")
				.attr("width", root.dy * kx)
	      		.attr("height", function(d) { return d.dx * ky; });
	      		
	      	t.select("text")
	      		.attr("transform", function(d){ 
	      				return "translate(8," + d.dx * ky / 2 + ")";
	      		})
	     	    .attr("dy", ".35em")
	            .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; });

} // end of visualize function


// data element on-click handler to zoom in/out the visualization
function click(d) {
    
	$("#show-vis-button").prop('disabled', true);
	
    if (!d.children)
    	d = treeRoot;
	
    kx = (d.y ? w - 40 : w) / (1 - d.y);
    ky = h / d.dx;
    	
    x.domain([d.y, 1]).range([d.y ? 40 : 0, w]);
    y.domain([d.x, d.x + d.dx]);
    
    var gTransition = g.transition()
        .duration(1500)
        .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; })
        .call(endall, function(){
        	if (!isInChangeDimension && dimensionsValues.indexOf("") == -1) 
        		$("#show-vis-button").prop('disabled', false);
        });

    var rectTransition = gTransition.select("rect")
        .attr("width", d.dy * kx)
        .attr("height", function(d) { return d.dx * ky; });

    var textTransition = gTransition.select("text")
        .attr("transform", function(d){
        	return "translate(8," + d.dx * ky / 2 + ")";
        })
        .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; });
        
    if (isInChangeDimension){
    
    	gTransition.transition()
    		.duration(1500)
    		.attr("transform", function(d) { return "translate(" + x(d.y) + ", 0)"; })
        	.remove()
        	.call(endall, function(){
        		if (dimensionsValues.indexOf("") == -1)
        			$("#show-vis-button").prop('disabled', false);
        	});
    	
    	rectTransition.transition()
    		.duration(1500)
    		.attr("height", 0);
    
      	textTransition.transition()
      		.duration(1500)
    		.attr("transform", "translate(8,0)")
    		.style("opacity",0);
    }     
}


// fading out the current visualization
function fadeOut() {
	
	$("#analytics-modal-label").text("Rendering ...");
	$("#show-vis-button").text("Rendering ...");
	$("#show-vis-button").prop('disabled', true);
	
	isInChangeDimension = true;

	if (!(kx == w && ky == h)) click(treeRoot);
	else {
		var t = g.transition()
        	.duration(2000)
        	.attr("transform", function(d) { return "translate(" + x(d.y) + ", 0)"; })
        	.remove()
        	.call(endall, function(){
        		$("#show-vis-button").prop('disabled', false);
        	});
        
    	t.select("rect")
    		.attr("height", 0);
    	
   		t.select("text")
    		.attr("transform", "translate(8,0)")
    		.style("opacity",0);
    }
}