#!/usr/bin/python -t
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import sys
import time

class text_progress_meter:
    def __init__(self, fo=sys.stderr):
        self.fo = fo
        self.update_period = 0.3 # seconds

    def start(self, filename, url, basename, length):
        self.filename = filename
        self.url = url
        self.basename = basename
        self.length = length
        if not length == None:
            self.flength = self.format_number(length) + 'B'
        self.start_time = time.time()
        self.last_update = 0
        self._do_start()

    def _do_start(self):
        pass

    def end(self):
        self.now = time.time()
        self._do_end()

    def _do_end(self):
        total_time = self.format_time(self.now - self.start_time)
        total_size = self.format_number(self.read)
        if self.length is None:
            out = '\r%-60.60s    %5sB %s ' % \
                  (self.basename, total_size, total_time)
        else:
            bar = '='*25
            out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s     ' % \
                  (self.basename, 100, bar, total_size, total_time)
        self.fo.write(out)
        self.fo.write('\n')
        self.fo.flush()
        
    def update(self, read):
        # for a real gui, you probably want to override and put a call
        # to your mainloop iteration function here
        self.read = read # put this here so it's caught for self.end
        now = time.time()
        if (now >= self.last_update + self.update_period) or \
               not self.last_update:
            self.now = now
            self._do_update(read)
            self.last_update = now

    def _do_update(self, read):
        # elapsed time since last update
        etime = self.now - self.start_time
        fetime = self.format_time(etime)
        fread = self.format_number(read)

        #self.length = None
        if self.length is None:
            out = '\r%-60.60s    %5sB %s ' % \
                  (self.basename, fread, fetime)
        else:
            rtime = self.format_time(self.project(etime, read))
            try: frac = float(read)/self.length
            except ZeroDivisionError: frac = 1.0
            if frac > 1.0: frac = 1.0
            bar = '='*int(25 * frac)
            out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s ETA ' % \
                  (self.basename, frac*100, bar, fread, rtime)
        self.fo.write(out)
        self.fo.flush()

    def project(self, etime, read):
        # get projected time for total download
        if read == 0:
            # if we just started this file, all bets are off
            self.last_etime = etime
            self.last_read  = 0
            self.ave_rate = None
            return None

        time_diff = etime - self.last_etime
        read_diff = read  - self.last_read
        self.last_etime = etime
        self.last_read  = read
        try: rate = time_diff / read_diff  ## this is actually an inverse-rate
        except ZeroDivisionError: return 0 ## should only happen at end of file

        self._get_new_ave_rate(rate)
        remaining_time = self.ave_rate * (self.length - read)
        if remaining_time < 0: remaining_time = 0
        return self._round_remaining_time(remaining_time)
        
    def _get_new_ave_rate(self, rate, epsilon=0.98):
        if self.ave_rate == None:
            self.ave_rate = rate
        else:
            # calculate a "rolling average" - this balances long-term behavior
            # with short-term fluctuations
            # epsilon = 0.0  -->  only consider most recent block
            # epsilon = 1.0  -->  only consider first block
            self.ave_rate = (self.ave_rate * epsilon) + (rate * (1-epsilon))

    def _round_remaining_time(self, remaining_time):
        # round to further stabilize it
        i = 1
        while remaining_time > 30:
            i = i * 2
            remaining_time = remaining_time / 2
        remaining_time = int(remaining_time)
        return float(remaining_time * i)
            
    def format_time(self, seconds):
        if seconds is None or seconds < 0:
            return '--:--'
        else:
            seconds = int(seconds)
            minutes = seconds / 60
            seconds = seconds % 60
            return '%02i:%02i' % (minutes, seconds)
        
    def format_number(self, number, SI=0, space=' '):
        """Turn numbers into human-readable metric-like numbers"""
        symbols = ['',  # (none)
                   'k', # kilo
                   'M', # mega
                   'G', # giga
                   'T', # tera
                   'P', # peta
                   'E', # exa
                   'Z', # zetta
                   'Y'] # yotta
    
        if SI: step = 1000.0
        else: step = 1024.0

        thresh = 999
        depth = 0
    
        # we want numbers between 
        while number > thresh:
            depth  = depth + 1
            number = number / step

        # just in case someone needs more than 1000 yottabytes!
        diff = depth - len(symbols) + 1
        if diff > 0:
            depth = depth - diff
            number = number * thresh**depth

        if type(number) == type(1) or type(number) == type(1L):
            format = '%i%s%s'
        elif number < 9.95:
            # must use 9.95 for proper sizing.  For example, 9.99 will be
            # rounded to 10.0 with the .1f format string (which is too long)
            format = '%.1f%s%s'
        else:
            format = '%.0f%s%s'
    
        return(format % (number, space, symbols[depth]))
