""" Two different progress meter routines.  One is for when you know
the total number of items to be processed, the other is for when you
don't.

Both work on the same principle: you invoke them with some kind of
introductory text (i.e., "Processing"; "Loading Database") that
explains what you're doing, and a work generator (i.e., function with
yield statements).

We will iterate through the generator and it should periodically yield
to us to allow us to update the meter.  When it is done, we will
return to you.

See progress_meter() and indefinite_progress_meter()'s docstrings for more
details on what the generator function should yield etc. """

import window

debug_file = None

def __debug (str):
    if debug_file: debug_file.write (str)
    pass

def __progress_meter (win, barwidth, prompt, workroutine,
                      barline, donechar, remchar):

    """
    workhorse used by both definite and indefinite progress meters
    
    win: win to draw on
    barwidth: total width of progress bar
    prompt: prompt text (We may wrap it)
    workroutine: iterator to do work
    barline: true to put progess bar on its own line
    donechar: char to use to represent work that has been done
    remchar: char to use to represent work remaining
    """

    # Load width of screen.  Leave some room at the edges.
    scrwidth = win.width - 7
    scrheight = win.height - 4
    maxrowwidth = scrwidth - 4
    maxcols = scrheight - 5

    # Adjust width to ensure it is reasonable
    if barwidth <= 0: barwidth = 22
    if barwidth >= maxrowwidth: barwidth = maxrowwidth

    # If we will put the dots on the same line as the prompt, adjust
    # maxrowwidth accordingly for the prompt
    if not barline: maxrowwidth -= barwidth

    # Adjust prompt, wrapping lines if needed (hacky).
    promptwidth = len (prompt)
    if promptwidth >= maxrowwidth:
        promptlines = []
        promptcols = 0
        while prompt:
            promptlines.append (prompt[:maxrowwidth])
            prompt = prompt[maxrowwidth:]
            promptcols += 1
            if promptcols >= maxcols: break
            pass
        promptwidth = maxrowwidth
        pass
    else:
        promptlines = [ prompt ]
        promptcols = 1
        pass

    # Determine number of rows and columns we will need
    if barline:
        if promptwidth > barwidth: cols = len (prompt)
        else: cols = barwidth
        rows = promptcols + 1 # prompt + progress bar
    else:
        cols = promptwidth + barwidth
        rows = promptcols
        pass

    cols += 2   # include space for border/prompt

    __debug ("rows=%d cols=%d promptcols=%d promptwidth=%d barwidth=%d scrwidth=%d scrheight=%d barline=%d\n" %
             (rows, cols, promptcols, promptwidth,
              barwidth, scrwidth, scrheight, barline))

    # Compute position, centered on screen
    y = win.height/2 - rows/2
    x = win.width/2 - cols/2
    __debug ("rows=%d cols=%d y=%d x=%d\n" % (rows, cols, y, x))
    subwin = win.newwin (rows+2, cols+2, y, x)
    curcol = 0 # for horizontal scrolling of entries that are too long

    # Draw window
    subwin.border ()

    # Draw prompt
    py = 1
    px = 2
    for line in promptlines:
        subwin.addstr (py,2,line)
        __debug ("y=%d,x=2,line=%s\n" % (py, line))
        py += 1
        px = 2 + len (line)
        pass
    if not barline: py -= 1
    else: px = 2

    # Loop through work, display progress
    def _update_progress (cur, max):
        filled = (barwidth * cur) // max
        if cur != max and filled == barwidth: filled -= 1 # while work remains
        subwin.addstr (py,px,donechar*filled)
        remaining = barwidth - filled
        subwin.addstr (py,px+filled,remchar*remaining)
        subwin.refresh ()
        pass

    _update_progress (0, 1)
    for cur, max in workroutine: _update_progress (cur, max)
    pass

def progress_meter (win, barwidth, prompt, workroutine):

    """ win should be the window to draw on.  We will always draw it centered.

    barwidth should be the number of characters to use to display the
    progress bar.  If it is too long it will be truncated.  Use -1 to
    get a default value.

    The work routine should yield with a tuple of two parts (cur,
    total).  cur should be the number of items processed and total
    should be the total number to process.  """

    return __progress_meter (win, barwidth, prompt, workroutine,
                             True, '=', '-')

def indefinite_progress_meter (win, prompt, workroutine):

    """ It doesn't matter value the work routine yields, we simply
    move a little dot around to assure the user you are working on
    their request.

    This is implemented with definite_progress_meter and a wrapper around
    workroutine. :) """

    barwidth = 3

    def wrapper():
        direction = 1
        counter = 0
        while 1:
            res = workroutine.next () # will raise except when done
            __debug ("res=%s counter=%d" % (res, counter))
            counter += direction
            yield counter, barwidth
            if counter >= barwidth: direction = -1
            if counter == 0: direction += 1
            pass
        pass

    __progress_meter (win, barwidth, prompt, wrapper (), False, '.', ' ')
    
    pass

def __definite_work (max):
    import random, time
    cnt = 0
    while cnt < max:
        sec = random.choice ([.1, .3, .5])
        time.sleep (sec)
        cnt += 1
        yield (cnt, max)
        pass
    return

def test (scr):
    progress_meter (scr, 30, "Working REALLY hard",
                    __definite_work(30))
    indefinite_progress_meter (scr, "Working INDEFINITELY hard",
                               __definite_work(30))
    pass
        
if __name__ == "__main__":
    debug_file = open ('x.debug.out', 'w')
    window.start (test)
    pass
    
