'use strict';
const op = {};
exports.op = op;

const fibers = require('fibers');
const nqp = require('./runtime.js');

const Null = require('./null.js');

function runTagged(currentHLL, tag, fiber, val, ctx) {
  let arg = val;
  while (1) {
    const control = fiber.run(arg);
    if (control.tag == tag || control.tag === Null) {
      if (control.value) {
        return control.value;
      } else {
        const cont = new Cont(tag, fiber);
        const value = nqp.retval(currentHLL, control.closure.$$call(ctx, null, cont));
        return value;
      }
    } else {
      arg = fibers.yield(control);
    }
  }
}

class Cont {
  constructor(tag, fiber) {
    this.tag = tag;
    this.fiber = fiber;
  }

  $$decont(ctx) {
    return this;
  }

  $$toBool(ctx) {
    return 1;
  }
};

op.continuationreset = function(ctx, currentHLL, tag, block) {
  if (block instanceof Cont) {
    return runTagged(currentHLL, tag, block.fiber, Null, ctx);
  } else {
    const fiber = fibers(function() {
      return {value: nqp.retval(currentHLL, block.$$call(ctx, null)), tag: tag};
    });
    fiber.tag = tag;
    return runTagged(currentHLL, tag, fiber, Null, ctx);
  }
};


op.continuationcontrol = function(ctx, currentHLL, protect, tag, closure) {
  return nqp.retval(currentHLL, fibers.yield({closure: closure, tag: tag}));
};

op.continuationinvoke = function(ctx, currentHLL, cont, inject) {
  const val = nqp.retval(currentHLL, inject.$$call(ctx, null));
  return runTagged(currentHLL, cont.tag, cont.fiber, val, ctx);
};
