import datetime
import operator
import time
from functools import reduce
from typing import Tuple

from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, QToolBar, \
    QProgressBar, QApplication, QPlainTextEdit, QToolButton, QHBoxLayout

from bauh import __app_name__
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.handler import TaskManager
from bauh.view.qt import root, styles
from bauh.view.qt.colors import GREEN
from bauh.view.qt.components import new_spacer
from bauh.view.qt.qt_utils import centralize
from bauh.view.qt.thread import AnimateProgress
from bauh.view.util.translation import I18n


class Prepare(QThread, TaskManager):
    signal_register = pyqtSignal(str, str, object)
    signal_update = pyqtSignal(str, float, str)
    signal_finished = pyqtSignal(str)
    signal_started = pyqtSignal()
    signal_ask_password = pyqtSignal()
    signal_output = pyqtSignal(str, str)

    def __init__(self, context: ApplicationContext, manager: SoftwareManager, i18n: I18n):
        super(Prepare, self).__init__()
        self.manager = manager
        self.i18n = i18n
        self.context = context
        self.waiting_password = False
        self.password_response = None

    def ask_password(self) -> Tuple[str, bool]:
        self.waiting_password = True
        self.signal_ask_password.emit()

        while self.waiting_password:
            time.sleep(0.1)  # waiting for user input

        return self.password_response

    def set_password_reply(self, password: str, valid: bool):
        self.password_response = password, valid
        self.waiting_password = False

    def run(self):
        root_pwd = None
        if self.manager.requires_root('prepare', None):
            root_pwd, ok = self.ask_password()

            if not ok:
                QCoreApplication.exit(1)

        self.manager.prepare(self, root_pwd, None)
        self.signal_started.emit()

    def update_progress(self, task_id: str, progress: float, substatus: str):
        self.signal_update.emit(task_id, progress, substatus)

    def update_output(self, task_id: str, output: str):
        self.signal_output.emit(task_id, output)

    def register_task(self, id_: str, label: str, icon_path: str):
        self.signal_register.emit(id_, label, icon_path)

    def finish_task(self, task_id: str):
        self.signal_finished.emit(task_id)


class CheckFinished(QThread):
    signal_finished = pyqtSignal()

    def __init__(self):
        super(CheckFinished, self).__init__()
        self.total = None
        self.finished = None

    def run(self):
        time.sleep(3)
        while True:
            if self.total == self.finished:
                break

            time.sleep(0.01)

        self.signal_finished.emit()

    def update(self, total: int, finished: int):
        if total is not None:
            self.total = total

        if finished is not None:
            self.finished = finished


class EnableSkip(QThread):

    signal_timeout = pyqtSignal()

    def run(self):
        ti = datetime.datetime.now()

        while True:
            if datetime.datetime.now() >= ti + datetime.timedelta(minutes=1.5):
                self.signal_timeout.emit()
                break

            time.sleep(0.1)


