/**
 * @license
 * Copyright 2016 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

addToLibrary({
  $PROXYFS__deps: ['$FS', '$PATH', '$ERRNO_CODES'],
  $PROXYFS: {
    mount(mount) {
      return PROXYFS.createNode(null, '/', mount.opts.fs.lstat(mount.opts.root).mode, 0);
    },
    createNode(parent, name, mode, dev) {
      if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) {
        throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
      }
      var node = FS.createNode(parent, name, mode);
      node.node_ops = PROXYFS.node_ops;
      node.stream_ops = PROXYFS.stream_ops;
      return node;
    },
    realPath(node) {
      var parts = [];
      while (node.parent !== node) {
        parts.push(node.name);
        node = node.parent;
      }
      parts.push(node.mount.opts.root);
      parts.reverse();
      return PATH.join(...parts);
    },
    node_ops: {
      getattr(node) {
        var path = PROXYFS.realPath(node);
        var stat;
        try {
          stat = node.mount.opts.fs.lstat(path);
        } catch (e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
        return {
          dev: stat.dev,
          ino: stat.ino,
          mode: stat.mode,
          nlink: stat.nlink,
          uid: stat.uid,
          gid: stat.gid,
          rdev: stat.rdev,
          size: stat.size,
          atime: stat.atime,
          mtime: stat.mtime,
          ctime: stat.ctime,
          blksize: stat.blksize,
          blocks: stat.blocks
        };
      },
      setattr(node, attr) {
        var path = PROXYFS.realPath(node);
        try {
          if (attr.mode !== undefined) {
            node.mount.opts.fs.chmod(path, attr.mode);
            // update the common node structure mode as well
            node.mode = attr.mode;
          }
          if (attr.atime || attr.mtime) {
            var atime = new Date(attr.atime || attr.mtime);
            var mtime = new Date(attr.mtime || attr.atime);
            node.mount.opts.fs.utime(path, atime, mtime);
          }
          if (attr.size !== undefined) {
            node.mount.opts.fs.truncate(path, attr.size);
          }
        } catch (e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      lookup(parent, name) {
        try {
          var path = PATH.join2(PROXYFS.realPath(parent), name);
          var mode = parent.mount.opts.fs.lstat(path).mode;
          var node = PROXYFS.createNode(parent, name, mode);
          return node;
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      mknod(parent, name, mode, dev) {
        var node = PROXYFS.createNode(parent, name, mode, dev);
        // create the backing node for this in the fs root as well
        var path = PROXYFS.realPath(node);
        try {
          if (FS.isDir(node.mode)) {
            node.mount.opts.fs.mkdir(path, node.mode);
          } else {
            node.mount.opts.fs.writeFile(path, '', { mode: node.mode });
          }
        } catch (e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
        return node;
      },
      rename(oldNode, newDir, newName) {
        var oldPath = PROXYFS.realPath(oldNode);
        var newPath = PATH.join2(PROXYFS.realPath(newDir), newName);
        try {
          oldNode.mount.opts.fs.rename(oldPath, newPath);
          oldNode.name = newName;
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      unlink(parent, name) {
        var path = PATH.join2(PROXYFS.realPath(parent), name);
        try {
          parent.mount.opts.fs.unlink(path);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      rmdir(parent, name) {
        var path = PATH.join2(PROXYFS.realPath(parent), name);
        try {
          parent.mount.opts.fs.rmdir(path);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      readdir(node) {
        var path = PROXYFS.realPath(node);
        try {
          return node.mount.opts.fs.readdir(path);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      symlink(parent, newName, oldPath) {
        var newPath = PATH.join2(PROXYFS.realPath(parent), newName);
        try {
          parent.mount.opts.fs.symlink(oldPath, newPath);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      readlink(node) {
        var path = PROXYFS.realPath(node);
        try {
          return node.mount.opts.fs.readlink(path);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
    },
    stream_ops: {
      open(stream) {
        var path = PROXYFS.realPath(stream.node);
        try {
          stream.nfd = stream.node.mount.opts.fs.open(path,stream.flags);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      close(stream) {
        try {
          stream.node.mount.opts.fs.close(stream.nfd);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      read(stream, buffer, offset, length, position) {
        try {
          return stream.node.mount.opts.fs.read(stream.nfd, buffer, offset, length, position);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      write(stream, buffer, offset, length, position) {
        try {
          return stream.node.mount.opts.fs.write(stream.nfd, buffer, offset, length, position);
        } catch(e) {
          if (!e.code) throw e;
          throw new FS.ErrnoError(ERRNO_CODES[e.code]);
        }
      },
      llseek(stream, offset, whence) {
        var position = offset;
        if (whence === {{{ cDefs.SEEK_CUR }}}) {
          position += stream.position;
        } else if (whence === {{{ cDefs.SEEK_END }}}) {
          if (FS.isFile(stream.node.mode)) {
            try {
              var stat = stream.node.node_ops.getattr(stream.node);
              position += stat.size;
            } catch (e) {
              throw new FS.ErrnoError(ERRNO_CODES[e.code]);
            }
          }
        }

        if (position < 0) {
          throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
        }

        return position;
      }
    }
  }
});

if (WASMFS) {
  error("using -lproxyfs is not currently supported in WasmFS.");
}
