defmodule MNDP.Render do
  @moduledoc """
  Helpers for rendering the MNDP struct
  """

  @type t() :: %__MODULE__{
          printed_rows: [String.t()],
          first_run: boolean()
        }

  # TODO: keep track of terminal width and redraw header and divider when changed
  defstruct printed_rows: [], first_run: true

  @mac_width String.length("1A:2B:3C:4D:5E:6F")
  @ip_width String.length("111.222.333.444")
  @version_width String.length("10.10.10")
  @identity_width String.length("nerves-abcd")

  # TODO: show more or less columns depending on terminal width instead of fixed list
  @headers [
    identity: @identity_width,
    mac: @mac_width,
    ip_v4: @ip_width,
    ifname: nil,
    version: @version_width,
    uptime: nil,
    seen: nil
  ]
  @cols length(@headers)

  @doc delegate_to: {MNDP, :print_discovered, 1}
  def print_discovered(module \\ %__MODULE__{})
  def print_discovered(nil), do: print_discovered()

  def print_discovered(%__MODULE__{} = module) do
    terminal_width = terminal_width() - 3 * (@cols + 1)

    static_width =
      Enum.reduce(@headers, 0, fn
        {_, nil}, acc -> acc
        {_, width}, acc -> acc + width
      end)

    flexible_cols_count = Enum.count(@headers, &is_nil(elem(&1, 1)))
    flexible_col_width = round((terminal_width - static_width) / flexible_cols_count)

    divider = String.duplicate("-", terminal_width) |> List.duplicate(@cols)
    rows = MNDP.list_discovered() |> to_rows()

    format =
      Enum.map_join(@headers, "|", fn
        {_, nil} -> " ~#{flexible_col_width}s "
        {_, width} -> " ~#{width}s "
      end)

    format = "|#{format}|~n"

    if module.first_run do
      :io.fwrite(format, Keyword.keys(@headers))
      :io.fwrite(format, divider)
    else
      for _row <- module.printed_rows do
        move_cursor_up(1)
        clear_line()
      end
    end

    for mndp <- rows do
      :io.fwrite(format, mndp)
    end

    %{module | first_run: false, printed_rows: rows}
  end

  def to_rows(mndps) do
    Enum.map(mndps, fn mndp ->
      [
        mndp.identity,
        print_mac(mndp.mac),
        print_ip(mndp.ip_v4),
        mndp.interface,
        mndp.version,
        "#{mndp.uptime}s",
        "#{DateTime.diff(DateTime.utc_now(), mndp.last_seen)}s ago"
      ]
    end)
  end

  defp move_cursor_up(n) do
    IO.write(IO.ANSI.cursor_up(n))
  end

  defp clear_line() do
    IO.write(IO.ANSI.clear_line())
  end

  defp terminal_width do
    case :io.columns() do
      {:ok, width} -> width
      _ -> 80
    end
  end

  defp print_mac(mac) do
    mac |> Enum.map_join(":", &Integer.to_string(&1, 16))
  end

  defp print_ip(ip) do
    case :inet.ntoa(ip) do
      {:error, :einval} -> "unknown"
      address -> address |> to_string()
    end
  end
end
