/////////////////
// random init //
/////////////////

function initpage() {
  // clear the example value on focus
  $(".example").bind("focus", function(e){
    if ($(this).data("touched"))
      return;
    $(this).val("");
    $(this).data("touched", 1);
  });

  // check textarea max size
  $("textarea[maxlength]").bind("keyup", function(e) {
    var max = parseInt($(this).attr("maxlength"));
    if ($(this).val().length > max) {
      $(this).val($(this).val().substr(0, max));
      $(this).attr("scrollTop", $(this).attr("scrollHeight"));
    }
    // red flash
    if ($(this).data("showerror"))
      return;
    if ($(this).val().length == max) {
      $(this).data("showerror", 1);
      $(this).animate({backgroundColor:"#ffaaaa"}, 600
		     ).animate({backgroundColor:"#ffffff"}, 600);
    }
  });

  // confirm an action
  $(".confirm").click(function(e) {
    var title = $(this).attr("title");
    return (confirm("Voulez vous vraiment " +
		    (title ?
		     (title.substr(0, 1).toLowerCase() +
		      title.substr(1)) :
		     "effectuer cette action") +
		    " ?"));
  });
}

/////////////
// control //
/////////////

function initcontrol() {
  setstate("welcome");

  // turn metadata position into pixel position
  $.each(_metadata, function(i) {
    var z = _metadata[i];
    z.x = ((z.x - _robotenv.x) *
	   _robotenv.psize);
    z.y = -((z.y - _robotenv.y) *
	    _robotenv.psize);
    z.mx = z.x-MDSIZE/2;
    z.my = z.y-MDSIZE/2;
  });

  // display info
  updatecam();
  updatemap();

  // show / hide zones
  $(".setmap").click(function(e) {
    $(".map").show();
    $(".metadata").hide();
  });
  $(".setmetadata").click(function(e) {
    $(".map").hide();
    $(".metadata").show();
  });
  // $(".setmetadata").trigger("click");
  $(".postcontainer").hide();
  $("form").submit(postcomment);

  // set button style
  var robotspeed = _robotenv.speed * _robotenv.psize,
  robotturn = _robotenv.turn/180*Math.PI,
  repeatinterval = 500;

  $(".command").map(function(){
    var ele = $(this),
    tid = null;
    ele.hover(function(){if (_active) setbg(ele, 1)},
	      function(){if (_active) setbg(ele)});
    ele.mouseup(function(e) {
      clearInterval(tid);
    });
    ele.mousedown(function(e) {
      if (!hasaccess())
	return;
      var id = this.id;
      doaction(id);
      tid = setInterval(function() {
	if (!_active)
	  clearInterval(tid);
	else
	  doaction(id);
      }, repeatinterval);
    });
  });

  var lock = 0;
  function doaction(id) {
    if (!hasaccess())
      return;
    if (lock)
      return;
    lock = 1;
    setTimeout(function() { lock = 0; }, repeatinterval);
    var directions = {up:[robotspeed, 0],
		      down:[-robotspeed, 0],
		      right:[0, -robotturn],
		      left:[0, robotturn]};
    if (id in directions) {
      updaterobotpos(directions[id]);
      // autopilot is stopped, invalidate mark
      if (_mark) {
	_mark.state = "error";
	setTimeout(function() { _mark = null; }, 1500);
      }
    }
    var commands = {action:id};
    // adjust tilt, turn to zoom
    if (id in {camup:1, camdown:1})
      commands.tilt = _robotenv.tilt * (1-_pos[7]/2500);
    if (id in {left:1, right:1})
      commands.turn = _robotenv.turn * (1-_pos[7]/2500);

    sendcommand(commands);
  };

  if (!_logged)
    return;

  // keys listeners
  function readkey(e) {
    var tra = {38:"#up", 39:"#right",
	       40:"#down", 37:"#left"};
    var aele = $(document.activeElement).attr("type");
    if (aele != "text" && aele != "textarea")
      $("#catchinput").focus();
    if (typeof tra[e.keyCode] == "undefined" ||
	$(document.activeElement).attr("id") != "catchinput" ||
	!_active)
      return 0;
    return tra[e.keyCode];
  }
  $(document).bind("keydown", function(e) {
    if (!readkey(e))
      return;
    var item = $(readkey(e));
    setbg(item, 1);
    doaction(item.attr("id"));
  });
  $(document).bind("keyup", function(e) {
    if (!readkey(e))
      return;
    setbg($(readkey(e)));
  });

  // on exit
  if (!DEBUG)
    window.onbeforeunload = function(e) {
      return (_active ? "Votre session de contrôle prendra fin." :
	      "Votre place dans la file d'attente sera perdue.");
    };
  $(window).unload(function() {
    $.ajax({async:false,
	    type:"GET",
	    url:"/leavequeue/"});
  });
  return startqueue();
}