class PreparePanel(QWidget, TaskManager):

    signal_status = pyqtSignal(object, object)
    signal_password_response = pyqtSignal(str, bool)

    def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize,  i18n: I18n, manage_window: QWidget):
        super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
        self.i18n = i18n
        self.context = context
        self.manage_window = manage_window
        self.setWindowTitle('{} ({})'.format(__app_name__, self.i18n['prepare_panel.title.start'].lower()))
        self.setMinimumWidth(screen_size.width() * 0.5)
        self.setMinimumHeight(screen_size.height() * 0.35)
        self.setMaximumHeight(screen_size.height() * 0.95)
        self.setLayout(QVBoxLayout())
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.manager = manager
        self.tasks = {}
        self.output = {}
        self.ntasks = 0
        self.ftasks = 0
        self.self_close = False

        self.prepare_thread = Prepare(self.context, manager, self.i18n)
        self.prepare_thread.signal_register.connect(self.register_task)
        self.prepare_thread.signal_update.connect(self.update_progress)
        self.prepare_thread.signal_finished.connect(self.finish_task)
        self.prepare_thread.signal_started.connect(self.start)
        self.prepare_thread.signal_ask_password.connect(self.ask_root_password)
        self.prepare_thread.signal_output.connect(self.update_output)
        self.signal_password_response.connect(self.prepare_thread.set_password_reply)

        self.check_thread = CheckFinished()
        self.signal_status.connect(self.check_thread.update)
        self.check_thread.signal_finished.connect(self.finish)

        self.skip_thread = EnableSkip()
        self.skip_thread.signal_timeout.connect(self._enable_skip_button)

        self.progress_thread = AnimateProgress()
        self.progress_thread.signal_change.connect(self._change_progress)

        self.label_top = QLabel()
        self.label_top.setText("{}...".format(self.i18n['prepare_panel.title.start'].capitalize()))
        self.label_top.setAlignment(Qt.AlignHCenter)
        self.label_top.setStyleSheet("QLabel { font-size: 14px; font-weight: bold; }")
        self.layout().addWidget(self.label_top)
        self.layout().addWidget(QLabel())

        self.table = QTableWidget()
        self.table.setStyleSheet("QTableWidget { background-color: transparent; }")
        self.table.setFocusPolicy(Qt.NoFocus)
        self.table.setShowGrid(False)
        self.table.verticalHeader().setVisible(False)
        self.table.horizontalHeader().setVisible(False)
        self.table.horizontalHeader().setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
        self.table.setColumnCount(4)
        self.table.setHorizontalHeaderLabels(['' for _ in range(4)])
        self.layout().addWidget(self.table)

        self.textarea_output = QPlainTextEdit(self)
        self.textarea_output.resize(self.table.size())
        self.textarea_output.setStyleSheet("background: black; color: white;")
        self.layout().addWidget(self.textarea_output)
        self.textarea_output.setVisible(False)
        self.textarea_output.setReadOnly(True)
        self.textarea_output.setMaximumHeight(100)
        self.current_output_task = None

        self.bottom_widget = QWidget()
        self.bottom_widget.setLayout(QHBoxLayout())
        self.bottom_widget.layout().addStretch()
        bt_hide_output = QPushButton(self.i18n['prepare.bt_hide_details'])
        bt_hide_output.setStyleSheet('QPushButton { text-decoration: underline; border: 0px; background: none } ')
        bt_hide_output.clicked.connect(self.hide_output)
        bt_hide_output.setCursor(QCursor(Qt.PointingHandCursor))
        self.bottom_widget.layout().addWidget(bt_hide_output)
        self.bottom_widget.layout().addStretch()
        self.layout().addWidget(self.bottom_widget)
        self.bottom_widget.setVisible(False)

        self.bt_bar = QToolBar()
        self.bt_close = QPushButton(self.i18n['close'].capitalize())
        self.bt_close.setCursor(QCursor(Qt.PointingHandCursor))
        self.bt_close.clicked.connect(self.close)
        self.bt_close.setVisible(False)
        self.ref_bt_close = self.bt_bar.addWidget(self.bt_close)

        self.bt_bar.addWidget(new_spacer())
        self.progress_bar = QProgressBar()
        self.progress_bar.setStyleSheet(styles.PROGRESS_BAR)
        self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 4)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.ref_progress_bar = self.bt_bar.addWidget(self.progress_bar)
        self.bt_bar.addWidget(new_spacer())

        self.bt_skip = QPushButton(self.i18n['prepare_panel.bt_skip.label'].capitalize())
        self.bt_skip.clicked.connect(self.finish)
        self.bt_skip.setEnabled(False)
        self.bt_bar.addWidget(self.bt_skip)

        self.layout().addWidget(self.bt_bar)

    def hide_output(self):
        self.current_output_task = None
        self.textarea_output.setVisible(False)
        self.textarea_output.clear()
        self.bottom_widget.setVisible(False)
        self._resize_columns()
        self.setFocus(Qt.NoFocusReason)

        if not self.bt_bar.isVisible():
            self.bt_bar.setVisible(True)

    def ask_root_password(self):
        root_pwd, ok = root.ask_root_password(self.context, self.i18n)
        self.signal_password_response.emit(root_pwd, ok)

    def _enable_skip_button(self):
        self.bt_skip.setEnabled(True)
        self.bt_skip.setCursor(QCursor(Qt.PointingHandCursor))

    def _change_progress(self, value: int):
        self.progress_bar.setValue(value)

    def get_table_width(self) -> int:
        return reduce(operator.add, [self.table.columnWidth(i) for i in range(self.table.columnCount())])

    def _resize_columns(self):
        header_horizontal = self.table.horizontalHeader()
        for i in range(self.table.columnCount()):
            header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)

        self.resize(self.get_table_width() * 1.05, self.sizeHint().height())

    def show(self):
        super(PreparePanel, self).show()
        self.prepare_thread.start()
        centralize(self)

    def start(self):
        self.ref_bt_close.setVisible(True)
        self.check_thread.start()
        self.skip_thread.start()

        self.ref_progress_bar.setVisible(True)
        self.progress_thread.start()

    def closeEvent(self, QCloseEvent):
        if not self.self_close:
            QCoreApplication.exit()

    def register_task(self, id_: str, label: str, icon_path: str):
        self.ntasks += 1
        self.table.setRowCount(self.ntasks)

        task_row = self.ntasks - 1

        icon_widget = QWidget()
        icon_widget.setLayout(QHBoxLayout())
        icon_widget.layout().setContentsMargins(10, 0, 10, 0)
        bt_icon = QToolButton()
        bt_icon.setEnabled(False)
        bt_icon.setToolTip(self.i18n['prepare.bt_icon.no_output'])
        bt_icon.setFixedSize(QSize(24, 24))
        bt_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

        if icon_path:
            bt_icon.setIcon(QIcon(icon_path))

        def _show_output():
            lines = self.output[id_]

            if lines:
                self.current_output_task = id_
                self.textarea_output.clear()
                self.textarea_output.setVisible(True)

                for l in lines:
                    self.textarea_output.appendPlainText(l)

                self.bottom_widget.setVisible(True)

            self.setFocus(Qt.NoFocusReason)

            if self.bt_bar.isVisible():
                self.bt_bar.setVisible(False)

        bt_icon.clicked.connect(_show_output)
        icon_widget.layout().addWidget(bt_icon)

        self.table.setCellWidget(task_row, 0, icon_widget)

        lb_status = QLabel(label)
        lb_status.setMinimumWidth(50)
        lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_status.setStyleSheet("QLabel { font-weight: bold; }")
        self.table.setCellWidget(task_row, 1, lb_status)

        lb_sub = QLabel()
        lb_sub.setContentsMargins(10, 0, 10, 0)
        lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_sub.setMinimumWidth(50)
        self.table.setCellWidget(task_row, 2, lb_sub)

        lb_progress = QLabel('{0:.2f}'.format(0) + '%')
        lb_progress.setContentsMargins(10, 0, 10, 0)
        lb_progress.setStyleSheet("QLabel { font-weight: bold; }")
        lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

        self.table.setCellWidget(task_row, 3, lb_progress)

        self.tasks[id_] = {'bt_icon': bt_icon,
                           'lb_status': lb_status,
                           'lb_prog': lb_progress,
                           'progress': 0,
                           'lb_sub': lb_sub,
                           'finished': False}

        self.signal_status.emit(self.ntasks, self.ftasks)

    def update_progress(self, task_id: str, progress: float, substatus: str):
        task = self.tasks[task_id]

        if progress != task['progress']:
            task['progress'] = progress
            task['lb_prog'].setText('{0:.2f}'.format(progress) + '%')

        if substatus:
            task['lb_sub'].setText('( {} )'.format(substatus))
        else:
            task['lb_sub'].setText('')

        self._resize_columns()

    def update_output(self, task_id: str, output: str):
        full_output = self.output.get(task_id)

        if full_output is None:
            full_output = []
            self.output[task_id] = full_output
            task = self.tasks[task_id]
            task['bt_icon'].setEnabled(True)
            task['bt_icon'].setCursor(QCursor(Qt.PointingHandCursor))
            task['bt_icon'].setToolTip(self.i18n['prepare.bt_icon.output'])

        full_output.append(output)

        if self.current_output_task == task_id:
            self.textarea_output.appendPlainText(output)

    def finish_task(self, task_id: str):
        task = self.tasks[task_id]
        task['lb_sub'].setText('')

        for key in ('lb_prog', 'lb_status'):
            task[key].setStyleSheet('QLabel { color: %s; text-decoration: line-through; }' % GREEN)

        task['finished'] = True
        self._resize_columns()

        self.ftasks += 1
        self.signal_status.emit(self.ntasks, self.ftasks)

        if self.ntasks == self.ftasks:
            self.label_top.setText(self.i18n['ready'].capitalize())

    def finish(self):
        if self.isVisible():
            self.manage_window.refresh_packages()
            self.manage_window.show()
            self.self_close = True
            self.close()
