require 'rbbt/util/log'
module Log
  class ProgressBar

    attr_accessor :depth, :num_reports, :desc, :io, :severity

    # Creates a new instance. Max is the total number of iterations of the
    # loop. The depth represents how many other loops are above this one,
    # this information is used to find the place to print the progress
    # report.
    def initialize(max, options = {})
      options = Misc.add_defaults options, :depth => 0, :num_reports => 100, :desc => "Progress", :io => STDERR, :severity => Log.severity
      depth, num_reports, desc, io, severity = Misc.process_options options, :depth, :num_reports, :desc, :io, :severity

      @max = max
      @max = 1 if @max and @max < 1
      @current = 0
      @time = Time.now
      @last_report = -1
      @num_reports = num_reports
      @severity = severity
      @depth = depth
      @desc = desc
    end

    # Used to register a new completed loop iteration.
    def tick(step = nil)

      if step.nil?
        @current += 1
      else
        @current = step
      end

      if @max
        if percent - @last_report > 1.to_f/@num_reports.to_f 
          report
          @last_report=percent
        end
      else
        @last_report = Time.now if @last_report == -1
        if Time.now - @last_report >= 1.0 
          throughput
        end
      end

      nil
    end


    def progress
      @current.to_f/ @max
    end

    def percent
      (self.progress * 100).to_i
    end

    def eta
      (Time.now - @time)/progress * (1-progress)
    end

    def used
      (Time.now - @time).to_i
    end


    def up_lines(depth)
      "\033[#{depth + 2}F\033[2K"
    end

    def down_lines(depth)
      "\n\033[#{depth + 3}E"
    end

    def report_msg
      progress = self.progress
      percent = self.percent

      indicator = Log.color(:magenta, @desc) << " "
      10.times{|i|
        if i < progress * 10 then
          indicator << Log.color(:yellow, ".")
        else
          indicator << " "
        end
      }
      done = progress == 1

      used = self.used
      used = [used/3600, used/60 % 60, used % 60].map{|t|  "%02i" % t }.join(':')

      if progress == 1
        indicator << Log.color(:green, " done ")


        indicator << Log.color(:blue, " #{used}")
      else
        indicator << " done #{Log.color(:blue, percent.to_s << "%")}"

        eta = self.eta
        eta = [eta/3600, eta/60 % 60, eta % 60].map{|t| "%02i" % t }.join(':')

        indicator << " (Time left #{eta} seconds) (Started #{used} seconds ago)"
      end

    end

    def throughput_msg
      indicator = Log.color(:magenta, @desc) 
      time = Time.now - @last_report
      thr = (@current / time).to_i
      indicator << " #{ Log.color :blue, thr } per second"
      indicator
    end

    # Prints de progress report. It backs up as many lines as the meters
    # depth. Prints the progress as a line of dots, a percentage, time
    # spent, and time left. And then goes moves the cursor back to its
    # original line. Everything is printed to stderr.
    def report(io = STDERR)
      io.print(up_lines(@depth) << report_msg << down_lines(@depth)) if severity >= Log.severity
    end

    def throughput(io = STDERR)
      io.print(up_lines(@depth) << throughput_msg << down_lines(@depth)) if severity >= Log.severity
      @last_report = Time.now
      @current = 0
    end
    BAR_MUTEX = Mutex.new
    BARS = []
    def self.new_bar(max, options = {})
      options = Misc.add_defaults options, :depth => BARS.length
      BAR_MUTEX.synchronize do
        BARS << (bar = ProgressBar.new(max, options))
        bar
      end
    end

    def self.remove_bar(bar)
      BAR_MUTEX.synchronize do
        index = BARS.index bar
        if index
          (index+1..BARS.length-1).each do |pos|
            bar = BARS[pos]
            bar.depth = pos - 1
            BARS[pos-1] = bar
          end 
          BARS.pop
        end
      end
    end

    def self.with_bar(max, options = {})
      bar = new_bar(max, options)
      begin
        yield bar
      ensure
        remove_bar(bar)
      end
    end
  end
end