///////////
// queue //
///////////

var _sessiondone = 0;
function setstate(state) {
  // update state
  var message = "";
  var states = {
    welcome:function() {
      $(".queuecontainer").hide();
      _active = 0;
      message = "Bienvenue !"; },
    joinqueue:function() {
      $(".queuecontainer").show();
      _active = 0;
      message = "Vous avez rejoint la file d'attente. Veuillez patienter."; },
    startsession: function() {
      $(".queuecontainer").hide();
      _active = 1;
      message = "Vous avez maintenant accès au robot !";
      sendcommand({action:"up", speed:10});
      if (!DEBUG) alert(message); },
    endsession: function() {
      window.onbeforeunload = null;
      _active = 0;
      _sessiondone = 1;
      $(".queueposition").text(" ");
      clearInterval(_ctimer);
      $(".timer").text("0");
      message = "Votre session de contrôle est terminée."; }};
  states[state]();

  // show hide stuff
  $(".command").map(function(){
    var ele = $(this);
    if (_active) {
      ele.css("cursor", "pointer");
      ele.wTooltip({auto:false});
    } else {
      ele.wTooltip();
      ele.css("cursor", "help");
    }
    setbg(ele);
  });
  $(".queueposition").map(function() { $(this).wTooltip(); });

  // ajax alert
  $(".flashinfo").text(message).hide().css("color", "#00A0CF").fadeIn(1000);
  setTimeout(function() { $(".flashinfo").css("color", "#fff"); }, 5000);
}

function startqueue() {
  $.getJSON("/joinqueue/"+document.location.search, {}, function(session) {
    if (typeof session.error != "undefined")
      return alert(session.error);
    setstate("joinqueue");
    (session.orbited ?  orbitedqueue: pollingqueue
    )(session);
  });
}

function pollingqueue(session) {
  var timeleft = 0;
  var updatequeue = setInterval(function() {
    $.getJSON("/queuestats/", {r:now()},
	      function(res) {
		// get queue position
		var i = 0,
		data = res.queue,
		ok = res.ok,
		name = res.name;
		for (i = 0; i < data.length; i++) {
		  if (data[i] == session.userid)
		    break;
		}
		if (i == data.length) {
		  $.getJSON("/joinqueue/", {}, function(s) {1;});
		  return;
		}
		if (timeleft == 0 || timeleft > i * session.maxtime)
		  timeleft = i * session.maxtime;

		$(".queueposition").text((i+1)+"/"+(data.length));
		$(".pilot").text(name.substr(0, 1).toUpperCase() +
				 name.substr(1) +
				 " pilote actuellement le robot.");
		// start session
		if (i == 0 && ok) {
		  $(".pilot").text(" ");
		  clearInterval(updatequeue);
		  clearInterval(timer);
		  return userobot(session);
		}
	      });
  }, session.pollingperiod);

  // queue timer
  var timer = setInterval(function() {
    if (timeleft > 0)
      $(".timer").text(prettytime(timeleft--));
  }, 1000);
}

var _ctimer = null;
function userobot(session) {
  setstate("startsession");
  // usage timer
  _ctimer = setInterval(function() {
    var a = session.maxtime--;
    $(".timer").text(prettytime(a));
    if (a == 0) {
      setstate("endsession");
    }
  }, 1000);
}

/////////////
// orbited //
/////////////

