package beehive

import (
	"bytes"
	"net/http"

	"github.com/kandoo/beehive/Godeps/_workspace/src/github.com/gorilla/mux"
)

type webPage struct {
	title  string
	url    string
	onMenu bool
	script string
	style  string
	body   string
	page   []byte
}

// handle must use a copy of p because this function is used as a closure in a
// loop.
func (p webPage) handle(w http.ResponseWriter, r *http.Request) {
	w.Write(p.page)
}

var (
	webPages = []webPage{
		{
			title:  "Status",
			url:    "/",
			onMenu: true,
			script: statusScript,
			style:  statusStyle,
		},
		{
			title:  "Bees",
			url:    "/bees",
			onMenu: true,
			script: beesScript,
			style:  beesStyle,
		},
		{
			title:  "Traffic Matrix",
			url:    "/matrix",
			onMenu: true,
			script: matrixScript,
			style:  matrixStyle,
		},
		{
			title:  "About",
			url:    "/about",
			onMenu: true,
			body:   aboutBody,
		},
	}

	bgColor = `#12191A`

	statusStyle = `
		.heading{
			font-size: 14pt;
			margin: 20px 0px 20px 10px;
		}

		.hive {
			margin: 20px;
		}

		.addr {
			color: #EEE;
		}
	`
	statusScript = `
		$(document).ready(function() {
			$.ajax({
				url: '/api/v1/state',
				context: document.body
			}).done(function(data) {
				writeState(data);
			}).error(function() {
				$('body').append('cannot fetch data');
			});
		});

		function writeState(state) {
			$('body').append('<div class="heading">' +
													state.peers.length + ' live hives:' +
												'</div>');
			for (var i in state.peers) {
				var p = state.peers[i];
				$('<div>', {
						'id': 'hive'+ p.id,
						'class': 'hive',
						'html': '<a href="http://' + p.public_addr +
										'" class="addr">Hive ' + p.id +
										'</a>'
					}).appendTo('body');
			}
		}
	`
	beesStyle = `
		@import url(http://fonts.googleapis.com/css?family=Ubuntu+Mono);

		.infobox {
			color: #EEE;
			float: left;
			font-family: 'Ubuntu Mono';
			width: 200px;
			margin: 20px;
		}

		svg {
			float: left;
		}
	`
	sortBees = `
		function sortBees(bees) {
			bees.sort(function(a, b) {
				if (a.hive < b.hive) {
					return -1;
				}
				if (a.hive > b.hive) {
					return 1;
				}
				return a.id - b.id;
			});
			return bees;
		}
	`
	generateMatrixColor = `
		function matrixValue(matrix, i, j) {
			return (matrix[i] && matrix[i][j] || 0) +
						 (matrix[j] && matrix[j][i] || 0);
		}

		function generateMatrixColor(matrix, baseColor) {
			var max = 0;
			for (var id1 in matrix) {
				for (var id2 in matrix[id1]) {
					max = Math.max(max, matrixValue(matrix, id1, id2));
				}
			}

			var colored = {}
			for (var id1 in matrix) {
				colored[id1] = {}
				for (var id2 in matrix[id1]) {
					var m = matrixValue(matrix, id1, id2);
					colored[id1][id2] = baseColor.brighter(6*m/max);
				}
			}
			return colored;
		}
	`
	beesScript = `
		$(document).ready(function() {
			$.ajax({
				url: '/api/v1/bees',
				context: document.body
			}).done(function(data) {
				drawBees(data);
			}).error(function() {
				$('body').append('cannot fetch data');
			});
		}); ` +
		sortBees +
		generateMatrixColor + `
		var BEE_R = 20;
		var BEE_HEAD_R = 5;
		var BEE_TAIL_RX = 12;
		var BEE_TAIL_RY = 5;
		var BEE_WING_RX = 4;
		var BEE_WING_RY = 10;
		var PI = Math.PI;
		var MARGIN = 10 * BEE_TAIL_RX;

		function getCenter(len) {
			return getRadius(len) + MARGIN/2;
		}

		function getRadius(len) {
			return 2 * BEE_R * len / PI;
		}

		function getDegree(len) {
			return 2 * PI / len;
		}

		function drawBees(bees) {
			sortBees(bees);
			apps = bees.map(function(b) {
				return b.app;
			});
			apps = $.unique(apps);

			if (apps.length <= 10) {
				var color = d3.scale.category10();
			} else if (apps.length <= 20) {
				var color = d3.scale.category20();
			} else {
				var color = scale.linear().domain([1, apps.length])
																	.range(['red', 'blue']);
			}

			var appColors = {}
			for (i in apps) {
				appColors[apps[i]] = color(i+1);
			}

			var len = bees.length;
			var center = getCenter(len);
			var rad = getRadius(len);
			var deg = getDegree(len);

			var svg = d3.select('body').append('svg')
																 .attr('width', center * 2)
																 .attr('height', center * 2);
			for (i in bees) {
				var b = bees[i];
				var c = appColors[b.app];
				var x = center + Math.sin(i * deg)*rad;
				var y = center + Math.cos(i * deg)*rad;
				drawBee(b, x, y, i*deg, c, svg)
			}

			$.ajax({
				url: '/apps/bh_collector/stats',
				context: document.body
			}).done(function(stats) {
				drawLinks(svg, bees, stats);
			}).error(function() {
				alert('cannot load stats');
			});
		}

		function drawLinks(svg, bees, matrix) {
			var len = bees.length;
			var center = getCenter(len);
			var rad = getRadius(len) - BEE_HEAD_R*4;
			var deg = getDegree(len);
			indices = {};
			for (var i in bees) {
				indices[bees[i].id] = +i;
			}

			var max = 0;
			for (var i in matrix) {
				for (var j in matrix[i]) {
					if (max < matrix[i][j]) {
						max = matrix[i][j];
					}
				}
			}

			var baseColor = d3.rgb(1, 1, 1);
			var colorized = generateMatrixColor(matrix, baseColor);
			for (var id1 in matrix) {
				var i = indices[id1];
				var x1 = center + Math.sin(i * deg)*rad;
				var y1 = center + Math.cos(i * deg)*rad;

				var row = matrix[id1];
				for (var id2 in row) {
					var j = indices[id2];
					var c = colorized[id1] && colorized[id1][id2] || null;
					if (!c) {
						continue;
					}
					var x2 = center + Math.sin(j * deg)*rad;
					var y2 = center + Math.cos(j * deg)*rad;
					var lineData = [
						{'x': x1, 'y': y1},
						{'x': center, 'y': center},
						{'x': x2, 'y': y2}
					];
					var lineFunction = d3.svg.line()
																		.x(function(d) { return d.x; })
																		.y(function(d) { return d.y; })
																		.interpolate('basis');
					var lineGraph = svg.append('path')
														 .attr('d', lineFunction(lineData))
														 .attr('stroke', c)
														 .attr('stroke-width', 2)
														 .attr('fill', 'none');
				}
			}
		}

		var bgColor = '` + bgColor + `';
		function drawBee(b, x, y, d, c, svg) {
			var g = svg.append('g');
			g.append('ellipse')
				.attr('cx', x + BEE_HEAD_R)
				.attr('cy', y)
				.attr('rx', BEE_TAIL_RX)
				.attr('ry', BEE_TAIL_RY)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor);
			g.append('ellipse')
				.attr('cx', x)
				.attr('cy', y + BEE_WING_RY )
				.attr('rx', BEE_WING_RX)
				.attr('ry', BEE_WING_RY)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor)
				.attr('transform', 'rotate(-30 ' + x + ' ' + y + ')');
			g.append('ellipse')
				.attr('cx', x)
				.attr('cy', y - BEE_WING_RY )
				.attr('rx', BEE_WING_RX)
				.attr('ry', BEE_WING_RY)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor)
				.attr('transform', 'rotate(30 ' + x + ' ' + y + ')');
			g.append('ellipse')
				.attr('cx', x)
				.attr('cy', y + BEE_WING_RY)
				.attr('rx', BEE_WING_RX)
				.attr('ry', BEE_WING_RY)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor);
			g.append('ellipse')
				.attr('cx', x)
				.attr('cy', y - BEE_WING_RY)
				.attr('rx', BEE_WING_RX)
				.attr('ry', BEE_WING_RY)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor);
			g.append('circle')
				.attr('cx', x - BEE_R + BEE_HEAD_R*2)
				.attr('cy', y)
				.attr('r', BEE_HEAD_R)
				.attr('style', 'stroke: ' + c + ';')
				.attr('fill', bgColor);
			g.attr('transform', 'rotate(' + (90 - d*180/PI) + ' ' + x + ' ' + y +')');
			g.on('click', function() {
				showBeeInfo(b);
			});
		}

		function showBeeInfo(b) {
			if ($('.infobox').length == 0) {
				$('body').append('<div class="infobox"></div>');
			}
			var info = $('.infobox');
			info.empty();
			info.append('<div>ID: ' + b.id + '</div>');
			info.append('<div>App: ' + b.app + '</div>');
			info.append('<div>Hive: ' + b.hive + '</div>');
			info.append('<div>Detached: ' + (b.detached ? 'YES' : 'NO') + '</div>');
			if (b.colony.leader == b.id) {
				info.append('<div>Is Leader: YES</div>');
			} else {
				if (!b.colony.leader) {
					info.append('<div>Leader: None</div>');
				} else {
					info.append('<div>Leader:' + b.colony.leader + '</div>');
				}
			}
			if (b.colony.followers == null) {
				info.append('<div>Followers: None</div>');
			} else {
				info.append('<div>Followers: ' +  b.colony.followes.join(',') +
						'</div>');
			}
		}
	`

	matrixStyle  = ""
	matrixScript = `
		$(document).ready(function() {
			$.ajax({
				url: '/api/v1/bees',
				context: document.body
			}).done(function(data) {
				drawMatrix(data);
			}).error(function() {
				$('body').append('cannot fetch data');
			});
		}); ` +
		sortBees +
		generateMatrixColor + `
		var CELL_WIDTH = 20;
		var MARGIN = 50;

		function drawMatrix(bees) {
			sortBees(bees);
			var svg = d3.select('body').append('svg')
																 .attr('width', bees.length * 30)
																 .attr('height', bees.length * 30);

			var offset = function(i) { return (i + 1)*CELL_WIDTH + MARGIN; };
			svg.selectAll('.beeXLables')
				.data(bees)
				.enter().append('text')
				.text(function (d) { return d.hive + "/" + d.id; })
				.attr('x', function(d, i) { return offset(i); })
				.attr('y', MARGIN)
				.attr('fill', '#EEE')
				.attr('transform', function(d, i) {
					return 'rotate(270 ' + offset(i) + ',' + MARGIN + ')'; }
				);
			svg.selectAll('.beeYLables')
				.data(bees)
				.enter().append('text')
				.text(function (d) { return d.hive + "/" + d.id; })
				.attr('x', MARGIN)
				.attr('y', function(d, i) { return offset(i); })
				.attr('fill', '#EEE')
				.style('text-anchor', 'end');

			$.ajax({
				url: '/apps/bh_collector/stats',
				context: document.body
			}).done(function(matrix) {
				drawCells(svg, bees, matrix);
			}).error(function() {
				alert('cannot load stats');
			});
		}

		var baseColor = d3.rgb(1, 1, 1);
		function drawCells(svg, bees, matrix) {
			var colorized = generateMatrixColor(matrix, baseColor);
			for (var i = 0; i < bees.length; i++) {
				var bx = bees[i];
				for (var j = 0; j < bees.length; j++) {
					var by = bees[j];
					var c = colorized[bx.id] && colorized[bx.id][by.id] || null;
					if (!c) {
						continue;
					}
					var rect = svg.append('rect')
												.attr('x', 55 + i * CELL_WIDTH)
												.attr('y', 55 + j * CELL_WIDTH)
												.attr('width', CELL_WIDTH)
												.attr('height', CELL_WIDTH);
					rect.attr('fill', c);
				}
			}
		}
	`
	aboutBody = `<div style="margin: 20px;">
								 Beehive Distributed Programming Framework
							 </div>`
)

