
--- Proxy module for filesystem and platform abstractions.
-- All code using "fs" code should require "luarocks.fs",
-- and not the various platform-specific implementations.
-- However, see the documentation of the implementation
-- for the API reference.

local pairs = pairs

local fs = {}
-- To avoid a loop when loading the other fs modules.
package.loaded["luarocks.fs"] = fs

local cfg = require("luarocks.core.cfg")

local pack = table.pack or function(...) return { n = select("#", ...), ... } end
local unpack = table.unpack or unpack

math.randomseed(os.time())

local fs_is_verbose = false

do
   local old_popen, old_execute

   -- patch io.popen and os.execute to display commands in verbose mode
   function fs.verbose()
      fs_is_verbose = true

      if old_popen or old_execute then return end
      old_popen = io.popen
      -- luacheck: push globals io os
      io.popen = function(one, two)
         if two == nil then
            print("\nio.popen: ", one)
         else
            print("\nio.popen: ", one, "Mode:", two)
         end
         return old_popen(one, two)
      end

      old_execute = os.execute
      os.execute = function(cmd)
         -- redact api keys if present
         print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap, key) return cap.."<redacted>/" end)) )
         local code = pack(old_execute(cmd))
         print("Results: "..tostring(code.n))
         for i = 1,code.n do
            print("  "..tostring(i).." ("..type(code[i]).."): "..tostring(code[i]))
         end
         return unpack(code, 1, code.n)
      end
      -- luacheck: pop
   end
end

do
   local function load_fns(fs_table, inits)
      for name, fn in pairs(fs_table) do
         if name ~= "init" and not fs[name] then
            fs[name] = function(...)
               if fs_is_verbose then
                  local args = pack(...)
                  for i=1, args.n do
                     local arg = args[i]
                     local pok, v = pcall(string.format, "%q", arg)
                     args[i] = pok and v or tostring(arg)
                  end
                  print("fs." .. name .. "(" .. table.concat(args, ", ") .. ")")
               end
               return fn(...)
            end
         end
      end
      if fs_table.init then
         table.insert(inits, fs_table.init)
      end
   end

   local function load_platform_fns(patt, inits)
      local each_platform = cfg.each_platform

      -- FIXME A quick hack for the experimental Windows build
      if os.getenv("LUAROCKS_CROSS_COMPILING") then
         each_platform = function()
            local i = 0
            local plats = { "linux", "unix" }
            return function()
               i = i + 1
               return plats[i]
            end
         end
      end

      for platform in each_platform("most-specific-first") do
         local ok, fs_plat = pcall(require, patt:format(platform))
         if ok and fs_plat then
            load_fns(fs_plat, inits)
         end
      end
   end

   function fs.init()
      local inits = {}

      if fs.current_dir then
         -- unload luarocks fs so it can be reloaded using all modules
         -- providing extra functionality in the current package paths
         for k, _ in pairs(fs) do
            if k ~= "init" and k ~= "verbose" then
               fs[k] = nil
            end
         end
         for m, _ in pairs(package.loaded) do
            if m:match("luarocks%.fs%.") then
               package.loaded[m] = nil
            end
         end
      end

      -- Load platform-specific functions
      load_platform_fns("luarocks.fs.%s", inits)

      -- Load platform-independent pure-Lua functionality
      load_fns(require("luarocks.fs.lua"), inits)

      -- Load platform-specific fallbacks for missing Lua modules
      load_platform_fns("luarocks.fs.%s.tools", inits)

      -- Load platform-independent external tool functionality
      load_fns(require("luarocks.fs.tools"), inits)

      -- Run platform-specific initializations after everything is loaded
      for _, init in ipairs(inits) do
         init()
      end
   end
end

return fs