function orbitedqueue(session) {
  Orbited.settings.port = 9000;
  TCPSocket = Orbited.TCPSocket;
  var timeleft = 0;
  var lastupdate = now();

  // if I don't get updates from stomp for n seconds,
  // I poll the server and broadcast the result to stomp
  var statstimer = setInterval(function() {
    if (now() - lastupdate > session.broadcastafter) {
      clearTimeout(statstimer);
      var updatequeue = setInterval(function() {
	$.get("/queuestats/", {},
	      function(data) { stomp.send(data, session.channel); });
      }, session.pollingperiod);
    }
  }, 1000);

  // stomp
  stomp = new STOMPClient();
  stomp.onopen = function() {};
  stomp.onclose = function(c) {
    trace('close: ' + c);
  };
  stomp.onerror = function(error) {trace("error: " + error);};
  stomp.onerrorframe = function(frame) {trace("Error frame: " + frame.body);};
  stomp.onconnectedframe = function() {
    stomp.subscribe(session.channel);
    trace("onconnectedframe");
  };
  stomp.onmessageframe = function(frame) {
    lastupdate = now();
    data = $.secureEvalJSON(frame.body);
    // update queue info
    var i = 0;
    for (i = 0; i < data.length; i++) {
      if (data[i].fields.user == session.userid)
	break;
    }
    if (timeleft == 0)
      timeleft = i * session.maxtime;
    $(".queueposition").text((i)+"/"+(data.length-1));

    // is it my turn yet?
    if (i == 0) {
      if (timer) {
	timer = cleartimer(timer);
	return userobot(session);
      }
    }
  };
  stomp.connect('localhost', 61613);

  // queue timer
  var timer = setInterval(function() {
    if (timeleft > 0)
      $(".timer").text(prettytime(timeleft--));
  }, 1000);
}

/////////
// map //
/////////