func genPage(title, script, style, body string) []byte {
	var b bytes.Buffer
	b.WriteString(`
	<!DOCTYPE HTML>
	<html>
		<head>
			<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
			<title>`)
	b.WriteString(title)
	b.WriteString(`
			</title>
			<style>
				body {
					background: ` + bgColor + `;
					color: #EEE;
					font-family: 'Ubuntu Mono';
					margin: 0;
					padding: 0;
				}

				.bar {
					background: #345;
					padding: 1em 1em 1em 1em;
					vertical-align: middle;
					box-shadow: 0px 5px 10px #000;
				}

				.bar a {
					margin: 0em 1em 0em 1em;
					color: #999;
					text-decoration: none;
				}

				.bar a:hover {
					color: #FFF;
				}

				.bar .active {
					color: #FFF;
				}
	`)
	b.WriteString(style)
	b.WriteString(`
			</style>
			<script>
	`)
	b.WriteString(jQuery)
	b.WriteString(d3JS)
	b.WriteString(`
			</script>
			<script>`)
	b.WriteString(script)
	b.WriteString(`
			</script>
		</head>
		<body>
			<div class="bar">
		`)
	for _, p := range webPages {
		var c string
		if p.title == title {
			c = "active"
		}
		b.WriteString(`
				/// <a class="` + c + `" href="` + p.url + `">` + p.title + `</a>
		`)
	}
	b.WriteString(`
			</div>
		`)
	b.WriteString(body)
	b.WriteString(`
		</body>
	</html>
	`)
	return b.Bytes()
}

type webHandler struct{}

func (w webHandler) install(r *mux.Router) {
	for _, p := range webPages {
		p.page = genPage(p.title, p.script, p.style, p.body)
		r.HandleFunc(p.url, p.handle)
	}
}
