defmodule Util.Ext.File do

  @moduledoc false

  @doc """
  Recursive file list from the current directory.
  """
  def ls_r do
    ls_r "."
  end

  @doc """
  Recursive file list from a specific path.  The path can be absolute or
  relative.
  """
  def ls_r(paths), do: ls_r(paths, %{})

  def ls_r(paths, opts) when is_list(paths) do
    lcl_opts = default_opts(opts)
    paths
    |> Enum.map(fn(path) -> ls_process(path, lcl_opts) end)
    |> List.flatten
    |> Enum.take(lcl_opts.file_maxnum)
  end

  def ls_r(path, opts), do: ls_r([path], opts)

  # --------------------------------------------------------------------

  defp default_opts(opts) do
    defaults = %{
      file_types:    []    ,
      file_skips:    []    ,
      file_maxnum:   1000  ,
      file_depth:    10    ,
    }
    Map.merge(defaults, opts)
  end

  defp ls_process(path, opts) do
    if File.dir?(path), do: ls_dir(path, opts), else: ls_file(path, opts)
  end

  # --------------------------------------------------------------------

  defp ls_dir(_path, %{file_depth: 0} = _opts), do: []
  defp ls_dir(base_path, opts) do
    new_opts = %{opts | file_depth: opts.file_depth - 1}
    result = dirglob_match?(base_path, opts.file_skips)
    if result do
      []
    else
      base_path
      |> File.ls!
      |> Enum.map(fn(path) -> base_path <> "/" <> path end)
      |> Enum.map(fn(path) -> ls_r(path, new_opts) end)
    end
  end

  defp ls_file(path, opts) do
    if fileglob_match?(path, opts.file_types), do: [path], else: []
  end

  # --------------------------------------------------------------------

  defp fileglob_match?(path, globs) do
    newglobs = Enum.map(globs, fn(glob) -> "\\.#{glob}$" end)
    case newglobs do
      []       -> true
      newglobs -> Enum.any?(newglobs, &glob_match?(path, &1))
    end
  end

  defp dirglob_match?(path, globs) do
    dirpath = String.split(path, "/") |> List.last
    case globs do
      []    -> false
      globs -> Enum.any?(globs, &glob_match?(dirpath, &1))
    end
  end

  # eg> glob_match?("/my/path", "txt")
  defp glob_match?(path, glob) do
    String.match?(path, reg_compile(glob))
  end

  # compiles a string (like "txt") into a regex.
  # the regex matches EOL (eg> ~r/txt!/)
  defp reg_compile(glob) do
    {:ok, reg} = Regex.compile(glob)
    reg
  end

  # --------------------------------------------------------------------

  @doc """
  Get size of directories
  """
  def du_s(path_list, opts) when is_list(path_list) do
    path_list
    |> ls_r(opts)
    |> Enum.map(fn(path) -> get_size(path) end)
    |> Enum.sum
  end

  defp get_size(path) do
    if File.exists?(path) do
      %{size: size} = File.stat!(path)
      size
    else
      0
    end
  end
end
