import sys
import threading
import time
from timeit import default_timer


def format_time(t):
    """Format seconds into a human readable form.

    >>> format_time(10.4)
    '10.4s'
    >>> format_time(1000.4)
    '16min 40.4s'
    """
    m, s = divmod(t, 60)
    h, m = divmod(m, 60)
    if h:
        return f"{h:2.0f}hr {m:2.0f}min {s:4.1f}s"
    elif m:
        return f"{m:2.0f}min {s:4.1f}s"
    else:
        return f"{s:4.1f}s"


class progressbar:
    """A simple progressbar for iterables.

    Displays a progress bar showing progress through an iterable.

    Parameters
    ----------
    iterable : iterable
        The object to iterate over.
    width : int, optional
        Width of the bar in characters.
    enabled : bool, optional
        Whether to log progress. Useful for turning off progress reports
        without changing your code. Default is True.
    file : file, optional
        Where to log progress. Default is ``sys.stdout``.

    Example
    -------
    >>> with progressbar(iterable) as itbl:  # doctest: +SKIP
    ...     for i in itbl:
    ...         do_stuff(i)
    [########################################] | 100% Completed | 5.2 s
    """
    def __init__(self, iterable, width=40, enabled=True, file=None):
        self._iterable = iterable
        self._ndone = 0
        self._ntotal = len(iterable) + 1  # wait for exit to finish
        self._width = width
        self._enabled = enabled
        self._file = sys.stdout if file is None else file

    def __enter__(self):
        if self._enabled:
            self._start_time = default_timer()
            # Start background thread
            self._running = True
            self._timer = threading.Thread(target=self._timer_func)
            self._timer.daemon = True
            self._timer.start()
        return self

    def __exit__(self, type, value, traceback):
        if self._enabled:
            self._running = False
            self._timer.join()
            if type is None:  # Finished if no exception
                self._ndone += 1
            self._update_bar()
            self._file.write('\n')
            self._file.flush()

    def __iter__(self):
        for i in self._iterable:
            self._ndone += 1
            yield i

    def _timer_func(self):
        while self._running:
            self._update_bar()
            time.sleep(0.1)

    def _update_bar(self):
        elapsed = default_timer() - self._start_time
        frac = (self._ndone / self._ntotal) if self._ntotal else 1
        bar = '#' * int(self._width * frac)
        percent = int(100 * frac)
        elapsed = format_time(elapsed)
        msg = '\r[{0:<{1}}] | {2}% Completed | {3}'.format(bar, self._width,
                                                           percent, elapsed)
        try:
            self._file.write(msg)
            self._file.flush()
        except ValueError:
            pass