function updatemap() {
  // update map
  // ie bug
  try {
    var ctx = $("#mapc").get(0).getContext("2d"),
    ctxh = $("#mapc").attr("height"),
    ctxw = $("#mapc").attr("width");
  } catch(e) {
    return setTimeout(updatemap, 200); }

  var robotray = 7;
  var drawmap = function() {
    ctx.clearRect(0, 0, ctxw, ctxh);
    ctx.save()
    try { ctx.drawImage(mapimage, 0, 0);
	} catch(e) { trace(e); }

    ctx.lineWidth = 2;
    if (_mark) {
      var ray = 8;
      if (_mark.state == "ok") {
	ctx.beginPath();
	ctx.globalAlpha = .5;
	ctx.strokeStyle = "#00bb00";
	var x = _mark.x,
	y = _mark.y - 2*ray;
	ctx.arc(x, y, ray, Math.PI/4, Math.PI*3/4, true);
	// ctx.quadraticCurveTo(x, y + ray,
	// x, y + ray*2);
	// ctx.quadraticCurveTo(x, y + ray,
	// x + ray*5.6/8, y + ray*5.6/8);
	ctx.lineTo(x, y + ray*2);
	ctx.closePath();
	ctx.stroke();
	ctx.fillStyle = "#00bb00";
	ctx.globalAlpha = .3;
	ctx.fill();
      } else if (_mark.state == "error") {
	ctx.beginPath();
	ctx.strokeStyle = "#bb0000";
	ctx.globalAlpha = .7;
	var y = _mark.y - ray;
	ctx.moveTo(_mark.x - ray, y - ray);
	ctx.lineTo(_mark.x + ray, y + ray);
	ctx.moveTo(_mark.x + ray, y - ray);
	ctx.lineTo(_mark.x - ray, y + ray);
	ctx.stroke();
      }
    }

    // metadata
    for (var i in _metadata) {
      var z = _metadata[i];
      ctx.globalAlpha = 1;
      ctx.strokeStyle = METADATACOLOR;
      ctx.strokeRect(z.mx, z.my, MDSIZE, MDSIZE);
      ctx.globalAlpha = .3;
      ctx.fillStyle = METADATACOLOR;
      ctx.fillRect(z.mx, z.my, MDSIZE, MDSIZE);
    }

    // robot
    ctx.globalAlpha = 1;
    ctx.strokeStyle = ROBOTPOS;
    ctx.beginPath();
    ctx.moveTo(_pos[0], _pos[1]);
    ctx.lineTo(_pos[0] + Math.cos(_pos[2]) * robotray,
	       _pos[1] - Math.sin(_pos[2]) * robotray);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(_pos[0], _pos[1], robotray, 0, Math.PI * 2, true);
    ctx.stroke();
    ctx.beginPath();
    ctx.globalAlpha = .3;//0.6 - .05 * mapcount;
    ctx.arc(_pos[0], _pos[1], robotray, 0, Math.PI * 2, true);
    ctx.fillStyle = ROBOTPOS;
    ctx.fill();

    ctx.restore();
  };

  // update position
  var mapimage = new Image(),
  resetcounter = 0;
  setInterval(function() {
    $.getJSON("/position/", {r:now()}, function(data) {
      var pos = data[0];
      // tell when the robot is offline, get the new video stream
      if (data[1] == "False") {
	if (document.location.search == "?force") {
	  _vstream = "/static/img/cam.jpg";
	} else {
	  $(".tagload").show();
	  resetcounter++;
	  if (resetcounter == 200)
	    redirect("?");
	}
      } else {
	$(".tagload").hide();
	resetcounter = 0;
	_vstream = data[1];
      }

      // update the mark
      if (typeof pos == "undefined")
	return;

      if (_mark && _pos[9] != pos[9]) {
	var newstate = ({0:"ok", 1:"ok", 2:"ok",
			 3:"done",
			 4:"error", 5:"error", 6:"error", 7:"error"}
		       )[pos[8]];
	_mark.state = newstate;
	if (_mark.state == "error")
	  setTimeout(function() { _mark = null; }, 1500);
	else if (_mark.state == "done")
	  _mark = null;
      }
      try {
	_pos = [(pos[0] - _robotenv.x) * _robotenv.psize,
		-(pos[1] - _robotenv.y) * _robotenv.psize,
		pos[2]/180 * Math.PI].concat(pos.slice(3));

	trace(pos[0]);
	trace(_robotenv.x);
	trace(_pos[0]);

      } catch(e) { 1; }
      drawmap();
    });
  }, 1200);
  mapimage.src = MEDIA_URL+"/img/map.jpg";

  // user interaction
  var map = $("#mapc").offset();
  function checkelement(e) {
    for (var i in _metadata) {
      var x = e.pageX - map.left,
      y = e.pageY - map.top,
      z = _metadata[i];
      if (z.mx < x && x < z.mx + MDSIZE &&
	  z.my < y && y < z.my + MDSIZE)
	return z;
    }
    return null;
  };

  var previous = -1;
  $("#mapc").mousemove(function(e) {
    map = $("#mapc").offset();
    var el = checkelement(e);
    if (el != previous) {
      previous = el;
      $(this).css("cursor",
		  previous ? "pointer" : "crosshair");
      if (previous) {
	$(".tag").show();
	$(".tag").text(el.name)
	$(".tag").css("left", (el.mx + map.left +
			       (MDSIZE / 2 -
				($(".tag").outerWidth() / 2))));
	$(".tag").css("top", (el.my - MDSIZE + map.top));
      } else
	$(".tag").hide();
    }
  });

  $("#mapc").click(function(e) {
    var el = checkelement(e);
    if (el)
      return setmetadata(el);
    if (!hasaccess())
      return;
    _mark = {x:(e.pageX - map.left),
	     y:(e.pageY - map.top),
	     state:"ok"};

    var x = _mark.x * 1/_robotenv.psize + _robotenv.x,
    y = _robotenv.y - _mark.y * 1/_robotenv.psize;
    sendcommand({action:"sendtopos", x:x, y:y});
    drawmap();
  });
}

/////////
// cam //
/////////

