if (typeof(window.Whomix) == 'undefined') { window.Whomix = {}; }

if (typeof(window.gcanvas) == 'undefined') {
	// taken from Mathieu p01's sierpinski demo
	var chainer = function( target_func )
	{
		return function () { 
			return target_func.apply(this, arguments) || this;
		};
	}
	//adapted from Mathieu's demo
	window.gcanvas = {
		_chainableFunctions: ["set", "drawEntity","drawEntity_","drawLayers","switchContext","clearRect","fillRect","drawImage","save","translate","rotate","scale","restore","moveTo","lineTo","beginPath","closePath","stroke","fill","arc"],
		RenderingContext2d: window.CanvasRenderingContext2D.prototype,
		_baseIdNumber: 0,
		init: function() {
			this.RenderingContext2d.set = function(key, value) {
				this[key] = value;
			}
			this.RenderingContext2d.switchContext = function(newContext) {
				return newContext;
			}
			/*
			drawEntity(theSource, posX, posY, destW, destH) =(maps to)>
			theSource.drawImage(context, posX, posY, destW, destH)
			*/
			this.RenderingContext2d.drawEntity = function() {
				var args = Array.prototype.slice.call(arguments);
				var entity = args.shift();
				args.unshift(this);
				entity.drawImage.apply(entity, args);
			}
			this.RenderingContext2d.drawEntity_ = function() {
				var args = Array.prototype.slice.call(arguments);
				var entity = args.shift();
				args.unshift(this);
				entity.drawImage_.apply(entity, args);
			}
			this.RenderingContext2d.drawLayers = function() {
				var args = Array.prototype.slice.call(arguments);
				var layerManager = args.shift();
				args.unshift(this);
				layerManager.drawLayers.apply(layerManager, args);
			}
			var k;
			for (k=0; k < this._chainableFunctions.length; ++k)
			{
				var fn = this._chainableFunctions[k];
				var of = this.RenderingContext2d[fn];
				this.RenderingContext2d[fn] = chainer(of);
			}
		},
		createCanvas: function() {
			try {
				var theId, width, height;
				// created with width and height
				theId = "gcan_" + String(this._baseIdNumber);
				this._baseIdNumber++;
				if (arguments.length == 2) {
					width = arguments[0];
					height = arguments[1];
				}
				// created with 
				else if (arguments.length == 1) {
					containerId = arguments[0];
					var $container = null;
          if (typeof(containerId) == "string" && containerId.substr(0,1) != "#") {
            $container = $("#"+containerId);
          }
					else {
            $container = $(containerId);
					}
					width = $container.width();
					height = $container.height();
				}
				var html = [];
				var $canv = $('<canvas></canvas>');
				if ($.browser.msie) {
					G_vmlCanvasManager.initElement($canv[0]);
				}
				$canv.attr('id', theId);
				$canv.attr('width', width);
				$canv.attr('height', height);
				var canv = $canv[0];
				var ctx = canv.getContext('2d');
				ctx.clearRect(0,0,canv.width,canv.height);
				return canv;
			}
			catch(e)
			{
				Whomix.log("GCanvas::createCanvas - threw exception: " + e.message);
			}
		}
	};
	window.gcanvas.init();
}

