/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

LogGraph.ServerGraph = function(asyncq, canvas, starttime, endtime, filter) {
    this.starttime = starttime;
    this.endtime = endtime;
    this.millis = endtime - starttime;
    this.nextserverid = 0;
    this.serveroffset = 100;
    this.filter = filter;
    
    this.pixels_per_tick =  20;
    this.ticker = new LogGraph.ticker();

    
    var paper = Raphael(canvas, 1, 1);

    var self = this;

    this.timescale = new LogGraph.timescale(starttime, endtime);
    this.objects = new Array();
    
    this.add = function(obj) {
	this.objects.push(obj);
    }
    
    this.tick_to_x = function (timestamp) {
	var x = timestamp * this.pixels_per_tick; 
	return x;
    }; 
    
    this._drawTime  = function(paper, x, time) {
	var p = paper.path("M" + x + " 0 L" + x + " " + paper.height);
	var t = paper.text(x, 10, dateFormat(time, "h:MM:ss,l"));
	
	t.hide();
	p.mouseover(function(evt) {
		t.show();
		p.attr({stroke: "red"});
	    });
	p.mouseout(function(evt) {
		t.hide();
		p.attr({stroke: "lightgray"});
	    });
	
	return p;
    };
    
    this.draw = function(paper) {
	var grid = paper.set();
	for (var i = 0; i < paper.height; i += 20) {
	    grid.push(paper.path("M0 " + i + " L" + paper.width + " " + i));
	}
	var lasttick = this.starttime;
	var scale = 500; // 500 ms
	
	var y = 0;
	
	for (var t = 0, len = this.ticker.ticks.length; t < len; t++) {
	    var basex = t * this.pixels_per_tick;
	    var thistick = this.ticker.ticks[t];
	    var nexttick = t + 1 == this.ticker.ticks.length ? this.endtime : this.ticker.ticks[t+1];
	    if (nexttick == thistick) {
		continue;
	    }
	    var time = thistick - lasttick;
	    var first = scale - (lasttick % scale);
	    
	    /*		for (var i = 0; (first+scale*i) < time; i++) {
			
			var toffset = first+scale*i;
			var x = basex + LogGraph._pixels_per_tick * toffset/time;
			grid.push(this._drawTime(paper, x, lasttick + toffset, grid));
			
			}*/
	    
	    
	    //grid.push(paper.path("M" + i + " 0 L" + i + " " + paper.height));
	    lasttick = thistick;
	}
	grid.attr({stroke: "lightgray"});
	this.timescale.draw(paper);
	
	for (o in this.objects) {
	    this.objects[o].draw(paper);
	}
    };


    var processdata = function(data) {
	var servermap = {};
	var servers = data.servers;
	var count = 0;
	for (s in servers) {
	    var server = new LogGraph.ServerGraph.server(self, "Server " + servers[s]);
	    servermap[servers[s]] = server;
	    self.add(server);
	    count++;
	}
	
	var messages = {};
	var events = data.events;
	for (var i in events) {
	    var e = events[i];
	    var t = e.time;
	    if (e.type == "stateChange") {
		servermap[e.server].addState(e.state, self.ticker.tick(e.time));
	    }
	    if (e.type == "postmessage") {
		src = servermap[e.src];
		dst = servermap[e.dst];
		var key = "key:s" + e.src + ",d" + e.dst + ",z" + e.zxid;
		    
		var m = new LogGraph.ServerGraph.message(self, src, self.ticker.tick(e.time), dst, e.zxid);
		messages[key] = m;
	    } 
	    if (e.type == "delivermessage") {
		var key = "key:s" + e.src + ",d" + e.dst + ",z" + e.zxid;
		
		var m = messages[key];
		if (m) {
		    m.dsttime = self.ticker.tick(e.time);
		    m.name = "Propose";
		    self.add(m);
		    delete messages[key];
		}
	    } 
	    if (e.type == "exception") {
		servermap[e.server].addException(self.ticker.tick(e.time), e.text, e.time);
	    }
	    count++;
	}
	
	for (var i in messages) {
	    var m = messages[i];
	    m.markIncomplete();
	    self.add(m);
	    count++;
	}

	if (count != 0) {
	    paper.setSize(self.tick_to_x(self.ticker.current()), 1000);	    
	    
	    var line = paper.path("M0 0 L0 1000");	    
	    line.attr({"stroke": "red", "stroke-dasharray": "- "});
	    var base = canvas.offsetLeft;// + ((canvas.offsetWidth - paper.width)/2);
	    canvas.onmousemove = function (evt) {
		var x = evt.screenX - base;
		
		line.attr({"path": "M" + x + " 0 L"+ x +" 1000"});
		
	    };

	    self.draw(paper);
	    return true;
	} else {
	    return false;
	}
    };
		
    var uri = "/data?start=" + self.starttime + "&end=" + self.endtime + "&filter=" + filter;

    LogGraph.loadData(asyncq, uri, processdata);    
};