function updatecam() {

  // update cam
  // ie bug
  try {
    var arr = [],
    ctx2 = $("#cam").get(0).getContext("2d"),
    ctx2h = $("#cam").attr("height"),
    ctx2w = $("#cam").attr("width");
  } catch(e) {
    return setTimeout(updatecam, 200); }

  var drawcam = function() {
    elements = [];

    ctx2.clearRect(0, 0, ctx2w, ctx2h);
    ctx2.save();

    // preloading on ie doesn't work in canvas
    if (!$.support.cssFloat)
      ctx2.element_.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+image.src+"', sizingMethod='scale');";
    else
      try { ctx2.drawImage(image, 0, 0, ctx2w, ctx2h);
	  } catch(e) { trace(e); }

    // draw markers
    if (_pos[6] == _robotenv.defaulttilt &&
	_pos[7] == _robotenv.defaultzoom) {
      for (var i in getmarks()) {
	var item = arr[i];
	if (!item) continue;
	ctx2.globalAlpha = 1;
	ctx2.lineWidth = 1;
	ctx2.strokeStyle = METADATACOLOR;
	ctx2.fillStyle = METADATACOLOR;
	ctx2.strokeRect(item.x, item.y, item.h, item.h);
	ctx2.globalAlpha = 0.15;
	ctx2.fillRect(item.x, item.y, item.h, item.h);
      }
    }
    ctx2.restore();
  };

  // find the position / size of the metadata on the screen
  // x1, y1 = metadata position,
  // x2, y2 = robot position, rob = robot angle
  var range = _robotenv.camangle*.9;
  var metpos = function(x1, y1, x2, y2, rob, id) {
    var met = Math.atan2(y2-y1, x1-x2);
    if (met < 0)
      met += 2 * Math.PI;
    var res = met - rob;
    if (res > Math.PI)
      res = res - 2*Math.PI;
    // if visible, return position on the screen and distance
    if (Math.abs(res) < range) {
      var distance = Math.sqrt(Math.pow(Math.abs(x1-x2), 2) +
			       Math.pow(Math.abs(y1-y2), 2));
      // if object is too far, don't display
      if (distance / _robotenv.psize > _robotenv.maxdistance) {
	return false;
      }

      // set object height
      var height = (1 / distance * ctx2h * 13);
      height = (height < 20 ? 20 :
		(height > 2*ctx2h/3 ? 2*ctx2h/3 :
		 height));
      return {x:ctx2w/2 - ctx2w * (res) / range/2 - height / 2 ,
	      y:ctx2h/2- height/2,
	      h:height,
	      id:id};
    }
    return false;
  };

  // check for visible marks
  var prev = [0, 0, 0],
  getmarks = function() {
    if (prev[0] == _pos[0]&&
	prev[1] == _pos[1]&&
	prev[2] == _pos[2])
      return arr;
    arr = [];
    for (var i in _metadata) {
      var item = metpos(_metadata[i].x,
			_metadata[i].y,
			_pos[0],
			_pos[1],
			_pos[2],
			_metadata[i].id);
      // add if visible
      if (item)
	arr.push(item);
    }

    // hide the elements overlapping
    arr.sort(function(a, b) { return b.h - a.h; });
    var s = arr.length;
    for (var i=0; i < s-1; i++) {
      if (!arr[i]) continue;
      var x = arr[i].x + arr[i].h / 2,
      h = arr[i].h;
      for (var j=i+1; j < s; j++) {
	if (!arr[j]) continue;
	var xp = arr[j].x + arr[j].h / 2;
	if (Math.abs(xp - x) < h/2) {
	  arr[j] = null;
	}
      }
    }
    return arr;
  };

  var image = new Image(),
  loaded = 0;
  image.onload = function() {
    if ($.browser.mozilla)
      // the image is supposed to be loaded but isn't always on Firefox
      setTimeout(drawcam, 50);
    else
      drawcam();
    loaded = -1;
  };

  var refreshcam = function() {
    if (loaded < 0) {
      image.src = _vstream + "?" + now();
      loaded = 6;
    }
    loaded--;
  };
  setInterval(refreshcam, 250);

  // mouse events

  // cam interactive elements
  var checkelement = function(e) {
    var x = (e.pageX - offset.left),
    y = (e.pageY - offset.top);
    for (var i in arr) {
      if (!arr[i]) continue;
      var el = arr[i];
      if (el.x < x && x < el.x + el.h &&
	  el.y < y && y < el.y + el.h) {
	return el;
      }
    }
    return null;
  };
  var offset = $("#cam").offset();
  $(".tagload").css("left", offset.left).css("top", offset.top);
  $("#cam").click(function(e) {
    setmetadata(checkelement(e));
  });

  var previous = null;
  $("#cam").mousemove(function(e) {
    offset = $("#cam").offset();
    var el = checkelement(e);
    if (previous != el) {
      previous = el;

      // update the cursor
      $(this).css("cursor",
		  previous ? "pointer" : "default");

      // show the metadata tag
      if (previous) {
	$(".tag").show();
	$(".tag").text(_metadata[el.id].name)
	$(".tag").css("left", (el.x + offset.left +
			       (el.h / 2 -
				($(".tag").outerWidth() / 2))));
	$(".tag").css("top", (el.y + el.h + offset.top));
      } else
	$(".tag").hide();
    }
  });

}

