require 'htty'

# Encapsulates the display logic of _htty_'s command-line interface.
module HTTY::CLI::Display

  FORMATS = {bold:                    '1',
             underlined:              '4',
             blinking:                '5',
             inverse:                 '7',
             foreground_black:        '30',
             foreground_dark_red:     '31',
             foreground_dark_green:   '32',
             foreground_dark_yellow:  '33',
             foreground_dark_blue:    '34',
             foreground_dark_magenta: '35',
             foreground_dark_cyan:    '36',
             foreground_light_gray:   '37',
             foreground_dark_default: '39',
             foreground_dark_gray:    '1;30',
             foreground_red:          '1;31',
             foreground_green:        '1;32',
             foreground_yellow:       '1;33',
             foreground_blue:         '1;34',
             foreground_magenta:      '1;35',
             foreground_cyan:         '1;36',
             foreground_white:        '1;37',
             foreground_default:      '1;39',
             background_black:        '40',
             background_dark_red:     '41',
             background_dark_green:   '42',
             background_dark_yellow:  '43',
             background_dark_blue:    '44',
             background_dark_magenta: '45',
             background_dark_cyan:    '46',
             background_light_gray:   '47',
             background_default:      '49'}

  def format(string, *attributes)
    segments = attributes.collect do |a|
      "\e[#{FORMATS[a]}m"
    end
    segments << string
    segments << "\e[0m"
    segments.join ''
  end

  def rescuing_from(*exception_classes)
    yield
  rescue Interrupt
    nil
  rescue *exception_classes => e
    $stderr.puts notice(sentence_case(e.message))
    nil
  end

  def indent(string, column=2)
    "#{' ' * column}#{string}"
  end

  def logotype
    format ' htty ', :bold, :background_dark_red, :foreground_yellow
  end

  def notice(string)
    "*** #{string}"
  end

  def normal(string)
    return string
  end

  def pluralize(word, number)
    case number
      when 0
        "no #{word}s"
      when 1
        "1 #{word}"
      else
        "#{number} #{word}s"
    end
  end

  def formatted_prompt_for(request)
    format_request_uri(request.uri) + normal('> ')
  end

  def say(message, style=:normal)
    puts send(style, notice(message))
  end

  def say_goodbye
    say 'Happy Trails To You!'
  end

  def say_header(message, style=:normal)
    puts send(style, notice('')) + highlight(message)
  end

  def say_hello
    puts normal(notice('Welcome to ')) + logotype + normal(', the ') +
         strong('HTTP TTY') + normal('. Heck To The Yeah!')
  end

  def break
    puts ''
  end

  def show_headers(headers, options={})
    show_asterisk_next_to   = (options[:show_asterisk_next_to] || '').downcase
    show_mercantile_next_to = (options[:show_mercantile_next_to] || '').downcase

    asterisk_symbol, mercantile_symbol = nil, nil
    margin = headers.inject 0 do |result, header|
      header_name = header.first.downcase
      asterisk_symbol   ||= (header_name == show_asterisk_next_to)   ? '*' : nil
      mercantile_symbol ||= (header_name == show_mercantile_next_to) ? '@' : nil
      asterisk   = (header_name == show_asterisk_next_to)   ? asterisk_symbol   : ''
      mercantile = (header_name == show_mercantile_next_to) ? mercantile_symbol : ''
      [(header_name.length + [asterisk.length, mercantile.length].max),
       result].max
    end
    headers.each do |name, value|
      asterisk   = (name.downcase == show_asterisk_next_to)   ? asterisk_symbol   : nil
      mercantile = (name.downcase == show_mercantile_next_to) ? mercantile_symbol : nil
      justified_name = name.rjust margin - [asterisk.to_s.length, mercantile.to_s.length].max
      puts "#{justified_name}:#{strong(asterisk || mercantile)} " + value
    end
  end

  def show_request(request)
    method = format(" #{request.request_method.to_s.upcase} ", :inverse)
    print "#{method} "
    cookies_asterisk = request.cookies.empty? ? '' : strong('*')
    body_length = request.body.to_s.length
    body_size   = body_length.zero? ? 'empty' : "#{body_length}-character"
    puts [format_request_uri(request.uri),
          pluralize('header', request.headers.length) + cookies_asterisk,
          "#{body_size} body"].join(' -- ')
  end

  def show_response(response)
    code, description = response.status
    case code.to_i
      when 100...200, 300...400
        print format(" #{code} ", :background_dark_blue, :foreground_white)
      when 200...300
        print format(" #{code} ", :background_dark_green, :foreground_black)
      when 500...600
        print format(" #{code} ", :inverse, :blinking,
                                  :background_black, :foreground_yellow)
      else
        print format(" #{code} ", :background_dark_red, :foreground_white)
    end
    print ' '
    cookies_asterisk = response.cookies.empty? ? '' : strong('*')
    body_length      = response.body.to_s.length
    body_size        = body_length.zero? ? 'empty' : "#{body_length}-character"
    response_time_ms = (response.time * 1000).round(2)
    puts([description,
          pluralize('header', response.headers.length) + cookies_asterisk,
          "#{body_size} body", "#{response_time_ms} ms"].join(' -- '))
  end

  def strong(string)
    format string, :bold
  end

  def word_wrap(text, column=nil)
    word_wrap_indented(text, (0..(column || 80)))
  end

  # Adapted from
  # http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#M002281
  def word_wrap_indented(text, columns=2..80)
    indent_by, wrap_at = columns.min, columns.max - columns.min
    text.split("\n").collect { |line|
      (wrap_at < line.length)                              ?
      line.gsub(/(.{1,#{wrap_at}})(\s+|$)/, "\\1\n").strip :
      line
    }.join("\n").split("\n").collect { |line|
      indent line, indent_by
    }.join "\n"
  end

private

  def format_request_uri(uri)
    if uri.userinfo
      userinfo_with_at = "#{uri.userinfo}@"
      before_userinfo, after_userinfo = uri.to_s.split(userinfo_with_at, 2)
      return strong(before_userinfo)  +
             normal(userinfo_with_at) +
             strong(after_userinfo)
    end
    return strong(uri)
  end

  def sentence_case(text)
    text.gsub(/^./) do |letter|
      letter.upcase
    end
  end

end
