--- Module implementing the LuaRocks "show" command.
-- Shows information about an installed rock.
local show = {}

local queries = require("luarocks.queries")
local search = require("luarocks.search")
local dir = require("luarocks.core.dir")
local fs = require("luarocks.fs")
local cfg = require("luarocks.core.cfg")
local util = require("luarocks.util")
local path = require("luarocks.path")
local fetch = require("luarocks.fetch")
local manif = require("luarocks.manif")
local repos = require("luarocks.repos")

function show.add_to_parser(parser)
   local cmd = parser:command("show", [[
Show information about an installed rock.

Without any flags, show all module information.
With flags, return only the desired information.]], util.see_also())
      :summary("Show information about an installed rock.")

   cmd:argument("rock", "Name of an installed rock.")
      :action(util.namespaced_name_action)
   cmd:argument("version", "Rock version.")
      :args("?")

   cmd:flag("--home", "Show home page of project.")
   cmd:flag("--modules", "Show all modules provided by the package as used by require().")
   cmd:flag("--deps", "Show packages the package depends on.")
   cmd:flag("--build-deps", "Show build-only dependencies for the package.")
   cmd:flag("--test-deps", "Show dependencies for testing the package.")
   cmd:flag("--rockspec", "Show the full path of the rockspec file.")
   cmd:flag("--mversion", "Show the package version.")
   cmd:flag("--rock-tree", "Show local tree where rock is installed.")
   cmd:flag("--rock-namespace", "Show rock namespace.")
   cmd:flag("--rock-dir", "Show data directory of the installed rock.")
   cmd:flag("--rock-license", "Show rock license.")
   cmd:flag("--issues", "Show URL for project's issue tracker.")
   cmd:flag("--labels", "List the labels of the rock.")
   cmd:flag("--porcelain", "Produce machine-friendly output.")
end

local friendly_template = [[
          :
?namespace:${namespace}/${package} ${version} - ${summary}
!namespace:${package} ${version} - ${summary}
          :
*detailed :${detailed}
?detailed :
?license  :License:      \t${license}
?homepage :Homepage:     \t${homepage}
?issues   :Issues:       \t${issues}
?labels   :Labels:       \t${labels}
?location :Installed in: \t${location}
?commands :
?commands :Commands:
*commands :\t${name} (${file})
?modules  :
?modules  :Modules:
*modules  :\t${name} (${file})
?bdeps    :
?bdeps    :Has build dependency on:
*bdeps    :\t${name} (${label})
?tdeps    :
?tdeps    :Tests depend on:
*tdeps    :\t${name} (${label})
?deps     :
?deps     :Depends on:
*deps     :\t${name} (${label})
?ideps    :
?ideps    :Indirectly pulling:
*ideps    :\t${name} (${label})
          :
]]

local porcelain_template = [[
?namespace:namespace\t${namespace}
?package  :package\t${package}
?version  :version\t${version}
?summary  :summary\t${summary}
*detailed :detailed\t${detailed}
?license  :license\t${license}
?homepage :homepage\t${homepage}
?issues   :issues\t${issues}
?labels   :labels\t${labels}
?location :location\t${location}
*commands :command\t${name}\t${file}
*modules  :module\t${name}\t${file}
*bdeps    :build_dependency\t${name}\t${label}
*tdeps    :test_dependency\t${name}\t${label}
*deps     :dependency\t${name}\t${label}
*ideps    :indirect_dependency\t${name}\t${label}
]]

local function keys_as_string(t, sep)
   local keys = util.keys(t)
   table.sort(keys)
   return table.concat(keys, sep or " ")
end

local function word_wrap(line)
   local width = tonumber(os.getenv("COLUMNS")) or 80
   if width > 80 then width = 80 end
   if #line > width then
      local brk = width
      while brk > 0 and line:sub(brk, brk) ~= " " do
         brk = brk - 1
      end
      if brk > 0 then
         return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1))
      end
   end
   return line
end

local function format_text(text)
   text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2")
   local paragraphs = util.split_string(text, "\n\n")
   for n, line in ipairs(paragraphs) do
      paragraphs[n] = word_wrap(line)
   end
   return (table.concat(paragraphs, "\n\n"):gsub("%s$", ""))
end

local function installed_rock_label(dep, tree)
   local installed, version
   local rocks_provided = util.get_rocks_provided()
   if rocks_provided[dep.name] then
      installed, version = true, rocks_provided[dep.name]
   else
      installed, version = search.pick_installed_rock(dep, tree)
   end
   return installed and "using "..version or "missing"
end

local function render(template, data)
   local out = {}
   for cmd, var, line in template:gmatch("(.)([a-z]*)%s*:([^\n]*)\n") do
      line = line:gsub("\\t", "\t")
      local d = data[var]
      if cmd == " " then
         table.insert(out, line)
      elseif cmd == "?" or cmd == "*" or cmd == "!" then
         if (cmd == "!" and d == nil)
             or (cmd ~= "!" and (type(d) == "string"
                                 or (type(d) == "table" and next(d)))) then
            local n = cmd == "*" and #d or 1
            for i = 1, n do
               local tbl = cmd == "*" and d[i] or data
               if type(tbl) == "string" then
                  tbl = tbl:gsub("%%", "%%%%")
               end
               table.insert(out, (line:gsub("${([a-z]+)}", tbl)))
            end
         end
      end
   end
   return table.concat(out, "\n")
end

