var cellfs = {}

var fd = use('fd')
var miniz = use('miniz')
var qop = use('qop')
var wildstar = use('wildstar')

var mounts = []

var writepath = "."

function normalize_path(path) {
  if (!path) return ""
  return replace(path, /^\/+|\/+$/, "")
}

function mount_exists(mount, path) {
  var result = false
  var full_path = null
  var st = null
  var _check = null
  if (mount.type == 'zip') {
    _check = function() {
      mount.handle.mod(path)
      result = true
    } disruption {}
    _check()
  } else if (mount.type == 'qop') {
    _check = function() {
      result = mount.handle.stat(path) != null
    } disruption {}
    _check()
  } else {
    full_path = fd.join_paths(mount.source, path)
    _check = function() {
      st = fd.stat(full_path)
      result = st.isFile || st.isDirectory
    } disruption {}
    _check()
  }
  return result
}

function is_directory(path) {
  var res = resolve(path)
  var mount = res.mount
  var result = false
  var full_path = null
  var st = null
  var _check = null
  if (mount.type == 'zip') {
    _check = function() {
      result = mount.handle.is_directory(path)
    } disruption {}
    _check()
  } else if (mount.type == 'qop') {
    _check = function() {
      result = mount.handle.is_directory(path)
    } disruption {}
    _check()
  } else {
    full_path = fd.join_paths(mount.source, path)
    _check = function() {
      st = fd.stat(full_path)
      result = st.isDirectory
    } disruption {}
    _check()
  }
  return result
}

function resolve(path, must_exist) {
  var idx = null
  var mount_name = ""
  var rel_path = ""
  var mount = null
  var found_mount = null
  var npath = normalize_path(path)

  if (starts_with(npath, "@")) {
    idx = search(npath, "/")

    if (idx == null) {
      mount_name = text(npath, 1)
      rel_path = ""
    } else {
      mount_name = text(npath, 1, idx)
      rel_path = text(npath, idx + 1)
    }

    arrfor(mounts, function(m) {
      if (m.name == mount_name) {
        mount = m
        return true
      }
    }, false, true)

    if (!mount) {
      print("Unknown mount point: @" + mount_name); disrupt
    }

    return { mount: mount, path: rel_path }
  }

  arrfor(mounts, function(m) {
    if (mount_exists(m, npath)) {
      found_mount = { mount: m, path: npath }
      return true
    }
  }, false, true)

  if (found_mount) {
    return found_mount
  }

  if (must_exist) {
    print("File not found in any mount: " + npath); disrupt
  }
}

function mount(source, name) {
  var st = fd.stat(source)
  var blob = null
  var qop_archive = null
  var zip = null

  var _try_qop = null

  var mount_info = {
    source: source,
    name: name || null,
    type: 'fs',
    handle: null,
    zip_blob: null
  }

  if (st.isDirectory) {
    mount_info.type = 'fs'
  } else if (st.isFile) {
    blob = fd.slurp(source)

    qop_archive = null
    _try_qop = function() {
      qop_archive = qop.open(blob)
    } disruption {}
    _try_qop()

    if (qop_archive) {
        mount_info.type = 'qop'
        mount_info.handle = qop_archive
        mount_info.zip_blob = blob
    } else {
        zip = miniz.read(blob)
        if (!is_object(zip) || !is_function(zip.count)) {
          print("Invalid archive file (not zip or qop): " + source); disrupt
        }

        mount_info.type = 'zip'
        mount_info.handle = zip
        mount_info.zip_blob = blob
    }
  } else {
    print("Unsupported mount source type: " + source); disrupt
  }

  push(mounts, mount_info)
}

function unmount(name_or_source) {
  mounts = filter(mounts, function(m) {
    return m.name != name_or_source && m.source != name_or_source
  })
}

function slurp(path) {
  var res = resolve(path, true)
  var data = null
  var full_path = null
  if (!res) { print("File not found: " + path); disrupt }

  if (res.mount.type == 'zip') {
    return res.mount.handle.slurp(res.path)
  } else if (res.mount.type == 'qop') {
    data = res.mount.handle.read(res.path)
    if (!data) { print("File not found in qop: " + path); disrupt }
    return data
  } else {
    full_path = fd.join_paths(res.mount.source, res.path)
    return fd.slurp(full_path)
  }
}

function slurpwrite(path, data) {
  var full_path = writepath + "/" + path

  var f = fd.open(full_path, 'w')
  fd.write(f, data)
  fd.close(f)
}

function exists(path) {
  var res = resolve(path, false)
  if (starts_with(path, "@")) {
    return mount_exists(res.mount, res.path)
  }
  return res != null
}

function stat(path) {
  var res = resolve(path, true)
  var mod = null
  var s = null
  var full_path = null
  if (!res) { print("File not found: " + path); disrupt }

  if (res.mount.type == 'zip') {
    mod = res.mount.handle.mod(res.path)
    return {
      filesize: 0,
      modtime: mod * 1000,
      isDirectory: false
    }
  } else if (res.mount.type == 'qop') {
    s = res.mount.handle.stat(res.path)
    if (!s) { print("File not found in qop: " + path); disrupt }
    return {
        filesize: s.size,
        modtime: s.modtime,
        isDirectory: s.isDirectory
    }
  } else {
    full_path = fd.join_paths(res.mount.source, res.path)
    s = fd.stat(full_path)
    return {
      filesize: s.size,
      modtime: s.mtime,
      isDirectory: s.isDirectory
    }
  }
}