//////////////
// metadata //
//////////////

function setmetadata(i) {
  if (i == null)
    return;
  $(".tag").hide();
  _metadataid = i.id;
  var el = _metadata[_metadataid];
  $(".article").html("<h2>"+el.name+"</h2>"+
		     el.content.replace(/\n/g, "<br>"));
  $(".setmetadata").trigger("click");
  loadcomments();
  // if (_logged)
  $(".postcontainer").show();
}

function postcomment() {
  if (!_logged) {
    alert(NOTCONNECTED);
    return false;
  }
  $.post("/postcomment/", {content:$(".commentcontent").val(),
			   metadata:_metadataid},
	 function(data) { loadcomments(); }
	);
  $(".commentcontent").val("");
  return false;
}

function loadcomments() {
  id = _metadataid;
  $(".comments").html("<img src='"+MEDIA_URL+"/img/loader.gif'>");
  $.getJSON("/loadcomments/", {id:id, r:now()}, function(data) {
    var buf = "<h2>Commentaires</h2><br>";
    for (var i in data) {
      var el = data[i];
      buf += ("<b>" + el.username + "</b> le " +
	      el.created + " "+
	      (el.username == _username ?
	       ("<span class='l' onclick='deletecomment(" +
		el.id + ")'>Supprimer</span>") :
	       "") + "<br>" +
	      el.content + "<br><br>");
    }
    if (!data.length)
      buf = "Aucun commentaire";
    $(".comments").html(buf);
  });
}

function deletecomment(id) {
  $.get("/deletecomment/", {id:id}, function(data) {
    loadcomments();
  });
}


///////////////
// utilities //
///////////////

function prettytime(seconds) {
  return (seconds < 1 ? "0" :
	  (Math.floor(seconds / 60) + ":" +
	   function(x){return x < 10 ? "0"+x : x;}(seconds % 60)));
}

function redirect(url) {
  window.onbeforeunload = null;
  document.location.href = url;
}

function sendcommand(data) {
  if (data.action == "exit")
    return redirect("/");
  $("#cam").trigger("mousemove");
  $.getJSON("/sendcommand/", data,
	    function(res) {
	      trace(res);
	      if (res == -1)
		return setstate("endsession");

	      if (res == "fail" && 1) {
		// alert("La connexion avec le robot s'est interrompue.");
		// redirect("?");
	      }
	    });
}

function setbg(item, over) {
  var name = (item.attr("id") +
	      (typeof over == "undefined" ? "" : "over"));
  item.css("background-image",
	   "url("+MEDIA_URL+"/img/"+name+".png)");
};

function trace(obj) {
  if (typeof(console) != "undefined")
    console.log(obj);
  return obj;
}
function tracew(obj, depth, indent) {
  if (typeof(console) == "undefined")
    return obj;
  for (var i in obj) {
    trace(i + "=" + obj[i]);
  }
}

function updaterobotpos(data) {
  var a = _pos[2] + data[1];
  _pos[0] = Math.cos(_pos[2]) * data[0] + _pos[0];
  _pos[1] = -Math.sin(_pos[2]) * data[0] + _pos[1];
  _pos[2] = (a < 0 ? a + 2*Math.PI : a) % (2 * Math.PI);
}

function identity(obj) { return obj; }
function now() { return (new Date()).getTime(); }
function cleartimer(id) {
  clearTimeout(id);
  clearInterval(id);
  return 0;
}

function hasaccess() {
  if (!_active) {
    alert(_logged ?
	  (_sessiondone ?
	   "Votre session de contrôle est terminée." :
	   "Vous n'avez pas encore le contrôle du robot. Veuillez patientez.") :
	  NOTCONNECTED);
  }
  return _active;
}

/////////////////////
// test ui helpers //
/////////////////////

function $fu(func) {
  if (typeof(fireunit) == "undefined")
    return;
  $(func);
}
function $u(action, sel, val) {
  if (action == "value" &&
      ($(sel).val() != "" || $(sel).text() != ""))
    return;
  trace(sel + "  " + action.toUpperCase() + "  " + val);
  fireunit[action]($(sel).get(0), val);
}
function $r() {
  return Math.floor(Math.random()*100000);
}
