function setTimeout(callback, timeout) {
  const args = Array.prototype.slice.call(arguments, 2);

  if (Number(timeout) === 0) {
    // special case
    vertx.runOnContext(function (v) {
      callback.apply(globalThis, args);
    });
  } else {
    return vertx.setTimer(Number(timeout), function (t) {
      callback(globalThis, args);
    });
  }
}

function setInterval(callback, timeout) {
  const args = Array.prototype.slice.call(arguments, 2);

  if (Number(timeout) === 0) {
    // special case
    vertx.runOnContext(function (v) {
      callback.apply(globalThis, args);
    });
  } else {
    return vertx.setPeriodic(Number(timeout), function (t) {
      callback.apply(globalThis, args);
    });
  }
}

function clearTimeout(id) {
  if (id !== undefined) {
    return vertx.cancelTimer(id);
  }
}

function clearInterval(id) {
  if (id !== undefined) {
    return vertx.cancelTimer(id);
  }
}

/**
 * This is a Proxy to emulate the SockJS client
 */
const JsonObject = Java.type('io.vertx.core.json.JsonObject');
const DeliveryOptions = Java.type('io.vertx.core.eventbus.DeliveryOptions');

function wrapBody(body) {
  var json = JSON.stringify(body);
  return new JsonObject(json);
}

function unwrapMsg(msg) {
  var json = new JsonObject();
  var headers = new JsonObject();
  var msgHeaders = msg.headers();
  for (var i = msgHeaders.names().iterator();i.hasNext();) {
    var headerName = i.next();
    headers.put(headerName, msgHeaders.get(headerName));
  }
  json.put("body", msg.body());
  json.put("headers", headers);
  return JSON.parse(json.encode());
}

function wrapHeaders(headers) {
  var ret = new DeliveryOptions();
  if (typeof headers !== 'undefined') {
    for (var name in headers) {
      if (headers.hasOwnProperty(name)) {
        ret.addHeader(name, headers[name]);
      }
    }
  }
  return ret;
}

function unwrapError(err) {
  return {
    "failureType" : err.failureType().name(),
    "failureCode" : err.failureCode(),
    "message": err.getMessage()
  };
}

function SockJS() {
  var self = this;
  setTimeout(function () {
    self.onopen && self.onopen();
  }, 1);
}

SockJS.prototype.send = function(string) {
  var json = JSON.parse(string);
  var self = this;

  if (json.type === 'ping') {
    return;
  }

  var address = json.address;
  var headers = json.headers && wrapHeaders(json.headers);
  var message = wrapBody(json.body);

  var handler = function (ar) {
    if (ar.failed()) {
      var err = unwrapError(ar.cause());
      err.type = 'err';

      if (json.replyAddress) {
        err.address = json.replyAddress;
      }
      // error messages are always delivered
      self.onmessage && self.onmessage({data: JSON.stringify(err)});
    } else {
      var result = unwrapMsg(ar.result());

      if (json.replyAddress) {
        result.address = json.replyAddress;
        self.onmessage && self.onmessage({data: JSON.stringify(result)});
      }
    }
  };

  if (address) {
    var eb = vertx.eventBus();

    switch (json.type) {
      case 'send':
        if (headers) {
          eb.request(address, message, headers, handler)
        } else {
          eb.request(address, message, handler)
        }
        break;
      case 'publish':
        if (headers) {
          eb.publish(address, message, headers)
        } else {
          eb.publish(address, message)
        }
        break;
      default:
        throw new Error('Can not understand ' + json.type);
    }
  }
};

SockJS.prototype.close = function() {
  this.onclose && this.onclose();
};
