isRunning = true;
uint32_t = 0;
uint16_t = 0;
uint8_t = 0;

timerNowCounter = -1;
timerNowStep    = 1024;
timerNowStepSec = 1000;

timeoutID = new Array();
ledStatus = new Array();

function println(x)
{
  document.getElementById("console").innerHTML += x + '<br>';
}

function sizeof(x)
{
  return 0;
}

function ledOn(i)
{
  ledStatus[i] = true;
  switch (i) {
  case 0:
    document.getElementById('led0').style.backgroundColor = '#ff0000';
    break;
  case 1:
    document.getElementById('led1').style.backgroundColor = '#00ff00';
    break;
  case 2:
    document.getElementById('led2').style.backgroundColor = '#0000ff';
    break;
  }
}

function ledOff(i)
{
  ledStatus[i] = false;
  document.getElementById('led' + i).style.backgroundColor = '#ffffff';
}

function ledToggle(i)
{
  if (!isRunning) return;
  println("led" + i + "Toggle");
  if (ledStatus[i] != true) {
    ledOn(i);
  } else {
    ledOff(i);
  }
}

function timerStop(i)
{
  if (timeoutID[i] != undefined) {
    clearTimeout(timeoutID[i]);
    timeoutID[i] = undefined;
  }
}

function timerStartOneShot(i)
{
  return function(interval, fun) {
    if (!isRunning) return;
    println('timer' + i + ': interval: ' + interval + 'ms');
    timerStop(i);
    timeoutID[i] = setTimeout(function() { threadCreate(fun, 'timer' + i); }, interval);
  }
}

function timerStartPeriodic(i)
{
  return function(interval, fun) {
    if (!isRunning) return;
    println('periodic timer' + i + ' will fire in ' + interval + 'ms');
    timerStop(i);
    timeoutID[i] = setTimeout(function() { 
      threadCreate(fun, 'timer' + i); 
      timerStartPeriodic(i)(interval, fun);
    }, interval);
  }
}

function timerNowInc()
{
  if (!isRunning) return;
  timerNowCounter += timerNowStep;
  setTimeout(timerNowInc, timerNowStepSec);
}


function timerNow()
{
  if (timerNowCounter < 0) {
    timerNowCounter = 0;
    setTimeout(timerNowInc, timerNowStepSec);
  }
}

function radioStart()
{
  println("radioStart");
}

function threadContinue(t, name, r)
{
  if (!isRunning) return;

  var desc;
  d = undefined;
  while (d == undefined) {
    if (t == undefined) {
      // The function is not an iterater. This happens when there are
      // no blocking calls. Function bind to timers can sometimes be
      // like this.
      println(name + ": Thread done.");
      return;
    }
    try {
      d = t.send(r);
    } catch (e) {
      if (e != StopIteration) {
	// StopIteration are usually encounter when the function ends
	// without a return. We'll ignore this case and only report
	// the others.
	println(name + ": Thread done. " + e);
      }
      return;
    }
  }
  //println("DEBUG: len: " + d.length + " values: " + d);
  if (d == 'never') {
    println(name + ": Thread done. " + d);
  }
  if (typeof(d) == "object") {
    r = d[1];
    desc = "Wait for " + d[0] + "ms...";
    if (d.length > 2) {
      desc = d[2];
    }
    d = d[0];
  }
  if (d >= 0) {
    setTimeout(threadContinue, d, t, name, r);
    println(name + ": " + desc);
  } else {
    println(name + ": Thread done. " + d);
  }
}

function _threadCreate(t, name)
{
  threadContinue(t(), name, undefined)
}

function threadCreate(t, name)
{
  // When a thread is created we don't want to run immediatly so we'll
  // use a timeout of 0 to run after the current "task".
  setTimeout(_threadCreate, 0, t, name);
}

function collectionSend(data, len)
{
  println("Collection.send: " + len + " " + ppPacket(data));
  return 1000; // non-realistic wait of 1 second
}

function collectionReceive(i)
{
  return function(proto) {
    if (collectionReceiveStore[i] == undefined || collectionReceiveStore[i].length == 0) {
      println("Collection[" + i +"].receive never");
      return ['never', undefined];
    }
    p = collectionReceiveStore[i].shift();
    println("Collection[" + i +"].receive " + ppPacket(p.data) + " after " + p.delay + "ms");
    return [p.delay, p.data];
  }
}

function collectionSetRoot()
{
  println("Collection.setRoot");
}

function trackingSensorGet()
{
  return Math.random()*10000; // uniform distribution between 0-10 seconds
}

