(function() {
  var Filesystem, UINT64, fs, path, tmp;

  fs = require('fs');

  path = require('path');

  tmp = require('tmp');

  UINT64 = require('cuint').UINT64;

  Filesystem = (function() {
    function Filesystem(src) {
      this.src = path.resolve(src);
      this.header = {
        files: {}
      };
      this.offset = UINT64(0);
    }

    Filesystem.prototype.searchNodeFromDirectory = function(p) {
      var dir, dirs, json, _i, _len;
      json = this.header;
      dirs = p.split(path.sep);
      for (_i = 0, _len = dirs.length; _i < _len; _i++) {
        dir = dirs[_i];
        if (dir !== '.') {
          json = json.files[dir];
        }
      }
      return json;
    };

    Filesystem.prototype.searchNodeFromPath = function(p) {
      var name, node, _base;
      p = path.relative(this.src, p);
      if (!p) {
        return this.header;
      }
      name = path.basename(p);
      node = this.searchNodeFromDirectory(path.dirname(p));
      if (node.files == null) {
        node.files = {};
      }
      if ((_base = node.files)[name] == null) {
        _base[name] = {};
      }
      return node.files[name];
    };

    Filesystem.prototype.insertDirectory = function(p, shouldUnpack) {
      var node;
      node = this.searchNodeFromPath(p);
      if (shouldUnpack) {
        node.unpacked = shouldUnpack;
      }
      return node.files = {};
    };

    Filesystem.prototype.insertFile = function(p, shouldUnpack, file, options, callback) {
      var dirNode, handler, node, tr,
        _this = this;
      dirNode = this.searchNodeFromPath(path.dirname(p));
      node = this.searchNodeFromPath(p);
      if (shouldUnpack || dirNode.unpacked) {
        node.size = file.stat.size;
        node.unpacked = true;
        process.nextTick(callback);
        return;
      }
      handler = function() {
        var size;
        size = file.transformed ? file.transformed.stat.size : file.stat.size;
        if (size > 4294967295) {
          throw new Error("" + p + ": file size can not be larger than 4.2GB");
        }
        node.size = size;
        node.offset = _this.offset.toString();
        if (process.platform !== 'win32' && file.stat.mode & 0x40) {
          node.executable = true;
        }
        _this.offset.add(UINT64(size));
        return callback();
      };
      tr = options.transform && options.transform(p);
      if (tr) {
        return tmp.file(function(err, path) {
          var out, stream;
          if (err) {
            return handler();
          }
          out = fs.createWriteStream(path);
          stream = fs.createReadStream(p);
          stream.pipe(tr).pipe(out);
          return tr.on('end', function() {
            file.transformed = {
              path: path,
              stat: fs.lstatSync(path)
            };
            return handler();
          });
        });
      } else {
        return process.nextTick(handler);
      }
    };

    Filesystem.prototype.insertLink = function(p, stat) {
      var link, node;
      link = path.relative(fs.realpathSync(this.src), fs.realpathSync(p));
      if (link.substr(0, 2) === '..') {
        throw new Error("" + p + ": file links out of the package");
      }
      node = this.searchNodeFromPath(p);
      return node.link = link;
    };

    Filesystem.prototype.listFiles = function() {
      var files, fillFilesFromHeader;
      files = [];
      fillFilesFromHeader = function(p, json) {
        var f, fullPath, _results;
        if (!json.files) {
          return;
        }
        _results = [];
        for (f in json.files) {
          fullPath = path.join(p, f);
          files.push(fullPath);
          _results.push(fillFilesFromHeader(fullPath, json.files[f]));
        }
        return _results;
      };
      fillFilesFromHeader('/', this.header);
      return files;
    };

    Filesystem.prototype.getNode = function(p) {
      var name, node;
      node = this.searchNodeFromDirectory(path.dirname(p));
      name = path.basename(p);
      if (name) {
        return node.files[name];
      } else {
        return node;
      }
    };

    Filesystem.prototype.getFile = function(p, followLinks) {
      var info;
      if (followLinks == null) {
        followLinks = true;
      }
      info = this.getNode(p);
      if (info.link && followLinks) {
        return this.getFile(info.link);
      } else {
        return info;
      }
    };

    return Filesystem;

  })();

  module.exports = Filesystem;

}).call(this);