local function adjust_path(name, version, basedir, pathname, suffix)
   pathname = dir.path(basedir, pathname)
   local vpathname = path.versioned_name(pathname, basedir, name, version)
   return (fs.exists(vpathname)
          and vpathname
          or pathname) .. (suffix or "")
end

local function modules_to_list(name, version, repo)
   local ret = {}
   local rock_manifest = manif.load_rock_manifest(name, version, repo)

   local lua_dir = path.deploy_lua_dir(repo)
   local lib_dir = path.deploy_lib_dir(repo)
   repos.recurse_rock_manifest_entry(rock_manifest.lua, function(pathname)
      table.insert(ret, {
         name = path.path_to_module(pathname),
         file = adjust_path(name, version, lua_dir, pathname),
      })
   end)
   repos.recurse_rock_manifest_entry(rock_manifest.lib, function(pathname)
      table.insert(ret, {
         name = path.path_to_module(pathname),
         file = adjust_path(name, version, lib_dir, pathname),
      })
   end)
   table.sort(ret, function(a, b)
      if a.name == b.name then
         return a.file < b.file
      end
      return a.name < b.name
   end)
   return ret
end

local function commands_to_list(name, version, repo)
   local ret = {}
   local rock_manifest = manif.load_rock_manifest(name, version, repo)

   local bin_dir = path.deploy_bin_dir(repo)
   repos.recurse_rock_manifest_entry(rock_manifest.bin, function(pathname)
      pathname = adjust_path(name, version, bin_dir, pathname)
      table.insert(ret, {
         name = pathname,
         file = adjust_path(name, version, bin_dir, pathname, cfg.wrapper_suffix),
      })
   end)
   table.sort(ret, function(a, b)
      if a.name == b.name then
         return a.file < b.file
      end
      return a.name < b.name
   end)
   return ret
end

local function deps_to_list(dependencies, tree)
   local ret = {}
   for _, dep in ipairs(dependencies or {}) do
      table.insert(ret, { name = tostring(dep), label = installed_rock_label(dep, tree) })
   end
   return ret
end

local function indirect_deps(mdeps, rdeps, tree)
   local ret = {}
   local direct_deps = {}
   for _, dep in ipairs(rdeps) do
      direct_deps[dep] = true
   end
   for dep_name in util.sortedpairs(mdeps or {}) do
      if not direct_deps[dep_name] then
         table.insert(ret, { name = tostring(dep_name), label = installed_rock_label(queries.new(dep_name), tree) })
      end
   end
   return ret
end

local function show_rock(template, namespace, name, version, rockspec, repo, minfo, tree)
   local desc = rockspec.description or {}
   local data = {
      namespace = namespace,
      package = rockspec.package,
      version = rockspec.version,
      summary = desc.summary or "",
      detailed = desc.detailed and util.split_string(format_text(desc.detailed), "\n"),
      license = desc.license,
      homepage = desc.homepage,
      issues = desc.issues_url,
      labels = desc.labels and table.concat(desc.labels, ", "),
      location = path.rocks_tree_to_string(repo),
      commands = commands_to_list(name, version, repo),
      modules = modules_to_list(name, version, repo),
      bdeps = deps_to_list(rockspec.build_dependencies, tree),
      tdeps = deps_to_list(rockspec.test_dependencies, tree),
      deps = deps_to_list(rockspec.dependencies, tree),
      ideps = indirect_deps(minfo.dependencies, rockspec.dependencies, tree),
   }
   util.printout(render(template, data))
end

--- Driver function for "show" command.
-- @return boolean: True if succeeded, nil on errors.
function show.command(args)
   local query = queries.new(args.rock, args.namespace, args.version, true)

   local name, version, repo, repo_url = search.pick_installed_rock(query, args.tree)
   if not name then
      return nil, version
   end
   local tree = path.rocks_tree_to_string(repo)
   local directory = path.install_dir(name, version, repo)
   local namespace = path.read_namespace(name, version, tree)
   local rockspec_file = path.rockspec_file(name, version, repo)
   local rockspec, err = fetch.load_local_rockspec(rockspec_file)
   if not rockspec then return nil,err end

   local descript = rockspec.description or {}
   local manifest, err = manif.load_manifest(repo_url)
   if not manifest then return nil,err end
   local minfo = manifest.repository[name][version][1]

   if args.rock_tree then util.printout(tree)
   elseif args.rock_namespace then util.printout(namespace)
   elseif args.rock_dir then util.printout(directory)
   elseif args.home then util.printout(descript.homepage)
   elseif args.rock_license then util.printout(descript.license)
   elseif args.issues then util.printout(descript.issues_url)
   elseif args.labels then util.printout(descript.labels and table.concat(descript.labels, "\n"))
   elseif args.modules then util.printout(keys_as_string(minfo.modules, "\n"))
   elseif args.deps then
      for _, dep in ipairs(rockspec.dependencies) do
         util.printout(tostring(dep))
      end
   elseif args.build_deps then
      for _, dep in ipairs(rockspec.build_dependencies) do
         util.printout(tostring(dep))
      end
   elseif args.test_deps then
      for _, dep in ipairs(rockspec.test_dependencies) do
         util.printout(tostring(dep))
      end
   elseif args.rockspec then util.printout(rockspec_file)
   elseif args.mversion then util.printout(version)
   elseif args.porcelain then
      show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, args.tree)
   else
      show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, args.tree)
   end
   return true
end

return show