LogGraph.ServerGraph.server = function (graph, name) {
    this.graph = graph;
    this.serverid = graph.nextserverid++;
    this.name = name;
    this.y = (this.serverid * 300 + graph.serveroffset);
    this.states = new Array();
    this.exception = new Array();
    
    this.addState = function(state, time) {
	this.states.push([state, time]);
    }
    
    this.addException = function(tick, exception, time) {
	this.exception.push(new LogGraph.ServerGraph.exception(this.graph, tick, exception, time));
    }
    
    this.draw = function(paper) {
	var st = paper.set();
	st.push(paper.path("M0 " + this.y + " L" + paper.width + " " + this.y));
	st.push(paper.text(20, this.y - 10, this.name));
	st.attr({stroke: "gray"});
	
	var numstates = this.states.length;
	
	for (s = 0; s < numstates; s++) {
	    var style = {};
	    switch (this.states[s][0]) {
	    case "INIT": style = {stroke: "yellow", "stroke-width":3}; break;
	    case "FOLLOWING": style = {stroke: "lightgreen", "stroke-width":7}; break;
	    case "LEADING": style = {stroke: "green", "stroke-width":10}; break;
	    case "LOOKING": style = {stroke: "orange", "stroke-width":5}; break;
	    }
	    var startx = this.graph.tick_to_x(this.states[s][1]);
	    var endx = s + 1 < numstates ? this.graph.tick_to_x(this.states[(s+1)][1]) : paper.width;
	    var p = paper.path("M" + startx + " " + this.y + " L" + endx + " " + this.y);
	    p.attr(style);
	}
	
	for (e in this.exception) {
	    this.exception[e].draw(paper, this);
	}
    }
};    
    
LogGraph.ServerGraph.message = function(graph, src, srctime, dst, zxid) {
    this.graph = graph;
    this.src = src;
    this.srctime = srctime;
    this.dst = dst;
    this.dsttime = 0; //dsttime;
    this.name = "Unknown";
    this.zxid = zxid;
    this.moreinfo = "No extra information";
    this.incomplete = false;
    
    this.markIncomplete = function() {
	this.incomplete = true;
	this.dsttime = this.srctime;
    }

    this.draw = function(paper) {
	var srcx = this.graph.tick_to_x(this.srctime);
	var dstx = this.graph.tick_to_x(this.dsttime);
	
	var arrow = paper.set();
	var p = paper.path("M" + srcx + " " + this.src.y + " L" + dstx + " " + this.dst.y);
	arrow.push(p);
	
	var tx = (srcx + dstx)/2;
	var ty = (this.src.y + this.dst.y)/2;
	var t = paper.text(tx, ty, this.name);
	
	var gradient = (this.dst.y - this.src.y)/(dstx - srcx);
	var angle = Math.atan(gradient) * 57.2958;
	t.rotate(angle, true);
	
	var arrowl = paper.path("M" + dstx + " " + this.dst.y + " L" + (dstx - 10) +" " + this.dst.y);
	arrowl.rotate(angle + 20, dstx, this.dst.y);
	arrow.push(arrowl);
	var arrowr = paper.path("M" + dstx + " " + this.dst.y + " L" + (dstx - 10) +" " + this.dst.y);
	arrowr.rotate(angle - 20, dstx, this.dst.y);
	arrow.push(arrowr);
	
	arrow.attr({"stroke-width": 2, stroke: "gray"});
	if (this.incomplete) {
	    arrow.attr({"stroke-dasharray": "- .", stroke: "pink", "stroke-width": 2});
	}
	arrow.mouseover(function(evt) {
		t.attr({"font-size": 20});
		arrow.attr({stroke: "red", "stroke-width": 3});
	    });
	arrow.mouseout(function(evt) {
		t.attr({"font-size": 10});
		
		if (this.incomplete) {
		    arrow.attr({stroke: "pink", "stroke-width": 2});
		} else {
		    arrow.attr({stroke: "gray", "stroke-width": 2});
		}
	    });
	
	

	arrow.click(function(evt) { 
		var popup = document.createElement("div");
		popup.className = "popUp";
		popup.innerHTML = "zxid: " + parseInt(this.zxid).toString(16);

		popup.style.top = evt.clientY;
		popup.style.left = evt.clientX;
		document.body.appendChild(popup);

		popup.onclick = function(evt) { 
		    document.body.removeChild(popup);
		};
	    });
    }
};
    
LogGraph.ServerGraph.exception = function(graph, tick, exceptiontext, time) {
    this.graph = graph;
    this.time = time;
    this.text = exceptiontext;
    this.tick = tick;
    
    var self = this;

    this.draw = function(paper, server) {
	var center = this.graph.tick_to_x(this.tick);
	var p = paper.circle(center, server.y, 5);
	p.attr({stroke: "orange", fill: "red"});
	
	p.mouseover(function(evt) {
		p.popup = document.createElement("div");
		p.popup.className = "popUp";
		p.popup.innerHTML = self.text.replace("\n", "<br/>");;
		p.popup.style.top = server.y + 50;
		p.popup.style.left = center + 25;
		document.body.appendChild(p.popup);

		p.animate({r: 10}, 500, "elastic");
	    });
	p.mouseout(function(evt) {
		document.body.removeChild(p.popup);
		p.animate({r: 5}, 100);
	    });
    }
};
    