function searchpath() {
  return array(mounts)
}

function mount_package(name) {
  if (name == null) {
    mount('.', null)
    return
  }

  var shop = use('internal/shop')
  var dir = shop.get_package_dir(name)

  if (!dir) {
    print("Package not found: " + name); disrupt
  }

  mount(dir, name)
}

function match(str, pattern) {
  return wildstar.match(pattern, str, wildstar.WM_PATHNAME | wildstar.WM_PERIOD | wildstar.WM_WILDSTAR)
}

function rm(path) {
  var res = resolve(path, true)
  var full_path = null
  var st = null
  if (res.mount.type != 'fs') { print("Cannot delete from non-fs mount"); disrupt }

  full_path = fd.join_paths(res.mount.source, res.path)
  st = fd.stat(full_path)
  if (st.isDirectory) fd.rmdir(full_path)
  else fd.unlink(full_path)
}

function mkdir(path) {
  var full = fd.join_paths(writepath, path)
  fd.mkdir(full)
}

function set_writepath(path) {
  writepath = path
}

function basedir() {
  return fd.getcwd()
}

function prefdir(org, app) {
   return "./"
}

function realdir(path) {
   var res = resolve(path, false)
   if (!res) return null
   return fd.join_paths(res.mount.source, res.path)
}

function enumerate(_path, recurse) {
    var path = _path == null ? "" : _path

    var res = resolve(path, true)
    var results = []
    var full = null
    var st = null
    var all = null
    var prefix = null
    var prefix_len = null
    var seen = null

    function visit(curr_full, rel_prefix) {
       var list = fd.readdir(curr_full)
       if (!list) return

       arrfor(list, function(item) {
          var item_rel = rel_prefix ? rel_prefix + "/" + item : item
          var child_st = null
          push(results, item_rel)

          if (recurse) {
             child_st = fd.stat(fd.join_paths(curr_full, item))
             if (child_st.isDirectory) {
                visit(fd.join_paths(curr_full, item), item_rel)
             }
          }
       })
    }

    if (res.mount.type == 'fs') {
        full = fd.join_paths(res.mount.source, res.path)
        st = fd.stat(full)
        if (st && st.isDirectory) {
           visit(full, "")
        }
    } else if (res.mount.type == 'qop') {
        all = res.mount.handle.list()
        prefix = res.path ? res.path + "/" : ""
        prefix_len = length(prefix)

        seen = {}

        arrfor(all, function(p) {
            var rel = null
            var slash = null
            if (starts_with(p, prefix)) {
                rel = text(p, prefix_len)
                if (length(rel) == 0) return

                if (!recurse) {
                    slash = search(rel, '/')
                    if (slash != null) {
                        rel = text(rel, 0, slash)
                    }
                }

                if (!seen[rel]) {
                    seen[rel] = true
                    push(results, rel)
                }
            }
        })
    }

    return results
}

function globfs(globs, _dir) {
   var dir = _dir == null ? "" : _dir
   var res = resolve(dir, true)
   var results = []
   var full = null
   var st = null
   var all = null
   var prefix = null
   var prefix_len = null

   function check_neg(path) {
        var result = false
        arrfor(globs, function(g) {
            if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
                result = true
                return true
            }
        }, false, true)
        return result
    }

   function check_pos(path) {
        var result = false
        arrfor(globs, function(g) {
            if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
                result = true
                return true
            }
        }, false, true)
        return result
    }

   function visit(curr_full, rel_prefix) {
       if (rel_prefix && check_neg(rel_prefix)) return

       var list = fd.readdir(curr_full)
       if (!list) return

       arrfor(list, function(item) {
          var item_rel = rel_prefix ? rel_prefix + "/" + item : item

          var child_full = fd.join_paths(curr_full, item)
          var child_st = fd.stat(child_full)

          if (child_st.isDirectory) {
              if (!check_neg(item_rel)) {
                 visit(child_full, item_rel)
              }
          } else {
              if (!check_neg(item_rel) && check_pos(item_rel)) {
                  push(results, item_rel)
              }
          }
       })
   }

   if (res.mount.type == 'fs') {
        full = fd.join_paths(res.mount.source, res.path)
        st = fd.stat(full)
        if (st && st.isDirectory) {
           visit(full, "")
        }
   } else if (res.mount.type == 'qop') {
        all = res.mount.handle.list()
        prefix = res.path ? res.path + "/" : ""
        prefix_len = length(prefix)

        arrfor(all, function(p) {
            var rel = null
            if (starts_with(p, prefix)) {
                rel = text(p, prefix_len)
                if (length(rel) == 0) return

                if (!check_neg(rel) && check_pos(rel)) {
                    push(results, rel)
                }
            }
        })
   }

   return results
}

cellfs.mount = mount
cellfs.mount_package = mount_package
cellfs.unmount = unmount
cellfs.slurp = slurp
cellfs.slurpwrite = slurpwrite
cellfs.exists = exists
cellfs.is_directory = is_directory
cellfs.stat = stat
cellfs.searchpath = searchpath
cellfs.match = match
cellfs.enumerate = enumerate
cellfs.globfs = globfs
cellfs.rm = rm
cellfs.mkdir = mkdir
cellfs.writepath = set_writepath
cellfs.basedir = basedir
cellfs.prefdir = prefdir
cellfs.realdir = realdir

cellfs.mount('.')

return cellfs
