exports.debugLevel = 0; // Increase to get more verbose debug output

function debugMessage (msg) {
  if (exports.debugLevel > 0) {
    node.error(__filename + ": " + msg.toString());
  }
}

function debugObject (obj) {
  if (exports.debugLevel > 0) {
    node.error(__filename + ": " + JSON.stringify(obj));
  }
}

exports.read = node.fs.cat;

exports.write = function (filename, data, encoding) {
  var promise = new node.Promise();

  encoding = encoding || "utf8"; // default to utf8

  node.fs.open(filename, node.O_WRONLY | node.O_TRUNC | node.O_CREAT, 0666)
    .addCallback(function (fd) {
      function doWrite (_data) {
        node.fs.write(fd, _data, 0, encoding)
          .addErrback(function () {
            node.fs.close(fd);
          })
          .addCallback(function (written) {
            if (written == _data.length) {
              node.fs.close(fd);
            } else {
              doWrite(_data.slice(written));
            }
          });
      }
      doWrite(data);
    })
    .addErrback(function () {
      promise.emitError()
    });

  return promise;
};

exports.File = function (filename, mode, options) {
  var self = this;

  options = options || {};
  self.encoding = options.encoding || "utf8";

  self.filename = filename;

  self.actionQueue = [];
  self.currentAction = null;

  switch (mode) {
    case "r":
      self.flags = node.O_RDONLY;
      break;

    case "r+":
      self.flags = node.O_RDWR;
      break;

    case "w":
      self.flags = node.O_CREAT | node.O_TRUNC | node.O_WRONLY;
      break;

    case "w+":
      self.flags = node.O_CREAT | node.O_TRUNC | node.O_RDWR;
      break;

    case "a":
      self.flags = node.O_APPEND | node.O_CREAT | node.O_WRONLY; 
      break;

    case "a+":
      self.flags = node.O_APPEND | node.O_CREAT | node.O_RDWR; 
      break;

    default:
      throw new Error("Unknown mode");
  }

  self.open(self.filename, self.flags, 0666).addCallback(function (fd) {
    debugMessage(self.filename + " opened. fd = " + fd);
    self.fd = fd;
  }).addErrback(function () {
    self.emit("error", ["open"]);
  });
};

var proto = exports.File.prototype;

proto._maybeDispatch = function () {
  var self = this;

  if (self.currentAction) return;
  self.currentAction = self.actionQueue.shift();
  if (!self.currentAction) return;

  debugObject(self.currentAction);

  var args = self.currentAction.args || [];

  if (self.currentAction.method != "open") {
    args.unshift(self.fd);
  }

  var method = self.currentAction.method;

  if (!args[3] && (method == "read" || method == "write")) {
    args[3] = self.encoding;
  }

  var promise = node.fs[method].apply(self, args);

  var userPromise = self.currentAction.promise;

  promise.addCallback(function () {
    node.assert(self.currentAction.promise == userPromise);
    userPromise.emitSuccess.apply(userPromise, arguments);
    self.currentAction = null;
    self._maybeDispatch();
  }).addErrback(function () {
    debugMessage("Error in method " + method);
    node.assert(self.currentAction.promise == userPromise);
    userPromise.emitError.apply(userPromise, arguments);
    self.currentAction = null;
    self._maybeDispatch();
  });
};

proto._queueAction = function (method, args) {
  var userPromise = new node.Promise;
  this.actionQueue.push({ method: method, args: args, promise: userPromise });
  this._maybeDispatch();
  return userPromise;
};

// FIXME the following can probably be DRY'd up with some fancy getter
// stuff.

proto.open = function (filename, flags, mode) {
  return this._queueAction("open", [filename, flags, mode]);
};

proto.write = function (data, pos, encoding) {
  return this._queueAction("write", [data, pos, encoding]);
};

proto.read = function (length, pos, encoding) {
  return this._queueAction("read", [length, pos, encoding]);
};

proto.close = function () {
  return this._queueAction("close");
};
