# frozen_string_literal: true

require_relative 'cursor/version'

module TTY
  # Terminal cursor movement ANSI codes
  module Cursor
    module_function

    ESC = "\e".freeze
    CSI = "\e[".freeze
    DEC_RST  = 'l'.freeze
    DEC_SET  = 'h'.freeze
    DEC_TCEM = '?25'.freeze

    # Make cursor visible
    # @api public
    def show
      CSI + DEC_TCEM + DEC_SET
    end

    # Hide cursor
    # @api public
    def hide
      CSI + DEC_TCEM + DEC_RST
    end

    # Switch off cursor for the block
    # @api public
    def invisible(stream = $stdout)
      stream.print(hide)
      yield
    ensure
      stream.print(show)
    end

    # Save current position
    # @api public
    def save
      Gem.win_platform? ? CSI + 's' : ESC + '7'
    end

    # Restore cursor position
    # @api public
    def restore
      Gem.win_platform? ? CSI + 'u' : ESC + '8'
    end

    # Query cursor current position
    # @api public
    def current
      CSI + '6n'
    end

    # Set the cursor absolute position
    # @param [Integer] row
    # @param [Integer] column
    # @api public
    def move_to(row = nil, column = nil)
      return CSI + 'H' if row.nil? && column.nil?
      CSI + "#{column + 1};#{row + 1}H"
    end

    # Move cursor relative to its current position
    #
    # @param [Integer] x
    # @param [Integer] y
    #
    # @api public
    def move(x, y)
      (x < 0 ? backward(-x) : (x > 0 ? forward(x) : '')) +
      (y < 0 ? down(-y) : (y > 0 ? up(y) : ''))
    end

    # Move cursor up by n
    # @param [Integer] n
    # @api public
    def up(n = nil)
      CSI + "#{(n || 1)}A"
    end
    alias cursor_up up

    # Move the cursor down by n
    # @param [Integer] n
    # @api public
    def down(n = nil)
      CSI + "#{(n || 1)}B"
    end
    alias cursor_down down

    # Move the cursor backward by n
    # @param [Integer] n
    # @api public
    def backward(n = nil)
      CSI + "#{n || 1}D"
    end
    alias cursor_backward backward

    # Move the cursor forward by n
    # @param [Integer] n
    # @api public
    def forward(n = nil)
      CSI + "#{n || 1}C"
    end
    alias cursor_forward forward

    # Cursor moves to nth position horizontally in the current line
    # @param [Integer] n
    #   the nth aboslute position in line
    # @api public
    def column(n = nil)
      CSI + "#{n || 1}G"
    end

    # Cursor moves to the nth position vertically in the current column
    # @param [Integer] n
    #   the nth absolute position in column
    # @api public
    def row(n = nil)
      CSI + "#{n || 1}d"
    end

    # Move cursor down to beginning of next line
    # @api public
    def next_line
      CSI + 'E' + column(1)
    end

    # Move cursor up to beginning of previous line
    # @api public
    def prev_line
      CSI + 'A' + column(1)
    end

    # Erase n characters from the current cursor position
    # @api public
    def clear_char(n = nil)
      CSI + "#{n}X"
    end

    # Erase the entire current line and return to beginning of the line
    # @api public
    def clear_line
      CSI + '2K' + column(1)
    end

    # Erase from the beginning of the line up to and including
    # the current cursor position.
    # @api public
    def clear_line_before
      CSI + '1K'
    end

    # Erase from the current position (inclusive) to
    # the end of the line
    # @api public
    def clear_line_after
      CSI + '0K'
    end

    # Clear a number of lines
    #
    # @param [Integer] n
    #   the number of lines to clear
    # @param [Symbol] :direction
    #   the direction to clear, default :up
    #
    # @api public
    def clear_lines(n, direction = :up)
      n.times.reduce([]) do |acc, i|
        dir = direction == :up ? up : down
        acc << clear_line + ((i == n - 1) ? '' : dir)
      end.join
    end
    alias clear_rows clear_lines

    # Clear screen down from current position
    # @api public
    def clear_screen_down
      CSI + 'J'
    end

    # Clear screen up from current position
    # @api public
    def clear_screen_up
      CSI + '1J'
    end

    # Clear the screen with the background colour and moves the cursor to home
    # @api public
    def clear_screen
      CSI + '2J'
    end

    # Scroll display up one line
    # @api public
    def scroll_up
      ESC + 'M'
    end

    # Scroll display down one line
    # @api public
    def scroll_down
      ESC + 'D'
    end
  end # Cursor
end # TTY