window.Whomix.RatingSpace = { ApiBridge: {} };
/*
testing
*/
$.extend(window.Whomix.RatingSpace.ApiBridge, {
	_simNumberMixes: 344,
	_ratingHelper: new Whomix.RatingHelper(),
	ratingsAreMean: function(ratings)
	{
		var areMean = false;
		try {
			$.each(ratings, function(mixid, kv) {
				$.each(kv, function(k, v) {
					if (typeof(v) == "object" && v.mean) {
						areMean = true;
					}
					throw {message: "done"};
				});
			});
		}
		catch(e) {
		}
		return areMean;
	},
	cleanRatings: function(current, keyIds)
	{
		var ret = {};
		var legend = $.extend(true, {}, current.legend);
		var killKeys = {};
		killKeys[legend.x] = keyIds.x ? true : false;
		killKeys[legend.y] = keyIds.y ? true : false;
		var ratings = current.ratings;
		var areMean = this.ratingsAreMean(ratings);
		$.each(ratings, function(m, iv) {
			ret[m] = {};
			$.each(iv,function(key, val) {
				if (!killKeys[key]) {
					ret[m][key] = val;
				}
			});
		});
		legend.x = keyIds.x || legend.x;
		legend.y = keyIds.y || legend.y;
		return {legend: legend, ratings: ret};
	},
	ratingLegend: function(callback)
	{
		this._ratingHelper.getRatingKeys(function(data,resp) {
			callback({legend: data.rating_keys},resp);
		});
	},
	/*
		data => {legend: {x: keyid, y: keyid}, ratings: [mixid: {keyid: val, keyid: val}]};
	*/
	getMyRatings: function(keyIds, opt)
	{
		var ret = opt.current ? this.cleanRatings(opt.current, keyIds) : {}
		ret.legend = ret.legend || keyIds;
		ret.ratings = ret.ratings || {};
		var retr = {}
		var apiRes = this._ratingHelper.getMyRatings();
		ret.ratings = apiRes.myratings;
		if ("function" == typeof(opt.complete)) 
		{
			opt.complete(ret);
		}	
		else {
			throw {message: "can only be called asynchronously"};
		}
	},
	getMeanRatings: function(keyIds, opt)
	{
		var ret = opt.current ? this.cleanRatings(opt.current, keyIds) : {}
		ret.legend = ret.legend || keyIds;
		ret.ratings = ret.ratings || {};
		var retr = {};

		this._ratingHelper.getMeanRatings(function(data,resp) {
			var apiRes = data;
			var retr = apiRes.ratings;
			var maxcnt = {};
			var mixweights = {};
			$.each(retr, function(mixid, kv) {
				var mweight = 0;
				var mweightcnt = 0;
				$.each(kv, function(k, v) {
					if (typeof(maxcnt[k]) == "undefined") {
						maxcnt[k] = 0;
					}
					mweight += parseFloat(v.count)*parseFloat(v.count);
					mweightcnt++;
					maxcnt[k] = Math.max(maxcnt[k], v.count);
				})
				mixweights[mixid] = Math.sqrt(mweight / mweightcnt);
			});
			ret['ratings'] = retr;
			ret['maxcount'] = maxcnt;
			ret['weights'] = mixweights;
			if ("function" == typeof(opt.complete)) 
			{
				opt.complete(ret);
			} else {
				throw {message: "can only be called asynchronously"};
			}
		});
	}
});
window.Whomix.Utils = $.extend({}, window.Whomix.Utils, {
	stash: function(el, attrname, obj)
	{
		$(el).attr(attrname, JSONUtils.toJSONString(obj));
	},
	retrieve: function(el, attrname) 
	{
		return JSONUtils.parseJSON($(el).attr(attrname));
	}
});
window.Whomix.RatingSpace = $.extend(window.Whomix.RatingSpace, {
	RATING_T: {mean: "RATING_TYPE_MEAN",
						 myIP: "RATING_TYPE_MY_IP",
						 myFB: "RATING_TYPE_MY_FACEBOOK",
						 myOID: "RATING_TYPE_MY_OPENID"},
	Highlight: function(args)
	{
		this._id = args.itemId;
		this._frame = 0;
		this.frame = function() {
			return this._frame;
		}
	},
	RatingLabel: function(opt)
	{
		var self = this;
		this._id = opt.itemId;
		var canvas = this._canvas = opt.canvas;
		this.id = function() {
			return self._id;
		}
		this.offset = function() {
			return opt.offsetForId(self.id());
		}
		this.canvasOffset = function() {
			var no = opt.offsetForId(self.id());
			var dim = [canvas.width, canvas.height];
			for (var k=0; k < dim.length; ++k) {
				no[k] *= dim[k];
			}
			return no;
		}
		this.divId = function() {
			return "remixDetailsDiv_" + this.id();
		}
		var $detailsDiv = $("<div/>").addClass("rating-space-detail")
														 		 .attr('id', this.divId() )
														 		 .attr("data", this.id() )
														 		 .css({"opacity": 0});
		$detailsDiv.text(self.id());
		var $target = $(canvas).parent();
		$target.append($detailsDiv);

		this.setPosition = function() {
			var ccoords = this.canvasOffset();
		 	$("#" + this.divId()).css({"left": ccoords[0], 
					 		 					 				"top": ccoords[1]});
		}
		this.setPosition();
		this.destructor = function () {
			if (!$detailsDiv) {
				return;
			}
			$detailsDiv.animate({"opacity": 0}, 500, function() { 
				$(this).remove(); 
			});
			$detailsDiv = undefined;
		}
		$("#"+this.divId()).bind("mousedown", function() {
			self.destructor();
		});
		$("#"+this.divId()).bind("remove.ratingsdetail", function() {
			self.destructor();
		});
		$target.bind("clear.ratings", function() {
			self.destructor();
		}).bind("worldchanged.ratings", function() {
			self.setPosition();
		});
		$("#"+this.divId()).animate({"opacity": 1}, 500);
		
	},
	getKey: function(axis)
	{
		var keyIndex = this.getKeyIndex(axis);
		return parseInt(this._legend[keyIndex].id);
	},
	getKeyIndex: function(axis)
	{
		return this._keyIndices[axis];
	},
  attach: function(target)
  {
		this._keyIndices = {x: 0, y: 1};
		var self = this;
		Whomix.RatingSpace.ApiBridge.ratingLegend(function(data,resp) {
			self._legend = data.legend;
		});
    var $target =$(target);
    var $inner = $("<div/>").addClass('rating-space');
    $target.append($("<div/>").addClass('rating-space-container').append($inner));
    $target = $inner;
		this._target = $target;
		
    var canvas = gcanvas.createCanvas($inner);
		this._backCanvas = gcanvas.createCanvas(canvas.width, canvas.height);
    $target.append($(canvas));
		
		this.setRatingType(this.RATING_T.mean);
		
		var ratingLabel = undefined;
		
		$(canvas).bind("mousedown", function(e) {
			var mouseDownMixId = self.highlightMix();
			if (ratingLabel) {
				$(document).trigger("mix_unselect");
				ratingLabel.destructor();
				if (parseInt(mouseDownMixId) == parseInt(ratingLabel.id())) {
					ratingLabel = undefined;
					return;
				}
			}
			$(document).trigger("mix_select", [mouseDownMixId]);
			ratingLabel = new Whomix.RatingSpace.RatingLabel( 
													{
														canvas: canvas,
														itemId: mouseDownMixId,
														offsetForId: function(id) {
																					return self.coordsForMix(id);
																				}
													});
		});
		$(canvas).bind("mousemove", function(e) {
			var pos;
			if ("number" == typeof(e.offsetX)) {
				pos = [e.offsetX / canvas.width, e.offsetY / canvas.height];
			} else {
				var thisOffset = $(this).offset();
				pos = [(e.clientX - thisOffset.left) / canvas.width, (e.clientY - thisOffset.top) / canvas.height];
			}
			if (self.ratings()) {
				self.setHighlightMix(self._findMixClosest(pos));
				self._drawRatings(self.ratings());
			}
		});
    var ctx = canvas.getContext('2d');
    ctx.set("fillStyle","rgb(0,0,0)").fillRect(0,0,canvas.width,canvas.height);
		this._canvas = canvas;
		var self = this;
		setTimeout(function() {
			self.refresh();
		}, 100);
	},
	target: function() {
		return this._target;
	},
	coordsForMix: function(id) {
		if (id >= 0) {
			return this.ratings().rcoords[id].slice();
		}
		else {
			return [-1000,-1000];
		}
	},
	highlightMixCoords: function() {
		var hm = this.highlightMix();
		return this.coordsForMix(hm);
	},
	highlightMix: function() {
		return (this.ratings() ? this.ratings()._highlightId : -1) || -1;
	},
	setHighlightMix: function(idx) {
		this.ratings()._highlightId = idx;
	},
	_persistentData: function() {
		return (Whomix.Utils.retrieve(this.target(), "data") || {});
	},
	setRatingType: function(ratingType) {
		try {
			var obj = this._persistentData();
			obj['ratingType'] = ratingType;
			Whomix.Utils.stash(this.target(), "data", obj);
		}
		catch(e)
		{
		}
	},
	ratingType: function() {
		try {
			return this._persistentData['ratingType'] || this.RATING_T.mean;
		}
		catch(e)
		{
			return this.RATING_T.mean;
		}
	},
	_makeRGBA: function(r,g,b,a)
	{
		return ["rgba(",[r,g,b,a].join(","),")"].join("");
	},
	_updateDetails: function()
	{
		$(this._canvas).parent().trigger("worldchanged.ratings");
/*		
		var $details = $(".rating-space-detail");
		var self = this;
		$details.each(function() {
			var mixid = $(this).attr('id').split('_')[1];
			var coords = self.coordsForMix(mixid);
			$(this).css({"left": coords[0] * self._canvas.width, "top": coords[1] * self._canvas.height});
		});
*/		
	},
	_drawRatings: function(data, opts)
	{
		var self = this;
		var opacity = opts ? (opts.opacity || 1.0) : 1.0;
		var canvas = this._backCanvas;
    var ctx = canvas.getContext('2d');
    ctx.set("fillStyle","rgb(0,0,0)").fillRect(0,0,canvas.width,canvas.height);
    ctx.save();
		
		var countScaler = data.countScaler;

		var ratingsAreMean = Whomix.RatingSpace.ApiBridge.ratingsAreMean(data.ratings);

		var _drawMixPoint = function(ctx, wx, wy, color, x, y, wad, wid) {
			if (wid > 2) {
				ctx.set("fillStyle", color)
					.fillRect(x*wx-wad, y*wy-wad+1, wid, wid-2)
					.fillRect(x*wx-wad+1, y*wy-wad, wid-2, wid);
			} else {
				ctx.set("fillStyle", color)
					.fillRect(x*wx-wad, y*wy-wad, wid, wid);
			}
		}
		var drawHighlightArgs = undefined;
		$.each(data.coords, function(m,coord) {
			var x = coord[0];
			var y = coord[1];
			var hasNonNumber = ((typeof(x) != "number") || (typeof(y) != "number"));
			if (opts && "number" == typeof(opts.rotate))
			{
				var oldx=opts.oldData.coords[m][0],oldy=opts.oldData.coords[m][1];
				x = (x - oldx)*opts.rotate + oldx;
				y = (y - oldy)*opts.rotate + oldy;
			}
			var isHighlight = false;
			if (data._highlightId && data._highlightId == m) {
				isHighlight = true;
			}
			var zf = 1;
			if (opts && "number" == typeof(opts.exploding)) {
				zf = opts.exploding;
				x = (x - 0.5)*opts.exploding + 0.5;
				y = (y - 0.5)*opts.exploding + 0.5;
			}
			data.rcoords[m] = [x,y];
			var wx = canvas.width;
			var wy = canvas.height;
			var wid = 1, wad = 0;
			if (data.weights) {
				wid = Math.min(10, Math.max(1, data.weights[m] / countScaler * 10.0 * zf));
				wad = wid/2;
			}
//			var color = isHighlight ? ("rgba(255,0,0,"+opacity+")") : ("rgba(255,255,255,"+opacity+")"); 
			var lop = opacity;
			if (hasNonNumber) {
				lop = 0;
			}
			if (isHighlight) {
				drawHighlightArgs = [ctx, wx, wy, self._makeRGBA(255,255,255,lop), x, y, wad, wid];
			} else {
				_drawMixPoint(ctx, wx, wy, self._makeRGBA(192,192,192,lop), x, y, wad, wid);
			}
		});
		ctx.restore();
		if (drawHighlightArgs) {
			this._canvas.getContext('2d').drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
			ctx = this._canvas.getContext('2d');
			drawHighlightArgs[0] = ctx;
			_drawMixPoint.apply(this, drawHighlightArgs);
		} else {
			this._canvas.getContext('2d').drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
		}
		this._updateDetails();
	},
	simpleBlur: function(sctx,dctx)
	{	
		var d=0.5; //0.5+0.5*Math.random();
		var W = dctx.canvas.width;
		var H = dctx.canvas.height;
		dctx.clearRect(0,0,W, H)
			.set("globalAlpha",1)
			.drawImage(sctx.canvas,0,0,W,H)
			.set("globalAlpha",0.2)
			.save()
			.translate(-d,0)
			.drawImage(sctx.canvas,0,0,W,H)
			.restore()
			.save()
			.translate(d,0)
			.drawImage(sctx.canvas,0,0,W,H)
			.restore()
			.save()
			.translate(0,-d)
			.drawImage(sctx.canvas,0,0,W,H)
			.restore()
			.save()
			.translate(0,d)
			.drawImage(sctx.canvas,0,0,W,H)
			.restore()
			.set("globalAlpha",1);
	},
	_transitionData: function(data, opts)
	{
		var config = {transition: "explode",
									numFrames: 14,
									delay: 10};
		opts = $.extend(config, opts);
		
		var self = this;
		var count = 0;
		var numFrames = opts.numFrames;
		var implode = opts.transition == "implode";
		var explode = opts.transition == "explode";
		var rotate = opts.transition == "rotate";
		sett = {};
		if (rotate) {
			sett.rotate = true;
			sett.oldData = self.ratings();
		}
		var _innerAnim = function() {
			setTimeout(function() {
				var zf = (implode ? (1.0 - count/numFrames) : count/numFrames);
				if (implode || explode) {
					sett.exploding = zf;
					sett.opacity = zf;
				} else if (rotate) {
					sett.rotate = zf;
				}
	
				self._drawRatings(data, sett);
				if (++count < numFrames) {
					_innerAnim();
				} else {
					if (implode) {
						self._drawRatings(data, {exploding: 0, opacity: 0});
					} else {
						self._drawRatings(data);
					} 
					if (opts && opts.complete) {
						opts.complete();
					}
				}
			}, opts.delay)
		}
		_innerAnim();
	},
	ratings: function()
	{
		return this._ratingsData;
	},
	setRatings: function(data)
	{
		this._ratingsData = data;
	},
	_findMixClosest: function(pos)
	{
		var closest = {rsq: 10,
									 idx: -1};
		var isMean = Whomix.RatingSpace.ApiBridge.ratingsAreMean(this.ratings().ratings);
		$.each(this.ratings().coords, function(m,coord) {
			var dx = coord[0] - pos[0];
			var dy = coord[1] - pos[1];
			var rsq = dx*dx+dy*dy;
			if (rsq < closest.rsq) {
				closest.rsq = rsq;
				closest.idx = m;
			}
		});
		return closest.idx;
	},
	_innerRefresh: function(opt)
	{
		var self = this;
		opt = opt || {};
		var _preProcessData = function(data) {
			var ratingsAreMean = Whomix.RatingSpace.ApiBridge.ratingsAreMean(data.ratings);
			data.coords = {}; // id => [x, y]
			$.each(data.ratings, function(m,kv) {
				var x=null,y=null;
				$.each(kv, function(k,v) {
					k = parseInt(k); 
					if (ratingsAreMean) {
						var value = parseFloat(v.mean);
						if (k == self.getKey('x')) { 
							x = value; 
						}
						if (k == self.getKey('y')) { 
							y = 1.0-value; 
						}
					}
					else {
						v = parseFloat(v);
						if (k == self.getKey('x')) { 
							x = v; 
						}
						if (k == self.getKey('y')) { 
							y = 1.0-v; 
						}
					} 
				});
				data.coords[m] = [x,y];
			});
			data.rcoords = $.extend({}, data.coords);
			var countCounter=0,countScaler = 0;
			if (data.maxcount) {
				$.each(data.maxcount, function(k, v) {
					++countCounter;
					countScaler += v*v;
				});
				countScaler /= countCounter;
				countScaler = Math.pow(countScaler, 0.2)*3;
			} else {
				countScaler = 1;
			}
			data.countScaler = countScaler;
			if (self._labelSelectors && self._labelSelectors['x-axis']) {
				$(self._labelSelectors['x-axis']).text(self._legend[self.getKeyIndex('x')].name)
			}
			if (self._labelSelectors && self._labelSelectors['y-axis']) {
				$(self._labelSelectors['y-axis']).text(self._legend[self.getKeyIndex('y')].name)
			}
			return data;
		}
		var _onComplete = function(data) {
			data = _preProcessData(data);
			self._transitionData(data, opt.transition)
			self.setRatings(data);
		};
		
		var ratingKeys = null;
		if (opt && opt.keys && (opt.keys.x || opt.keys.y)) {
			ratingKeys = opt.keys;
		} else {
			ratingKeys = {x:1,y:2};
		}
		switch(this.ratingType())
		{
			case this.RATING_T.myIP:
			default:
				Whomix.RatingSpace.ApiBridge.getMyRatings(ratingKeys, {current: this.ratings(), complete: _onComplete});
				break;
			case this.RATING_T.mean:
				Whomix.RatingSpace.ApiBridge.getMeanRatings(ratingKeys, {current: this.ratings(), complete: _onComplete});
				break;
		}
	},
	setLabel: function(item, selector)
	{
		if (typeof(this._labelSelectors) == "undefined") {
			this._labelSelectors = {};
		}
		this._labelSelectors[item] = selector;
	},
	setKeyForAxis: function(axis, newKey)
	{
		if (typeof(newKey) == "function") {
			var newVal = newKey(this.getKeyIndex(axis));
			this._keyIndices[axis] =  (newVal >= 0) ? (newVal % this._legend.length) : (this._legend.length-1);
			var keyObj = {};
			keyObj[axis] = this.getKey(axis);
			this._innerRefresh({	transition: {transition: "rotate"}, 
														keys: keyObj
												});
		} else {
			var keyObj = {};
			keyObj[axis] = newKey;
			this._innerRefresh({	transition: {transition: "rotate"}, 
														keys: keyObj 
												});
		}
	},
	refresh: function()
	{
		var self = this;
		if (this._ratingsData) {
			this._transitionData($.extend(true, {}, this._ratingsData), {transition: "implode", complete: function() {
				self._innerRefresh();
			}});
		} else {
			this._innerRefresh();
		}
	}
	
});