function ppAddr(addr)
{
  if (addr == 0xFFFF) return "*";
  return addr;
}

function ppPacket(data)
{
  s = "{";
  for (k in data) {
    s += " '" + k + "':" + data[k] + ",";
  }
  s = s.slice(0, s.length-1) + " }";
  return s;
}

function amSend(i)
{
  return function(addr, data, len) {
    return [10, undefined, "AM[" + i + "].sending to " + ppAddr(addr) + " " + ppPacket(data)];
  }
}

amReceiveStore = [];
collectionReceiveStore = [];

function amReceive(i)
{
  return function(proto) {
    if (amReceiveStore[i] == undefined || amReceiveStore[i].length == 0) {
      return ['never', undefined, "AM["+ i +"].receive will never be return"];
    }
    p = amReceiveStore[i].shift();
    return [p.delay, p.data, "AM["+ i +"].receive of " + ppPacket(p.data) + " will come after " + p.delay + "ms"];
  }
}

function logSeek(cookie)
{
  d = 100;
  return [d, undefined, "log seek will be done in "+ d +"ms"];
}

function logRead(data)
{
  d = 100;
  return [d, data, "log read will be done in "+ d +"ms"];
}

function logAppend(data)
{
  d = 10;
  return [10, undefined, "append will finish in "+ d +"ms"];
}

function sensorRead()
{
  d = 10;
  return [d, Math.round(Math.random()*1024), "sensor reading will be ready in "+ d +"ms"];
}

function threadSleep(i)
{
  return [i, undefined, "Sleeping for "+ i +"ms..."];
}

function Struct(desc)
{
  return function() {
    i = 0;
    for (v in desc) {
      if (i < arguments.length) {
	this[v] = arguments[i];
      } else {
	this[v] = desc[v];
      }
      i++;
    }
  }
}

function simAddMessage(amId, delay, data)
{
  if (amReceiveStore[amId] == undefined) {
    amReceiveStore[amId] = new Array();
  }

  amReceiveStore[amId].push({'delay': delay, 'data': data});
}

function simAddCollection(collectionId, delay, data)
{
  if (collectionReceiveStore[collectionId] == undefined) {
    collectionReceiveStore[collectionId] = new Array();
  }

  collectionReceiveStore[collectionId].push({'delay': delay, 'data': data});
}

TinyOS = {'Leds': [{'toggle': function () { ledToggle(0); }},
                   {'toggle': function () { ledToggle(1); }},
                   {'toggle': function () { ledToggle(2); }}], 
          'Radio': {'start': radioStart,
                    'ALL': 0xFFFF,
                    'AM': [{'send': amSend(0), 'receive': amReceive(0)},
			   {'send': amSend(1), 'receive': amReceive(1)},
			   {'send': amSend(2), 'receive': amReceive(2)},
			   {'send': amSend(3), 'receive': amReceive(3)},
                           {'send': amSend(4), 'receive': amReceive(4)}]},
          'Thread': {'create': threadCreate, 'sleep': threadSleep},
          'Time': {'now': timerNow},
          'Collection': [{'send': collectionSend, 'receive': collectionReceive(0), 'setRoot': collectionSetRoot}],
          'Position': {'X': 0, 'Y': 0},
          'TrackingSensor': {'get': trackingSensorGet},
          'Log': [{'seek': logSeek, 'read': logRead, 'append': logAppend}],
          'Sensor': [{'read': sensorRead}, {'read': sensorRead}, {'read': sensorRead}],
	  'Sim': {'addMessage': simAddMessage, 'addCollection': simAddCollection}
}

function Map(count, o)
{
  // dummy implementation
  this.get = function(key) { return o; }
  this.put = function(key,val) { }
}

function min(i, j)
{
  if (i < j) return i;
  return j;
}

function _start()
{
  if (!isRunning) {
    println("A program can only run once! Please pick another example.");
    return;
  }

  try {
    sim();
  } catch (e) {
    println("The sim() failed. main() was not run." + e);
    return;
  }
  TinyOS.Thread.create(main, "main");
}

function _stop()
{
  isRunning = false;
}

function _clearConsole()
{
  document.getElementById("console").innerHTML = '';
}

function _load()
{
  println("Loading...", document.body.childNodes.length);
  s = document.createElement('script');
  s.setAttribute('type','text/javascript;version=1.7');
  s.appendChild(document.createTextNode(document.getElementById("code").value));
  document.body.appendChild(s);
  println("Loading done", document.body.childNodes.length);
  println("You can press 'Start' now.");
}

