var SilvaMap = function(element, options) {
  var that = this;

  var style = {
    'default': {
      tooltip : "${name}",
      fillOpacity: 0.0,
      fillColor: "white",
      strokeOpacity: 1.0,
      strokeColor: "black",
      strokeWidth: 1.0
    },
    selected : {
      tooltip : "${name}",
      fillOpacity: 0.5,
      fillColor: "white",
      strokeOpacity: 1.0,
      strokeColor: "blue",
      strokeWidth: 2.0
    },
    highlight : {
      tooltip : "${name}",
      fillOpacity: 0.5,
      fillColor: "white",
      strokeOpacity: 1.0,
      strokeColor: "red",
      strokeWidth: 2.0
    },
    intersection : {
      tooltip : "${name}",
      fillOpacity: 0.7,
      fillColor: "white",
      strokeOpacity: 1.0,
      strokeColor: "#00ffff",
      strokeWidth: 2.0
    }
  };

  var parseTilemapResource = function(xml) {
    var doc = OpenLayers.parseXMLString(xml);
    var bbox = doc.getElementsByTagName('BoundingBox')[0];
    var tilesets = doc.getElementsByTagName('TileSet');
    var numZoomLevels = tilesets.length;
    var maxResolution = Math.pow(2, numZoomLevels-1);
    var minx = bbox.getAttribute("minx");
    var maxx = bbox.getAttribute("maxx");
    var miny = bbox.getAttribute("miny");
    var maxy = bbox.getAttribute("maxy");
    var resource = {
      controls: [],
      maxExtent: new OpenLayers.Bounds(0, minx, maxy, 0),
      maxResolution: maxResolution,
      numZoomLevels: numZoomLevels,
      units: 'pixels'
    };
    return resource;
  };

  OpenLayers.loadURL(options.path + '/tilemapresource.xml', undefined, undefined, function(result) {
    var resource = parseTilemapResource(result.responseText);
    var map = new OpenLayers.Map(element.attr("id"), resource);
    var layer = new OpenLayers.Layer.TMS( "TMS",options.path + "/", {layername: '.', serviceVersion: '.', type:'png'});
    var gml = new OpenLayers.Layer.GML("GeoJSON", options.path + "/polygon.json", {format:  OpenLayers.Format.GeoJSON, styleMap: new OpenLayers.StyleMap(style)});
    var highlightFeature = new OpenLayers.Control.SelectFeature(gml, {hover: true, highlightOnly: true, renderIntent: "highlight"});
    var selectFeature = new OpenLayers.Control.SelectFeature(gml, {multiple: true, toggle: true, renderIntent: "selected"});
    var intersectionFeature = new OpenLayers.Control.SelectFeature(gml, {multiple: true, toggle: false, highlightOnly: true, renderIntent: "intersection"});
    that.map = map;
    that.features = {};
    that.highlightFeature = highlightFeature;
    that.selectFeature = selectFeature;
    that.intersectionFeature = intersectionFeature;
    that.selection = {};
    that.popups = {};
    that.gml = gml;
    map.addLayer(layer);
    map.addLayer(gml);
    map.addControl(selectFeature);
    map.addControl(highlightFeature);
    map.addControl(intersectionFeature);
    highlightFeature.activate();
    selectFeature.activate();
    map.addControl(new OpenLayers.Control.MousePosition());
    map.addControl(new OpenLayers.Control.PanZoomBar());
    map.addControl(new OpenLayers.Control.MouseDefaults());
    map.addControl(new OpenLayers.Control.KeyboardDefaults());
    gml.events.on({"featureselected": function(e) {
      }, "featureunselected": function(e) {
      }, "featurehighlighted": function(e) {
      }, "featureunhighlighted": function(e) {
      }, "loadend": function(e) {
	var markers = new OpenLayers.Layer.Text("Markers", {location: options.path + "/marker.txt"});
	that.markers = markers;
	map.addLayer(markers);
	markers.div.style.zIndex = parseInt(gml.div.style.zIndex, 10) + 1;
	$.each(gml.features, function(i, f) {
	  that.features[f.attributes.name] = f;
	});
	markers.events.on({"loadend": function(e) {
	    $.each(markers.markers, function(i, m) {
	      m.display(false);
	      gml.features[i].marker = m;
	    });
	    if (options.callbacks && options.callbacks.marker_loadend) {
		options.callbacks.marker_loadend(that);
	    }
	}});
    }});
    if (options.callbacks) {
	gml.events.on(options.callbacks);
    }
    map.zoomTo(2);
  });
};

