module('apps.ProtoVisInterface').requires().toRun(function() {

Object.extend(apps.ProtoVisInterface, {

	start: function() {
		WorldMorph.current().canvas().style.position = 'absolute';

		// FIXME
		var url = URL.codeBase.withFilename('projects/HTML5/protovis-3.2/protovis-d3.2.js'),
			src = new WebResource(url).get().content;
		try {
			eval(src)
			Global.pv = pv;
			apps.ProtoVisInterface.loaded = true
		} catch(e) {
			throw new Error('Could not load protovis because ' + e)
		}

		this.loadMorphicProtoVis();
			
		// FIXME
		// Loader.loadJs('http://lively-kernel.org/repository/webwerkstatt/projects/HTML5/protovis-3.2/protovis-d3.2.js',
			// function() { apps.ProtoVisInterface.loaded = true })
	},

	renderVis: function(vis, pos, scale) {
		scale = scale || 1;
		pos = pos || pt(0,0);
		vis.render();
		vis.canvas().style.position = 'absolute'
		vis.canvas().style.WebkitTransform = 'scale(' + scale + ') translate(' + pos.x +'px, ' + pos.y + 'px)'
		vis.canvas().style.WebkitTransformOrigin = '0px 0px'
		vis.canvas().parentNode.removeChild(vis.canvas());

		var node = vis.canvas().cloneNode(true);

		document.body.appendChild(node)

		return node
	},

	removeVis: function(vis) {
		if (!vis.parentNode) return;
		vis.parentNode.removeChild(vis)
	},

	loadMorphicProtoVis: function() {
		var pv = Global.pv;

		pv.MorphicScene = function() {
			pv.SvgScene.call(this);
		};
		pv.MorphicScene = pv.extend(pv.SvgScene);

		pv.MorphicScene.implicit.svg = Object.clone(pv.SvgScene.implicit.svg);
		delete pv.MorphicScene.implicit.svg['fill'];
		delete pv.MorphicScene.implicit.svg['fill-opacity'];

		pv.MorphicScene.panel = function(scenes) {
			var g = (scenes.$g || scenes.mark.canvas()), e = g && g.firstChild;
			for (var i = 0; i < scenes.length; i++) {
				var s = scenes[i];
		
				/* visible */
				if (!s.visible) continue;
		
				/* clip (nest children) */
				if (s.overflow == "hidden") {
					var id = pv.id().toString(36),
						c = this.expect(e, "g", {"clip-path": "url(#" + id + ")"});
					if (!c.parentNode) g.appendChild(c);
					scenes.$g = g = c;
					e = c.firstChild;
		
					e = this.expect(e, "clipPath", {"id": id});
					var r = e.firstChild || e.appendChild(this.create("rect").rawNode); // TODO: use morph
					r.setAttribute("x", s.left);
					r.setAttribute("y", s.top);
					r.setAttribute("width", s.width);
					r.setAttribute("height", s.height);
					if (!e.parentNode) g.appendChild(e);
					e = e.nextSibling;
				}
		
				/* fill */
				e = this.fill(e, scenes, i);
		
				/* transform (push) */
				var k = this.scale,
						t = s.transform,
						x = s.left + t.x,
						y = s.top + t.y;
				this.scale *= t.k;
		
				/* children */
				for (var j = 0; j < s.children.length; j++) {
					s.children[j].$g = e = this.expect(e, "g", {
						"transform": "translate(0,0)" // TODO: remove, not needed anymore!
							+ (t.k != 1 ? " scale(" + t.k + ")" : ""),
						"x": x,
						"y": y
					});
					this.updateAll(s.children[j]);
					if ((g instanceof Morph) && (e instanceof Morph))
						if (!e.owner) g.addMorph(e);
					else
						if (!e.parentNode) g.appendChild(e);
					e = e.nextSibling;
				}
		
				/* transform (pop) */
				this.scale = k;
		
				/* stroke */
				e = this.stroke(e, scenes, i);
		
				/* clip (restore group) */
				if (s.overflow == "hidden") {
					scenes.$g = g = c.parentNode;
					e = c.nextSibling;
				}
			}
			return e;
		};
		
		pv.MorphicScene.label = function(scenes) {
			var e = scenes.$g.firstChild;
			for (var i = 0; i < scenes.length; i++) {
				var s = scenes[i];
		
				/* visible */
				if (!s.visible) continue;
				var fill = s.textStyle;
				if (!fill.opacity || !s.text) continue;
		
				/* text-baseline, text-align */
				var x = 0, y = 0, dy = 0, anchor = "start";
				switch (s.textBaseline) {
					case "middle": dy = ".35em"; break;
					case "top": dy = ".71em"; y = s.textMargin; break;
					case "bottom": y = "-" + s.textMargin; break;
				}
				switch (s.textAlign) {
					case "right": anchor = "end"; x = "-" + s.textMargin; break;
					case "center": anchor = "middle"; break;
					case "left": x = s.textMargin; break;
				}
		
				e = this.expect(e, "text", {
						"pointer-events": s.events,
						"cursor": s.cursor,
						"x": s.left,
						"y": s.top,
						"relX": x,
						"relY": y,
						"dy": dy,
						// transform: (s.textAngle ? " rotate(" + 180 * s.textAngle / Math.PI + ")" : "")
						// 		+ (this.scale != 1 ? " scale(" + 1 / this.scale + ")" : ""),
						"fill": fill.color,
						"fill-opacity": fill.opacity || null,
						"text-anchor": anchor,
						"font-family": "sans-serif",
						"font-size": 10
					}, {
						"font": s.font,
						"text-shadow": s.textShadow,
						"text-decoration": s.textDecoration
					});
				if (e.firstChild) e.firstChild.nodeValue = s.text;
				else {
					if (e instanceof Morph)
						e.setTextString(s.text);
					else
						e.appendChild(document.createTextNode(s.text));
				};
		
				var spec = e.makeStyleSpec();
				spec['borderWidth'] = 0;
				spec['fillOpacity'] = 0;
				e.beLabel(spec);
		
				e.padding = Rectangle.inset(x, y, x, y);
				e.layoutChanged();
				e.changed();
		
				e = this.append(e, scenes, i);
			}
			return e;
		};
		
		pv.MorphicScene.dot = function (scenes) {
			var e = scenes.$g.firstChild;
			for (var i = 0; i < scenes.length; i++) {
				var s = scenes[i];
		
				/* visible */
				if (!s.visible) continue;
				var fill = s.fillStyle, stroke = s.strokeStyle;
				if (!fill.opacity && !stroke.opacity) continue;
		
				/* points */
				var radius = s.radius, path = null;
				switch (s.shape) {
					case "cross": {
						path = "M" + -radius + "," + -radius
								+ "L" + radius + "," + radius
								+ "M" + radius + "," + -radius
								+ "L" + -radius + "," + radius;
						break;
					}
					case "triangle": {
						var h = radius, w = radius * 1.1547; // 2 / Math.sqrt(3)
						path = "M0," + h
								+ "L" + w +"," + -h
								+ " " + -w + "," + -h
								+ "Z";
						break;
					}
					case "diamond": {
						radius *= Math.SQRT2;
						path = "M0," + -radius
								+ "L" + radius + ",0"
								+ " 0," + radius
								+ " " + -radius + ",0"
								+ "Z";
						break;
					}
					case "square": {
						path = "M" + -radius + "," + -radius
								+ "L" + radius + "," + -radius
								+ " " + radius + "," + radius
								+ " " + -radius + "," + radius
								+ "Z";
						break;
					}
					case "tick": {
						path = "M0,0L0," + -s.size;
						break;
					}
					case "bar": {
						path = "M0," + (s.size / 2) + "L0," + -(s.size / 2);
						break;
					}
				}
		
				/* Use <circle> for circles, <path> for everything else. */
				var svg = {
					"shape-rendering": s.antialias ? null : "crispEdges",
					"pointer-events": s.events,
					"cursor": s.cursor,
					"fill": fill.color,
					"fill-opacity": fill.opacity || null,
					"stroke": stroke.color,
					"stroke-opacity": stroke.opacity || null,
					"stroke-width": stroke.opacity ? s.lineWidth / this.scale : null
				};
				if (path) {
					svg.transform = "translate(" + s.left + "," + s.top + ")";
					if (s.angle) svg.transform += " rotate(" + 180 * s.angle / Math.PI + ")";
					svg.d = path;
					e = this.expect(e, "path", svg);
				} else {
					svg.cx = s.left;
					svg.cy = s.top;
					svg.rx = radius;
					svg.ry = radius;
					e = this.expect(e, "circle", svg);
				}
				e = this.append(e, scenes, i);
			}
			return e;
		};

		pv.MorphicScene.wedge = function(scenes) {
			var e = scenes.$g.firstChild;
			for (var i = 0; i < scenes.length; i++) {
				var s = scenes[i];

				/* visible */
				if (!s.visible) continue;
				var fill = s.fillStyle, stroke = s.strokeStyle;
				if (!fill.opacity && !stroke.opacity) continue;

				/* points */
				var r1 = s.innerRadius, r2 = s.outerRadius, a = Math.abs(s.angle), p, elems;
				if (a >= 2 * Math.PI) {
					if (r1) {
						p = "M0," + r2
						  + "A" + r2 + "," + r2 + " 0 1,1 0," + (-r2)
						  + "A" + r2 + "," + r2 + " 0 1,1 0," + r2
						  + "M0," + r1
						  + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1)
						  + "A" + r1 + "," + r1 + " 0 1,1 0," + r1
						  + "Z";
					} else {
						p = "M0," + r2
						  + "A" + r2 + "," + r2 + " 0 1,1 0," + (-r2)
						  + "A" + r2 + "," + r2 + " 0 1,1 0," + r2
						  + "Z";
					}
				} else {
					var sa = Math.min(s.startAngle, s.endAngle),
							ea = Math.max(s.startAngle, s.endAngle),
							c1 = Math.cos(sa), c2 = Math.cos(ea),
							s1 = Math.sin(sa), s2 = Math.sin(ea);
					if (r1) {
						p = "M" + r2 * c1 + "," + r2 * s1
						  + "A" + r2 + "," + r2 + " 0 "
						  + ((a < Math.PI) ? "0" : "1") + ",1 "
						  + r2 * c2 + "," + r2 * s2
						  + "L" + r1 * c2 + "," + r1 * s2
						  + "A" + r1 + "," + r1 + " 0 "
						  + ((a < Math.PI) ? "0" : "1") + ",0 "
						  + r1 * c1 + "," + r1 * s1 + "Z";
					} else {
						elems = [
							new lively.scene.MoveTo(true, r2 * c1, r2 * s1),
							new lively.scene.ArcTo(true, r2 * c2, r2 * s2, r2, r2, 0, ((a < Math.PI) ? 0 : 1) + ",1", ''),
							new lively.scene.LineTo(true, 0, 0),
							new lively.scene.ClosePath()
						];
					}
				}

				e = this.expect(e, "path", {
					"shape-rendering": s.antialias ? null : "crispEdges",
					"pointer-events": s.events,
					"cursor": s.cursor,
					"transform": "translate(" + s.left + "," + s.top + ")",
					"d": p || elems,
					"fill": fill.color,
					"fill-rule": "evenodd",
					"fill-opacity": fill.opacity || null,
					"stroke": stroke.color,
					"stroke-opacity": stroke.opacity || null,
					"stroke-width": stroke.opacity ? s.lineWidth / this.scale : null
				});
				e = this.append(e, scenes, i);
			}
			return e;
		};

		pv.MorphicScene.append = function(e, scenes, index) {
			e.$scene = {scenes:scenes, index:index};
			e = this.title(e, scenes[index]);
			if ((scenes.$g instanceof Morph) && (e instanceof Morph))
				if (!e.owner) scenes.$g.addMorph(e);
			else
				if (!e.parentNode) scenes.$g.appendChild(e);
			return e.nextSibling;
		};
		
		pv.MorphicScene.expect = function(e, type, attributes, style) {
			var m;
			if (e) {
				if (e.tagName == "a") e = e.firstChild;
				if (e.tagName != type) {
					m = this.create(type);
					var n = m.rawNode; // TODO: use morph
					e.parentNode.replaceChild(n, e);
					e = n;
				}
			} else {
				m = this.create(type);
				e = m.rawNode; // TODO: use morph
			}
		
			var n = e;
			if ((type == 'rect') || (type == 'circle')  || (type == 'path')) e = (e.firstChild || e);
			if (type == 'text') e = e.lastChild;
			for (var name in attributes) {
				var value = attributes[name];
				if (value == this.implicit.svg[name]) value = null;
				if (value == null) e.removeAttribute(name);
				else if (m) {
					switch (name) {
					case 'x':
						m.setPosition(m.getPosition().withX(value));
						break;
					case 'y':
						m.setPosition(m.getPosition().withY(value));
						break;
					case 'relX':
						e.setAttribute('x', value);
						break;
					case 'relY':
						e.setAttribute('y', value);
						break;
					case 'width':
						m.setExtent(m.getExtent().withX(value));
						break;
					case 'height':
						m.setExtent(m.getExtent().withY(value));
						break;
					case 'font-family':
						m.setFontFamily && m.setFontFamily(value);
						break;
					case 'font-size':
						m.setFontSize && m.setFontSize(value);
						break;
					case 'x1':
					case 'x2':
						var v = m.shape.vertices();
						var i = name.charAt(1) - 1;
						v[i] = v[i].withX(value);
						m.setVertices(v);
						break;
					case 'y1':
					case 'y2':
						var v = m.shape.vertices();
						var i = name.charAt(1) - 1;
						v[i] = v[i].withY(value);
						m.setVertices(v);
						break;
					case 'd':
						if (value instanceof Array)
							m.shape.setElements(value);
						else
							e.setAttribute(name, value);
						break;
					case 'fill':
						m.setFill(Color.fromString(value));
						break;
					case 'fill-opacity':
						m.setFillOpacity(value);
						break;
					default:
						e.setAttribute(name, value);
					}
				} else e.setAttribute(name, value);
			}
			for (var name in style) {
				var value = style[name];
				if (value == this.implicit.css[name]) value = null;
				if (value == null) e.style.removeProperty(name);
				else e.style[name] = value;
			}
			return m ? m : (console.log('Not returning a morph!') || e);
		};
		
		pv.MorphicScene.create = function(type) {
			var morph;
			switch (type) {
			case 'g':
				morph = new Morph(new lively.scene.Group());
			case 'rect':
				morph = new Morph(new lively.scene.Rectangle());
				morph.suppressGrabbing = true;
				morph.suppressHandles = true;
				break;
			case 'line':
				morph = new PathMorph([pt(0,0), pt(0,0)]);
				break;
			case 'path':
				morph = new Morph(new lively.scene.Path([new lively.scene.MoveTo(false, 0, 0)]));
				break;
			case 'circle':
				morph = new Morph(new lively.scene.Ellipse(pt(0, 0)));
				morph.suppressGrabbing = true;
				morph.suppressHandles = true;
				break;
			case 'text':
				morph = new TextMorph();
				break;
			default:
				debugger;
				throw new Error('Type to create is not implemented for morphic!');
			}
			return morph;
		};
	},
});

// deprecated
Object.subclass('GraphBuilder',
'graph drawing', {
	makePieChart: function(data, diameter) {

	var values = data.pluck('value');
	data.forEach(function(ea) { ea.normalizedValue = ea.value / pv.sum(values) })

	diameter = diameter || 100;

	var
		labelOffset = -40,
		outerLabelOffset = 200,
		borderWidth = Math.max(0, labelOffset) + outerLabelOffset,
		center = diameter / 2 + borderWidth;

	var colorFromNormalizedValue = pv.Scale.linear(pv.min(pv.normalize(values)) - 0.2, pv.max(pv.normalize(values)) + 0.1).range("white", "black");
	
	var vis = new pv.Panel()
	    .width(diameter + borderWidth*2)
	    .height(diameter + borderWidth*2);

	var wedge = vis.add(pv.Wedge)
		.data(data)
		.angle(function(d) { return d.normalizedValue * 2 * Math.PI })
		.fillStyle(function(d) { return colorFromNormalizedValue(d.normalizedValue) })
	    .left(center)
	    .bottom(center)
	    .outerRadius(diameter / 2)	
	
	wedge.add(pv.Label)
			.text(function(d) { return String(d.normalizedValue.toFixed(2) * 100) + '%\n' })

			.left(function() { return (labelOffset + diameter / 2) * Math.cos(wedge.midAngle()) + center })
  			.bottom(function() { return -1 * (labelOffset + diameter / 2) * Math.sin(wedge.midAngle()) + center })
			.textAlign("center")
			.textBaseline("middle")
			.textAngle(0)

			// .textDecoration('bold')
			.font('20px Verdana')
			.textStyle('white')

		vis.add(pv.Dot)
			.data(data)
			.size(100)
			.left(function(d) { return center - 50 })
			.bottom(function(d) { return center - diameter / 2 - 20 - this.index * 30 })
			.strokeStyle("none")
 			.fillStyle(function(d) { return colorFromNormalizedValue(d.normalizedValue) })
			.anchor("right").add(pv.Label)
				.text(function(d) { return d.description })
				.font('20px Verdana')
				.textStyle('black')

	return vis
},
makeBarChart: function(data, diameter) {

},
makeVis: function() {},


});
Object.subclass('ProtoVisDrawing',
'initializing', {
	initialize: function(parent) {
		this.vis = null;
		this.LKparent = parent;
		this.drawingObjects = {};
	},
},
'accessing', {
	canvas: function() { return this.vis && this.vis.canvas() },
	setPosition: function(pos) {
		var c = this.canvas();
		if (!c) return;
		c.style.left = pos.x + 'px';
		c.style.top = pos.y + 'px';
	},
},
'rendering', {
	draw: function() {
		throw new Error('subclass resbonsibility');
	},
	render: function() {
		this.remove();

		var w = 650,
			h = 400;
		if (this.LKparent && (this.LKparent instanceof Morph)) {
			w = this.LKparent.getExtent().x - 2; // 2 is for a border width of 1
			h = this.LKparent.getExtent().y - 2;
		}

		this.vis = this.draw(w, h);
		this.vis.render();
		this.canvas().style.position = 'absolute';
		this.canvas().addEventListener('mouseover', function() {WorldMorph.current().showHostMouseCursor() }, true);
		this.canvas().addEventListener('mouseout', function() {WorldMorph.current().hideHostMouseCursor() }, true);

		if (this.LKparent && (this.LKparent instanceof Morph)) {
			this.setPosition(this.LKparent.getPosition().addPt(pt(1,1))); // pt(1,1) is for a border width of 1
		}
	},
	remove: function() {
		var c = this.canvas();
		if (!c || !c.parentNode) return
		c.parentNode.removeChild(c);
		this.vis = null;
	},
});
ContainerMorph.subclass('ProtoVisMorph',
'initializing', {
	initialize: function($super, rect) {
		$super(rect);
		this.setFillOpacity(0);
		this.vis = null;
	},
},
'data provider', {
	setData: function(data) {
		this.data = data;
	},
},
'rendering', {
	draw: function() {
		throw new Error('subclass resbonsibility');
	},
	render: function() {
		this.removeDrawing();

		var w = this.getExtent().x - 5, // 2 is for a border width of 1
			h = this.getExtent().y - 3.5;

		this.vis = this.draw(w, h);
		this.vis.canvas(this);

		// FIX: find a bettter way to use pv.MorphicScene for rendering
		var defaultRenderer = pv.Scene; // save default renderer
		pv.Scene = pv.MorphicScene;
		this.vis.render();
		pv.Scene = defaultRenderer; // restore default renderer
	},
	removeDrawing: function() {
		this.removeAllMorphs();
		this.vis = null;
	},
},
'graph parameters', {
	setXScale: function(scale) {
		this.xScale = scale;
	},
	setYScale: function(scale) {
		this.yScale = scale;
	},
	setXScaleFormat: function(format) {
		this.xFormat = format;
	},
	setYScaleFormat: function(format) {
		this.yFormat = format;
	},
});

Object.extend(ProtoVisDrawing, {
	LINEAR: 'linear',
	LOG: 'logarithmic',
});

(function load() {
	lively.bindings.callWhenNotNull(
		WorldMorph, 'currentWorld',
		apps.ProtoVisInterface, 'start')
})();

}); // end of module