import time
import math
import pdb


class Progress:

    def __init__(self, total, name='Progress', ncol=3, max_length=20, indent=0, line_width=100, speed_update_freq=100):
        self.total = total
        self.name = name
        self.ncol = ncol
        self.max_length = max_length
        self.indent = indent
        self.line_width = line_width
        self._speed_update_freq = speed_update_freq

        self._step = 0
        self._prev_line = '\033[F'
        self._clear_line = ' ' * self.line_width

        self._pbar_size = self.ncol * self.max_length
        self._complete_pbar = '#' * self._pbar_size
        self._incomplete_pbar = ' ' * self._pbar_size

        self.lines = ['']
        self.fraction = '{} / {}'.format(0, self.total)

        self.resume()

    def update(self, n=1):
        self._step += n
        if self._step % self._speed_update_freq == 0:
            self._time0 = time.time()
            self._step0 = self._step

    def resume(self):
        self._skip_lines = 1
        print('\n', end='')
        self._time0 = time.time()
        self._step0 = self._step

    def pause(self):
        self._clear()
        self._skip_lines = 1

    def set_description(self, params=[]):

        ############
        # Position #
        ############
        self._clear()

        ###########
        # Percent #
        ###########
        percent, fraction = self._format_percent(self._step, self.total)
        self.fraction = fraction

        #########
        # Speed #
        #########
        speed = self._format_speed(self._step)

        ##########
        # Params #
        ##########
        num_params = len(params)
        nrow = math.ceil(num_params / self.ncol)
        params_split = self._chunk(params, self.ncol)
        params_string, lines = self._format(params_split)
        self.lines = lines

        description = '{} | {}{}'.format(percent, speed, params_string)
        print(description)
        self._skip_lines = nrow + 1

    def append_description(self, descr):
        self.lines.append(descr)

    def _clear(self):
        position = self._prev_line * self._skip_lines
        empty = '\n'.join([self._clear_line for _ in range(self._skip_lines)])
        print(position, end='')
        print(empty)
        print(position, end='')

    def _format_percent(self, n, total):
        if total:
            percent = n / float(total)

            complete_entries = int(percent * self._pbar_size)
            incomplete_entries = self._pbar_size - complete_entries

            pbar = self._complete_pbar[:complete_entries] + self._incomplete_pbar[:incomplete_entries]
            fraction = '{} / {}'.format(n, total)
            string = '{} [{}] {:3d}%'.format(fraction, pbar, int(percent * 100))
        else:
            fraction = '{}'.format(n)
            string = '{} iterations'.format(n)
        return string, fraction

    def _format_speed(self, n):
        num_steps = n - self._step0
        t = time.time() - self._time0
        speed = num_steps / t
        string = '{:.1f} Hz'.format(speed)
        if num_steps > 0:
            self._speed = string
        return string

    def _chunk(self, l, n):
        return [l[i:i + n] for i in range(0, len(l), n)]

    def _format(self, chunks):
        lines = [self._format_chunk(chunk) for chunk in chunks]
        lines.insert(0, '')
        padding = '\n' + ' ' * self.indent
        string = padding.join(lines)
        return string, lines

    def _format_chunk(self, chunk):
        line = ' | '.join([self._format_param(param) for param in chunk])
        return line

    def _format_param(self, param):
        k, v = param
        return '{} : {}'.format(k, v)[:self.max_length]

    def stamp(self):
        if self.lines != ['']:
            params = ' | '.join(self.lines)
            string = '[ {} ] {}{} | {}'.format(self.name, self.fraction, params, self._speed)
            self._clear()
            print(string, end='\n')
            self._skip_lines = 1
        else:
            self._clear()
            self._skip_lines = 0

    def close(self):
        self.pause()


class Silent:

    def __init__(self, *args, **kwargs):
        pass

    def __getattr__(self, attr):
        return lambda *args: None


if __name__ == '__main__':
    silent = Silent()
    silent.update()
    silent.stamp()

    num_steps = 1000
    progress = Progress(num_steps)
    for i in range(num_steps):
        progress.update()
        params = [
            ['A', '{:06d}'.format(i)],
            ['B', '{:06d}'.format(i)],
            ['C', '{:06d}'.format(i)],
            ['D', '{:06d}'.format(i)],
            ['E', '{:06d}'.format(i)],
            ['F', '{:06d}'.format(i)],
            ['G', '{:06d}'.format(i)],
            ['H', '{:06d}'.format(i)],
        ]
        progress.set_description(params)
        time.sleep(0.01)
    progress.close()