var SilvaList = function(element, options) {
  var that = this;
  if (typeof options.data == "string") {
    jQuery.get(options.data, function(data) {
      var csv = jQuery.csv(";", undefined, "\n")(data);
      that.initList(csv, element, options);
      if (options.callbacks) {
	options.callbacks.loadend(that);
      }
    });
  } else {
    that.initList(options.data, element, options);
  }
};

SilvaList.prototype.csv2map = function(csv) {
  var map = {
    key: {
    },
    value: {
    }
  };
  $.each(csv, function(i, pair) {
    if (map.key[pair[0]] === undefined) {
      map.key[pair[0]] = [];
    }
    if (map.value[pair[1]] === undefined) {
      map.value[pair[1]] = [];
    }
    map.key[pair[0]].push(pair[1]);
    map.value[pair[1]].push(pair[0]);
  });
  return map;
};

SilvaList.prototype.nbsp2space = function(str) {
  return str.replace(/&nbsp;/g, ' ');
};

SilvaList.prototype.stripNamespace = function(str) {
   var sub = str.split(":");
   if (sub.length < 2) {
       return str;
   } else {
       return sub.slice(1).join(":");
   }
};

SilvaList.prototype.initList = function(csv, element, options) {
  options = $.extend({letters_label: 'Skills', strings_label: 'Candidates'}, options);
  var place = element;
  var that = this;
  var data = that.csv2map(csv);
  var lists = $('<div id=\'lists\'><ul><li><a href=\'#letters_list\'>'+that.stripNamespace(options.letters_label)+'</a></li><li><a href=\'#strings_list\'>'+that.stripNamespace(options.strings_label)+'</a></li></ul></div>').appendTo(element);
  var results = $('<div id=\'results\'><ul><li><a href=\'#selected_result\'>Selection</a></li></ul></div>').appendTo(element);
  var selected_result = $('<div id=\'selected_result\'>').appendTo(results);
  var letters = $('<ul id=\'letters_list\' class=\'silva_letters\'>').appendTo(lists);
  var strings = $('<ul id=\'strings_list\' class=\'silva_strings\'>').appendTo(lists);
  that.data = csv;
  that.string2letter = data.key;
  that.letter2string = data.value;
  $.each(data.key, function(k, v) {
    $('<li>').attr("id", $.md5(k)).appendTo(strings).text(k);
  });
  $.each(data.value, function(k, v) {
    $('<li>').attr("id", $.md5(k)).appendTo(letters).text(k);
  });
  $(".silva_letters li", element).each(function() {
      var li = $(this);
      var letter = that.nbsp2space(li.html());
      var silva_string_count = that.letter2string[letter].length;
      li.empty();
      $("<div>").addClass("silva_letter").attr("name", letter).html(that.stripNamespace(letter)).appendTo(li);
      $("<div>").addClass("silva_string_count").html(silva_string_count).appendTo(li);
    });
    $(".silva_strings li", element).each(function() {
      var li = $(this);
      var string = that.nbsp2space(li.html());
      var silva_letter_count = 0;
      $.each(that.string2letter[string], function(i, letter) {
	if (that.letter2string[letter].length > 1) {
	  ++silva_letter_count;
	}
      });
      li.empty();
      $("<div>").addClass("silva_string").attr("name", string).html(that.stripNamespace(string)).appendTo(li);
      $("<div>").addClass("silva_letter_count").html(silva_letter_count).appendTo(li);
    });
    $("<ul id=\'letters_result\'>").addClass("silva_selected_letters").appendTo(selected_result);
    $("<ul id=\'strings_result\'>").addClass("silva_selected_strings").appendTo(selected_result);

	this.updateSelected = function(self, element, selection) {
	    if (self.selected[selection]) {
		    self.selected[selection] = undefined;
		    $(element).removeClass("silva_selected");
		} else {
		    self.selected[selection] = true;
		    $(element).addClass("silva_selected");
		}
	};
	this.updateScore  = function(self, left, right) {
	    if (self.selected[left]) {
		var score = self.scores[right];
		if (score === undefined) {
		    score = 0;
		}
		self.scores[right] = score + 1;
	    }
	};
	this.updateComment = function(self, left, right, comment) {
	    if (self.selected[left]) {
		if (comment) {
		    var comments = self.comments[right];
		    if (comments === undefined) {
			comments = {};
		    }
		    comments[left] = comment;
		    self.comments[right] = comments;
		}
	    }
	};
	this.sortSelection = function(self) {
	    var list = [];
	    $.each(self.scores, function(item, score) {
		    list.push(item);
		});
	    list.sort(function(l, r) { return self.scores[r] - self.scores[l]; });
	    return list;
	};
	this.updateDOM = function(self, selection, selector, childClass) {
	    selector.empty();
	    $.each(selection, function(i, string) {
		    var parent = $("<li>").appendTo(selector)
			.append($("<div>").addClass(childClass).html("<input type=\"checkbox\" name=\""+ string +"\"></input><a href=\"" + options.url + string + "\">" + that.stripNamespace(string) + "</a>"));
		    if (string in self.comments) {
			$.each(self.comments[string], function(i, comment) {
				$("<div>").addClass("silva_comment").html(comment).appendTo(parent);
			    });
		    }
		    if (string in self.scores) {
			$("<div>").addClass("silva_score").html(self.scores[string]).appendTo(parent);
		    }
		});
	};
	this.letters = {selected : {}, scores : {}, comments :{}, strings : {}, select: function() {
		var silva = that;
		var self = silva.letters;
		var letter = $(".silva_letter", this).attr("name");
		silva.updateSelected(self, this, letter);
		self.scores = {};
		self.comments = {};
		$.each(silva.data, function(i, tuple) {
			var letter = tuple[1];
			var string = tuple[0];
			var comment = tuple[2];
			silva.updateScore(self, letter, string);
			silva.updateComment(self, letter, string, comment);
		    });
		self.strings = silva.sortSelection(self);
		silva.updateDOM(self, self.strings, $(".silva_selected_strings", place), "silva_string");
		$(".silva_selected_strings li input[type=checkbox]", place).change(function () {
			if ($(this).attr("checked")) {
			    $.each(silva.string2letter[$(this).attr("name")], function(i, letter) {
				    $('#' + $.md5(letter)).addClass('silva_intersection');
				    if (options.callbacks) {
					options.callbacks.intersectionletter(letter);
				    }
				});
			} else {
			    $.each(silva.string2letter[$(this).attr("name")], function(i, letter) {
				    $('#' + $.md5(letter)).removeClass('silva_intersection');
				    if (options.callbacks) {
					options.callbacks.unintersectionletter(letter);
				    }
				});
			}
		    });
		silva.tree.update(place, function(letter) {
			return silva.letters.selected[letter] !== undefined;
		    });
	    }
	};
	this.strings = { selected : {}, scores : {}, comments : {}, letters : [], select : function() {
		var silva = that;
		var self = silva.strings;
		var string = $(".silva_string", this).attr("name");
		silva.updateSelected(self, this, string);
		self.scores = {};
		self.comments = {};
		$.each(silva.data, function(i, tuple) {
			var letter = tuple[1];
			var string = tuple[0];
			var comment = tuple[2];
			silva.updateScore(self, string, letter);
			silva.updateComment(self, string, letter, comment);
		    });
		var treeUpdate = silva.tree.update; // disable tree update
		silva.tree.update = function() {};
		//unselect previously selected letters
		$.each(self.letters, function(i, letter) {
			$('#' + $.md5(letter)).click();
		    });
		self.letters = silva.sortSelection(self);
		//select new letters
		$.each(self.letters, function(i, letter) {
			$('#' + $.md5(letter)).click();
		    });
		silva.tree.update = treeUpdate;

		silva.updateDOM(self, self.letters, $(".silva_selected_letters", place), "silva_letter");
		silva.tree.update(place, function(letter) {
			return ($.inArray(letter, silva.strings.letters) != -1) && (silva.letters.selected[letter] !== undefined);
		    });
	    }};
          this.tree = { update: function(place, predicate) {
		var silva = that;
		if (options.callbacks) {
		    $.each(silva.letter2string, function(letter, string) {
			    var letterSelected = predicate(letter);
			    if (letterSelected) {
				options.callbacks.highlightletter(letter);
			    } else {
				options.callbacks.unhighlightletter(letter);
			    }
			});
		}
	    }};
  $(".silva_letters li", place).click(this.letters.select);
  $(".silva_strings li", place).click(this.strings.select);
};

