'''
Created on Dec 22, 2018

@author: johnrabsonjr
'''

import os
import subprocess
import time

from my.globals.exceptions import ExternalChrootThisError
from my.globals.logging import Logger
from my.miscellany import system_or_die, __g_start_time, generate_temporary_filename

g_proxy = None


def call_binary_and_show_progress(binary_info, title_str, foot_str, status_lst, trim_output=False, pauses_len=5,
                                  logfile=None):
    '''
    binary_info:    e.g. ['ls','-l','/tmp']
    title_str:      e.g. 'This is the title'
    status_lst:     e.g. ['First line', 'Second line, '..and so on']
    '''
    import urwid  # @UnresolvedImport
    max_height = 28
    wrapQ = 200
    output_widget = urwid.Text('')
    status_lst = [''] + status_lst + ['']  # + ''.join( [r + ' ' for r in binary_info ]
    content = [urwid.Text(r) for r in status_lst]
#    content.append( urwid.Text( "%s" % ( ''.join( [r + ' ' for r in binary_info ] ) ) ) )
    content.append(output_widget)

    header = urwid.AttrWrap(urwid.Text(title_str, align='center'), 'header')
    footer = urwid.AttrWrap(urwid.Text(foot_str, align='center'), 'footer')
    how_many_lines = max_height - len(status_lst)

    for r in range(len(status_lst)):
        s = status_lst[r]
        while len(s) > wrapQ:
            s = s[wrapQ - 5:]
            how_many_lines -= 1  # allow for word wrap :)  ...clumsy & inaccurate, but it'll do

    listbox = urwid.ListBox(content)
    view = urwid.AttrWrap(listbox, 'body')
    frame_widget = urwid.Frame(view, header=header, footer=footer)

    def make_an_alarm(): return loop.set_alarm_in(pauses_len, call_me)

    lower_limit = 25 if trim_output else 10

    def exit_on_enter(key):
        if key == 'enter':
            raise urwid.ExitMainLoop()

    loop = urwid.MainLoop(frame_widget, unhandled_input=exit_on_enter)

    def received_output(data):
        old_lst = output_widget.text.split('\n')
        try:
            nd = data.decode('utf-8')
            new_data = nd.strip(' .\r\n').strip()
            if logfile is not None:
                with open(logfile, 'a+') as f:
                    try:
                        f.write(nd)
                    except UnicodeEncodeError:
                        try:
                            f.write(data)
                        except Exception as ex:
                            Logger.debug(str(ex))
                            f.write(str(data))
        except UnicodeDecodeError:
            new_data = '(unicode error)'
        new_lst_A = [r.strip('. \r\n') for r in new_data.replace('\r', '\n').split('\n') if len(r.strip()) >= lower_limit]
        new_lst = []
        for r in [s.strip() for s in new_lst_A]:
            if r not in new_lst and r != '':
                new_lst.append(r)
#        if not trim_output:
        full_lst = old_lst + new_lst
        last_N_lines_of_lst = full_lst[-how_many_lines:]
        output_text = ''.join([r + '\n' for r in last_N_lines_of_lst])
        if output_text not in (None, ''):
            while output_text[-1] in ('\r\n'):
                output_text = output_text[:-1]
                if len(output_text) == 0:
                    break
        if status_lst is not None:
            current_time = time.time()
            time_taken = current_time - __g_start_time
            s = 'Time taken so far: %02d:%02d:%02d' % (time_taken // 3600, (time_taken // 60) % 60, time_taken % 60)
            frame_widget.footer = urwid.AttrWrap(urwid.Text(s, align='center'), 'footer')
            output_widget.set_text(output_text)

    def call_me(x, y):
        x = x  # stop Eclipse warning
        y = y  # stop Eclipse warning
        if proc.poll() is not None:
            raise urwid.ExitMainLoop
        make_an_alarm()

    write_fd = loop.watch_pipe(received_output)
    proc = subprocess.Popen(
        #        ['find', '/Volumes/HMR3T/Video', '-type', 'f'],
        binary_info,
        stdout=write_fd,  # None if 'pacman' in str( binary_info ) else write_fd,
        stderr=write_fd,  # if 'pacman' in str( binary_info ) else None,
        close_fds=True)

    make_an_alarm()
    loop.run()
#     Logger.info( 'call_binary_and_show_progress() is returning' )
    (results, errors) = proc.communicate()
    del results
    del errors
    return proc.returncode
#     try:
#         proc.kill()
#     except Exception as ex:
#         pass
#     return proc.poll()


def chroot_this_and_display_output(mountpoint, cmd, on_fail=None, attempts=3, title_str=None, status_lst=None, pauses_len=1, user='root'):
    #    Logger.info( 'chroot_this (%s) ==> %s' % ( mountpoint, cmd ) )
    proxy_info = '' if g_proxy in (None, '') else 'export http_proxy=http://%s;' % (g_proxy)
    my_executable_script = generate_temporary_filename('/tmp')
    if not os.path.isdir(mountpoint):
        raise FileNotFoundError('%s not found --- are you sure the chroot is operational?' % (mountpoint))
    f = open(mountpoint + my_executable_script, 'wb')
    outstr = '#!/bin/bash\n%s\n%s\nexit $?\n' % (proxy_info, cmd)
    if os.path.exists(mountpoint + '/bin/sh') and not os.path.exists(mountpoint + '/bin/bash'):
        raise ExternalChrootThisError('That is wrong. There is /bin/sh but not /bin/bash. Ugh.')
    f.write(outstr.encode('utf-8'))
    f.close()
    system_or_die('chmod 777 %s' % (mountpoint + my_executable_script))
    system_or_die('chmod +x %s' % (mountpoint + my_executable_script))
#    Logger.info( 'chroot_this() --- calling %s' % ( cmd ) )
    for att in range(attempts):
        att = att  # hide Eclipse warning
        if title_str is not None or status_lst is not None:
            res = call_binary_and_show_progress(binary_info=['chroot', mountpoint, my_executable_script],
                                                title_str=title_str, foot_str=cmd, status_lst=status_lst,
                                                trim_output=True if 'wget' in cmd else False,
                                                pauses_len=pauses_len)
        else:
            if user == 'root':
                res = os.system('chroot %s %s' % (mountpoint, my_executable_script))
            elif mountpoint in ('/', None):  # build within alarpy, NOT within final distro
                res = os.system('chroot --userspec=%s %s %s' % (user, '/', my_executable_script))
            else:
                res = os.system('chroot --userspec=%s %s %s' % (user, mountpoint, my_executable_script))
        if res == 0:
            break
        else:
            os.system('sync;sync;sync')
            time.sleep(pauses_len)
    if res != 0 and on_fail is not None:
        raise ExternalChrootThisError('%schroot in %s of "%s" failed after several attempts; %s' %
                                      (proxy_info + ' ' if proxy_info not in (None, '') else '', mountpoint, cmd, on_fail))
#    if os.path.exists( mountpoint + my_executable_script ):
    if res == 0:
        os.unlink(mountpoint + my_executable_script)
    os.system('sync;sync;sync')
    Logger.info('chroot("%s") is returning w/ res=%d %s' % (cmd, res, '' if res == 0 else '...script=' + mountpoint + my_executable_script))
    return res


if __name__ == '__main__':
    pass
