defmodule ExTerm.Console.Cursor do
  @moduledoc false
  alias ExTerm.Console
  alias ExTerm.Console.Cell
  alias ExTerm.Console.Update

  use MatchSpec

  @home IO.ANSI.home()
  @clear IO.ANSI.clear()
  @clear_line IO.ANSI.clear_line()

  @spec from_ansi(String.t(), Console.t(), Console.location(), to_flush :: Update.cell_changes()) ::
          :not_cursor | {:update, Console.location(), Update.t(), String.t()}

  def from_ansi(@home <> rest, console, {cursor_row, _}, changes) do
    Console.insert(console, changes)
    {rows, cols} = Console.layout(console)

    updates =
      Enum.flat_map((cursor_row + 1)..(cursor_row + rows), fn row ->
        case Console.columns(console, row) do
          0 ->
            # doesn't exist.  Fill it out to the layout length, pad with sentinel.
            [{{row, cols + 1}, Cell.sentinel()} | Enum.map(1..cols, &{{row, &1}, %Cell{}})]

          length ->
            Enum.map(1..length, &{{row, &1}, %Cell{}})
        end
      end)

    Console.insert(console, updates)
    new_cursor = {cursor_row + 1, 1}
    {:update, new_cursor, {new_cursor, :end}, rest}
  end

  def from_ansi(@clear <> rest, console, cursor, changes) do
    Console.insert(console, changes)
    # this clears only the "current console".  Do this by looking up the console layout and
    # last row and fill out everything after that.  Doesn't add in extra rows.
    {rows, _} = Console.layout(console)
    {last_row, _} = Console.last_cell(console)
    clear_start = {last_row - rows + 1, 1}
    to_clear = Console.select(console, select_clear_from(clear_start))
    Console.insert(console, to_clear)

    {:update, cursor, {clear_start, :end}, rest}
  end

  def from_ansi(@clear_line <> rest, console, cursor = {row, _}, changes) do
    Console.insert(console, changes)
    to_clear = Console.select(console, select_clear_row(row))
    Console.insert(console, to_clear)
    {:update, cursor, {{row, 1}, {row, :end}}, rest}
  end

  def from_ansi(_, _, _, _), do: :not_cursor

  defmatchspecp select_clear_from(from) do
    {location, cell} when location >= from and cell.char !== "\n" -> {location, %Cell{}}
  end

  defmatchspecp select_clear_row(row) do
    {{^row, column}, cell} when cell.char !== "\n" -> {{row, column}, %Cell{}}
  end
end