(function($) {
    $.fn.silva = function(options) {
      var place = $(this);
      var map_element = $('<div id="map">').appendTo(place);
      var map = new SilvaMap(map_element, {path: options.path + '/' + options.trees[0], callbacks: {loadend: function(e) {
	  var list = new SilvaList(place, {data: options.path + '/'+ options.trees[0] + '/data.csv', url: options.url, strings_label: options.strings_label, letters_label: options.letters_label, callbacks: {loadend: function(e) {
	  $("#lists", place).tabs({collapsible: true}).tabs("select", 0);
	  $("#results", place).tabs({collapsible: true});
	  if (options.callbacks) {
	    options.callbacks.loadend(map);
	  }
	  }, highlightletter: function(letter) {
		      if (map.selection[letter] === undefined) {
			  map.selectFeature.select(map.features[letter]);
		      }
	  }, unhighlightletter: function(letter) {
		      if (map.selection[letter]) {
			  map.selectFeature.unselect(map.features[letter]);
		      }
	  }, intersectionletter: function(letter) {
		      map.features[letter].previousRenderIndent = map.features[letter].renderIntent;
		      map.intersectionFeature.highlight(map.features[letter]);
	  }, unintersectionletter: function(letter) {
		      map.intersectionFeature.unhighlight(map.features[letter]);
		      map.gml.drawFeature(map.features[letter], map.features[letter].previousRenderIndent);
	  }}});
      }, featureselected: function(e) {
	 if (map.selection[e.feature.attributes.name] === undefined) {
	     map.selection[e.feature.attributes.name] = true;
	 }
	 $('#' + $.md5(e.feature.attributes.name)).not('.silva_selected').click();
	 if (e.feature.marker) {
	     e.feature.marker.display(true);
	 }
      }, featureunselected: function(e) {
	 if (map.selection[e.feature.attributes.name] !== undefined) {
	     map.selection[e.feature.attributes.name] = undefined;
	 }
	 $('#' + $.md5(e.feature.attributes.name)).filter('.silva_selected').click();
	 if (e.feature.marker) {
	     e.feature.marker.display(false);
	 }
      }, featurehighlighted: function(e) {
	options.callbacks.featurehighlighted(e);
      }, featureunhighlighted: function(e) {
	options.callbacks.featureunhighlighted(e);
      }}});
      return place;
    };
})(jQuery);
