// richards.cm — Richards benchmark (scheduler simulation)
// Object-ish workload: dynamic dispatch, state machines, queuing.

def IDLE = 0
def WORKER = 1
def HANDLER_A = 2
def HANDLER_B = 3
def DEVICE_A = 4
def DEVICE_B = 5
def NUM_TASKS = 6

def TASK_RUNNING = 0
def TASK_WAITING = 1
def TASK_HELD = 2
def TASK_SUSPENDED = 3

function make_packet(link, id, kind) {
  return {link: link, id: id, kind: kind, datum: 0, data: array(4, 0)}
}

function scheduler() {
  var tasks = array(NUM_TASKS, null)
  var current = null
  var queue_count = 0
  var hold_count = 0
  var v1 = 0
  var v2 = 0
  var w_id = HANDLER_A
  var w_datum = 0
  var h_a_queue = null
  var h_a_count = 0
  var h_b_queue = null
  var h_b_count = 0
  var dev_a_pkt = null
  var dev_b_pkt = null

  var find_next = function() {
    var best = null
    var i = 0
    for (i = 0; i < NUM_TASKS; i++) {
      if (tasks[i] && tasks[i].state == TASK_RUNNING) {
        if (!best || tasks[i].priority > best.priority) {
          best = tasks[i]
        }
      }
    }
    return best
  }

  var hold_self = function() {
    hold_count++
    if (current) current.state = TASK_HELD
    return find_next()
  }

  var release = function(id) {
    var t = tasks[id]
    if (!t) return find_next()
    if (t.state == TASK_HELD) t.state = TASK_RUNNING
    if (t.priority > (current ? current.priority : -1)) return t
    return current
  }

  var queue_packet = function(pkt) {
    var t = tasks[pkt.id]
    var p = null
    if (!t) return find_next()
    queue_count++
    pkt.link = null
    pkt.id = current ? current.id : 0
    if (!t.queue) {
      t.queue = pkt
      t.state = TASK_RUNNING
      if (t.priority > (current ? current.priority : -1)) return t
    } else {
      p = t.queue
      while (p.link) p = p.link
      p.link = pkt
    }
    return current
  }

  // Idle task
  tasks[IDLE] = {id: IDLE, priority: 0, queue: null, state: TASK_RUNNING,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      v1--
      if (v1 == 0) return hold_self()
      if ((v2 & 1) == 0) {
        v2 = v2 >> 1
        return release(DEVICE_A)
      }
      v2 = (v2 >> 1) ^ 0xD008
      return release(DEVICE_B)
    }
  }

  // Worker task
  tasks[WORKER] = {id: WORKER, priority: 1000, queue: null, state: TASK_SUSPENDED,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      var i = 0
      if (!pkt) return hold_self()
      w_id = (w_id == HANDLER_A) ? HANDLER_B : HANDLER_A
      pkt.id = w_id
      pkt.datum = 0
      for (i = 0; i < 4; i++) {
        w_datum++
        if (w_datum > 26) w_datum = 1
        pkt.data[i] = 65 + w_datum
      }
      return queue_packet(pkt)
    }
  }

  // Handler A
  tasks[HANDLER_A] = {id: HANDLER_A, priority: 2000, queue: null, state: TASK_SUSPENDED,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      var p = null
      if (pkt) { h_a_queue = pkt; h_a_count++ }
      if (h_a_queue) {
        p = h_a_queue
        h_a_queue = p.link
        if (h_a_count < 3) return queue_packet(p)
        return release(DEVICE_A)
      }
      return hold_self()
    }
  }

  // Handler B
  tasks[HANDLER_B] = {id: HANDLER_B, priority: 3000, queue: null, state: TASK_SUSPENDED,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      var p = null
      if (pkt) { h_b_queue = pkt; h_b_count++ }
      if (h_b_queue) {
        p = h_b_queue
        h_b_queue = p.link
        if (h_b_count < 3) return queue_packet(p)
        return release(DEVICE_B)
      }
      return hold_self()
    }
  }

  // Device A
  tasks[DEVICE_A] = {id: DEVICE_A, priority: 4000, queue: null, state: TASK_SUSPENDED,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      var p = null
      if (pkt) { dev_a_pkt = pkt; return hold_self() }
      if (dev_a_pkt) {
        p = dev_a_pkt
        dev_a_pkt = null
        return queue_packet(p)
      }
      return hold_self()
    }
  }

  // Device B
  tasks[DEVICE_B] = {id: DEVICE_B, priority: 5000, queue: null, state: TASK_SUSPENDED,
    hold_count: 0, queue_count: 0,
    fn: function(pkt) {
      var p = null
      if (pkt) { dev_b_pkt = pkt; return hold_self() }
      if (dev_b_pkt) {
        p = dev_b_pkt
        dev_b_pkt = null
        return queue_packet(p)
      }
      return hold_self()
    }
  }

  var run = function(iterations) {
    var i = 0
    var pkt1 = null
    var pkt2 = null
    var steps = 0
    var pkt = null
    var next = null

    v1 = iterations
    v2 = 0xBEEF
    queue_count = 0
    hold_count = 0
    w_id = HANDLER_A
    w_datum = 0
    h_a_queue = null
    h_a_count = 0
    h_b_queue = null
    h_b_count = 0
    dev_a_pkt = null
    dev_b_pkt = null

    for (i = 0; i < NUM_TASKS; i++) {
      if (tasks[i]) {
        tasks[i].state = (i == IDLE) ? TASK_RUNNING : TASK_SUSPENDED
        tasks[i].queue = null
      }
    }

    pkt1 = make_packet(null, WORKER, 1)
    pkt2 = make_packet(pkt1, WORKER, 1)
    tasks[WORKER].queue = pkt2
    tasks[WORKER].state = TASK_RUNNING

    current = find_next()
    while (current && steps < iterations * 10) {
      pkt = current.queue
      if (pkt) {
        current.queue = pkt.link
        current.queue_count++
      }
      next = current.fn(pkt)
      if (next) current = next
      else current = find_next()
      steps++
    }
    return {queue_count: queue_count, hold_count: hold_count, steps: steps}
  }

  return {run: run}
}

return {
  richards_100: function(n) {
    var i = 0
    var s = null
    var result = null
    for (i = 0; i < n; i++) {
      s = scheduler()
      result = s.run(100)
    }
    return result
  },

  richards_1k: function(n) {
    var i = 0
    var s = null
    var result = null
    for (i = 0; i < n; i++) {
      s = scheduler()
      result = s.run(1000)
    }
    return result
  }
}
