import io
import sys
import os
import logging
import logging.handlers
#import os
import subprocess
from threading import Thread
from queue import Queue, Empty
import asyncio
from py_module.whiptail import Whiptail

class Base():

    def init_logger(self, name, level):
        self.logger = logging.getLogger(name);
        self.logger.setLevel(getattr(logging, level));
        handler = logging.handlers.RotatingFileHandler(f"{sys.path[0]}/support_logs/installer.log", maxBytes=10485760, backupCount=10 , mode='a')
        handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s [%(filename)s.%(funcName)s, line %(lineno)d]:  %(message)s"));
        self.logger.addHandler(handler);

    def __init__(self, **kwargs):
        self.exit_target = sys.exit
        self.logger_name = self.__class__.__name__;
        self.logger_level = "DEBUG"
        self.msg = None
        self.semaphore = None;
        self.DETALIZATION = None;
        for name, value in kwargs.items():
            self.__setattr__(name,value);
        self.init_logger(self.logger_name, self.logger_level);

        self.logger.debug(f"Вызван класс {self.__class__.__name__} из {sys._getframe(1).f_globals['__name__']}.{sys._getframe(1).f_code.co_name}()");
        self.loop = asyncio.get_event_loop()
        self.form = Whiptail(title="Лаунчер Р7-Офис", backtitle="Менеджер продукта", height=24, width=80)
#        self.form = Whiptail(title="Лаунчер Р7-Офис", backtitle="Менеджер продукта")
        if self.msg:
            asyncio.get_event_loop().run_until_complete(self.msgbox_async(self.msg));
        self.response = None;

    def __enter__(self, **kwargs):
        self.preload();
        self.show();
        self.postload();
        return self;

    def show(self):
        if self.semaphore:
            while True:
                self.preshow()
                self.logger.debug("Вызываю меню");
#                print(self.semaphore[1])
#                print([item["lang"] for item in self.semaphore[1]])
#                self.form.height=self.form.calc_height(self.semaphore[0])[0]
                self.response = self.menubox(msg = self.semaphore[0], items = [item["lang"] for item in self.semaphore[1]]);
                if not self.response[1]:
                    self.logger.info(f"Пользователь выбрал пункт меню: '{self.response[0]}'");
                    self.logger.debug(f'Произвожу попытку поиска в семафоре выбранного класса с действием "{self.response[0]}" и вызываю его');
                    with self.semaphore[1][[i for i,x in enumerate(self.semaphore[1]) if self.response[0] in x["lang"]][0]]["exec"](**self.semaphore[1][[i for i,x in enumerate(self.semaphore[1]) if self.response[0] in x["lang"]][0]]) as run:
                        pass
                else:
                    self.logger.info(f"Пользователь нажал кнопку 'Отмена'");
                    break
                self.postshow()

    def preload(self):
        pass

    def postload(self):
        pass

    def preshow(self):
        pass

    def postshow(self):
        pass

    def __exit__(self, exc_type , exc_value , traceback):
        if exc_type:
            self.logger.critical(f'Произошла ошибка при работе класса {self.__class__.__name__}\n{exc_type}: {exc_value}\n\nТрассировка:\n{traceback.__repr__()}\n');
        else:
            self.logger.debug(f"Выход из класса {self.__class__.__name__}");

    def calc_height_msg(self, msg, dialog_border: int = 7):
        def iterate(var):
            msg_height = 0
            if isinstance(var, str):
                msg_height = msg_height + len(var.split('\n'))
            else:
                try:
                    for subvar in var:
                        msg_height = msg_height + iterate(subvar)
                except TypeError:
                    pass
            return msg_height

        dialog_height = iterate(msg) + dialog_border
        terminal_height = os.get_terminal_size()[1]
        if terminal_height <= dialog_height:
            return terminal_height
        else:
            return dialog_height


    def menubox(self, msg: str = '', items = (), prefix: str = " - "):
#        self.form.height=24
        self.form.height = self.calc_height_msg((msg,items), dialog_border = 12)
#        self.form.height=self.form.calc_height(msg)[0]
        return self.form.menu(msg = msg, items = items, prefix = prefix);

#    def async_task(self, **kwarg):
        #Создание объекта цикла событий
#        self.loop = asyncio.get_running_loop();
#        task = loop.create_task(sleep());
#        loop.run_until_complete(task)
#        asyncio.get_event_loop().run_until_complete(self.msgbox_async(self.msg));

    def msgbox(self, msg):
#        self.form.height=24
        self.form.height=self.calc_height_msg(msg)
#        self.form.height=self.form.calc_height(msg)[0]
        return self.form.msgbox(msg);

    def textbox(self, msg):
#        self.form.height=24
        self.form.height=self.calc_height_msg(msg)
#        self.form.height=self.form.calc_height(msg)[0]
        return self.form.textbox(msg);

    async def msgbox_async(self, msg):
        return self.form.msgbox(msg);

    def inputbox(self, msg: str, default: str = '', password: bool = False):
#        self.form.height=self.form.calc_height(msg)[0]
#        self.form.height = 12
        self.form.height = self.calc_height_msg(msg)
        return self.form.inputbox(msg, default, password);

    def subprocess(self, stdin, fd_stdout = io.StringIO(''), fd_stderr = io.StringIO('')):
        stdout=[]
        stderr=[]
        queue_stdout = Queue()
        queue_stderr = Queue()
        def _enqueue_output(stream, queue):
            for line in iter(stream.readline, b''):
                queue.put(line.decode('utf-8').strip())
            stream.close()
        self.logger.info(f'Запускаю команду: {stdin}')
        process = subprocess.Popen(stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
        self.logger.info(f'Создаю потоки для обработки вывода stdout/stderr.')
        thread_stdout = Thread(
            target=_enqueue_output,
            args=(process.stdout, queue_stdout)
        )
        thread_stderr = Thread(
            target=_enqueue_output,
            args=(process.stderr, queue_stderr)
        )
        thread_stdout.daemon = True
        thread_stderr.daemon = True
        self.logger.info(f'Произвожу запуск потоков для обработки вывода stdout/stderr.')
        thread_stdout.start()
        thread_stderr.start()
        self.logger.info(f'Начинаю стримить потоки.')
        while True:
            try:
                line_stdout = queue_stdout.get_nowait()
                stdout.append(line_stdout)
                print(line_stdout, end='\n', file=fd_stdout)
            except Empty:
                pass

            try:
                line_stderr = queue_stderr.get_nowait()
                stderr.append(line_stderr)
                print(line_stderr, end='\n', file=fd_stderr)
            except Empty:
                pass

            if process.poll() is not None:
                self.logger.info(f'Выполнение команды завершилось ({stdin}). Произвожу получение остатков вывода stdout/stderr и закрываю потоки.')
                while not queue_stdout.empty():
                    line_stdout = queue_stdout.get_nowait()
                    stdout.append(line_stdout)
                    print(line_stdout, end='\n', file=fd_stdout)

                while not queue_stderr.empty():
                    line_stderr = queue_stderr.get_nowait()
                    stderr.append(line_stderr)
                    print(line_stderr, end='\n', file=fd_stderr)
                break
        return (process.returncode, stdout, stderr)
