require 'gorgon/colors'

require 'ruby-progressbar'
require 'colorize'

MAX_LENGTH = 200
LOADING_MSG = "Loading environment and workers..."
RUNNING_MSG = "Running files:"
LEGEND_MSG = "Legend:\nF - failure files count\nH - number of hosts that have run files\nW - number of workers running files"

PROGRESS_BAR_REFRESH_RATE = 0.5
# - because the resolution of the elapsed time is one second, we use the nyquist frequency of 0.5 seconds
# http://en.wikipedia.org/wiki/Nyquist_frequency

class ProgressBarView
  def initialize job_state
    @job_state = job_state
    @job_state.add_observer(self)
    @timer = EventMachine::PeriodicTimer.new(PROGRESS_BAR_REFRESH_RATE) { update_elapsed_time }
  end

  def show
    print LOADING_MSG
  end

  def update payload={}
    output_gorgon_crash_message payload if gorgon_crashed? payload

    create_progress_bar_if_started_job_running

    return if @progress_bar.nil? || @finished

    failed_files_count = @job_state.failed_files_count

    @progress_bar.title=" F: #{failed_files_count} H: #{@job_state.total_running_hosts} W: #{@job_state.total_running_workers}"
    if failed_files_count > 0
      @progress_bar.format(format(bar: :red, title: :default))
    end

    @progress_bar.progress = @job_state.finished_files_count

    if @job_state.is_job_complete? || @job_state.is_job_cancelled?
      @finished = true
      print_summary
    end
  end

  def create_progress_bar_if_started_job_running
    if @progress_bar.nil? && @job_state.state == :running
      print "\r#{' ' * (LOADING_MSG.length)}\r"
      puts LEGEND_MSG
      @progress_bar = ProgressBar.create(:total => @job_state.total_files,
                                         :length => [terminal_size[0], MAX_LENGTH].min,
                                         :format => format(bar: :green, title: :white));
    end
  end

  def update_elapsed_time
    @timer.cancel if @finished
    @progress_bar.refresh if @progress_bar
  end

private
  def gorgon_crashed? payload
     payload[:type] == "crash" && payload[:action] != "finish"
  end

  def output_gorgon_crash_message payload
    $stderr.puts "\nA #{'crash'.red} occured at '#{payload[:hostname].colorize Colors::HOST}':"
    $stderr.puts payload[:stdout].yellow unless payload[:stdout].to_s.strip.length == 0
    $stderr.puts payload[:stderr].yellow unless payload[:stderr].to_s.strip.length == 0
    if @progress_bar.nil?
      print LOADING_MSG         # if still loading, print msg so user won't think the whole job crashed
    end
  end

  def format colors
    # TODO: decide what bar to use
    #    bar = "%b>%i".colorize(colors[:bar])
    bar = "%w>%i".colorize(colors[:bar])
    title = "%t".colorize(colors[:title])

    "#{title} | [#{bar}] %c/%C %a"
  end

  def terminal_size
    `stty size`.split.map { |x| x.to_i }.reverse
  end

  def print_summary
    print_failed_tests
    print_running_files
    #TODO: print other stats: time, total file, total failures, etc
  end

  def print_failed_tests
    @job_state.each_failed_test do |test|
      puts "\n" + ('*' * 80).magenta #light_red
      puts("File '#{test[:filename].colorize(Colors::FILENAME)}' failed/crashed at " \
           + "'#{test[:hostname].colorize(Colors::HOST)}'\n")
      msg = build_fail_message test[:failures]
      puts "#{msg}\n"
    end
  end

  def build_fail_message failures
    result = []
    failures.each do |failure|
      if failure.is_a?(Hash)
        result << build_fail_message_from_hash(failure)
      else
        result << build_fail_message_from_string(failure)
      end
    end

    result.join("\n")
  end

  def print_running_files
    title = "Unfinished files".yellow
    puts "\n#{title} - The following files were still running:" if @job_state.total_running_workers > 0

    @job_state.each_running_file do |hostname, filename|
      filename_str = filename.dup.colorize(Colors::FILENAME)
      hostname_str = hostname.dup.colorize(Colors::HOST)
      puts "\t#{filename_str} at '#{hostname_str}'"
    end
  end

  def build_fail_message_from_string failure
    result = failure.gsub(/^Error:/, "Error:".yellow)
    result.gsub!(/^Failure:/, "Failure:".red)
    result
  end

  def build_fail_message_from_hash failure
    result = "#{'Test name'.light_yellow}: #{failure[:test_name]}"
    result << "\n#{failure[:class].red}" if failure[:class]
    result << "\n#{'Message:'.light_yellow} \n\t#{failure[:message]}" if failure[:message]
    if failure[:location]
      result << "\n#{'In:'.light_yellow} \n\t"
      result << failure[:location].join("\n\t")
    end
    result
  end
end