# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.

#
class ProgressBar

  class << self

    def start(args, &block)
      new(args).start &block
    end

    def width
      @width ||= Buildr::Console.output_cols || 0
    end

  end

  def initialize(args = {})
    @title = args[:title] || ''
    @total = args[:total] || 0
    @mark = args[:mark] || '.'
    @format = args[:format] || default_format
    @output = args[:output] || $stderr unless args[:hidden] || !$stdout.isatty
    clear
  end

  def start
    @start = @last_time = Time.now
    @count = 0
    @finished = false
    render
    if block_given?
      result = yield(self) if block_given?
      finish
      result
    else
      self
    end
  end

  def inc(count)
    set @count + count
  end

  def <<(bytes)
    inc bytes.size
  end

  def set(count)
    @count = [count, 0].max
    @count = [count, @total].min unless @total == 0
    render if changed?
  end

  def title
    return @title if ProgressBar.width <= 10
    @title.size > ProgressBar.width / 5 ? (@title[0, ProgressBar.width / 5 - 2] + '..') : @title
  end

  def count
    human(@count)
  end

  def total
    human(@total)
  end

  def percentage
    '%3d%%' % (@total == 0 ? 100 : (@count * 100 / @total))
  end

  def time
    @finished ? elapsed : eta
  end

  def eta
    return 'ETA:  --:--:--' if @count == 0
    elapsed = Time.now - @start
    eta = elapsed * @total / @count - elapsed
    'ETA:  %s' % duration(eta.ceil)
  end

  def elapsed
    'Time: %s' % duration(Time.now - @start)
  end

  def rate
    '%s/s' % human(@count / (Time.now - @start))
  end

  def progress(width)
    width -= 2
    marks = @total == 0 ? width : (@count * width / @total)
    "|%-#{width}s|" % (@mark * marks)
  end

  def human(bytes)
    magnitude = (0..3).find { |i| bytes < (1024 << i * 10) } || 3
    return '%dB' % bytes if magnitude == 0
    return '%.1f%s' % [ bytes.to_f / (1 << magnitude * 10), [nil, 'KB', 'MB', 'GB'][magnitude] ]
  end

  def duration(seconds)
    '%02d:%02d:%02d' % [seconds / 3600, (seconds / 60) % 60, seconds % 60]
  end

  def finish
    unless @finished
      @finished = true
      render
    end
  end

protected

  def clear
    return if @output == nil || ProgressBar.width <= 0
    @output.print "\r", " " * (ProgressBar.width - 1), "\r"
    @output.flush
  end

  def render
    return unless @output
    format, *args = @format
    line = format % args.map { |arg| send(arg) }
    if ProgressBar.width >= line.size
      @output.print line.sub('|--|') { progress(ProgressBar.width - line.size + 3) }
    else
      @output.print line.sub('|--|', '')
    end
    @output.print @finished ? "\n" : "\r"
    @output.flush
    @previous = @count
    @last_time = Time.now
  end

  def changed?
    return false unless @output && Time.now - @last_time > 0.1
    return human(@count) != human(@previous) if @total == 0
    return true if (@count - @previous) >= @total / 100
    return Time.now - @last_time > 1
  end

  def default_format
    @total == 0 ? ['%s %8s %s', :title, :count, :elapsed] : ['%s: %s |--| %8s/%s %s', :title, :percentage, :count, :total, :time]
  end

end
