from PyQt5.QtCore import QThread, pyqtSignal, QMutex

from lanzou.api.utils import is_folder_url, is_file_url
from lanzou.api import why_error 
from lanzou.debug import logger


def show_progress(file_name, total_size, now_size, symbol="█"):
    """显示进度条的回调函数"""
    percent = now_size / total_size
    # 进度条长总度
    file_len = len(file_name)
    if file_len >= 20:
        bar_len = 20
    elif file_len >= 10:
        bar_len = 30
    else:
        bar_len = 40
    if total_size >= 1048576:
        unit = "MB"
        piece = 1048576
    else:
        unit = "KB"
        piece = 1024
    bar_str = ("<font color='#00CC00'>" + symbol * round(bar_len * percent)
               + "</font><font color='#000080'>" + symbol * round(bar_len * (1 - percent)) + "</font>")
    msg = "\r{:>5.1f}%\t[{}] {:.1f}/{:.1f}{} | {} ".format(
        percent * 100,
        bar_str,
        now_size / piece,
        total_size / piece,
        unit,
        file_name,
    )
    if total_size == now_size:
        msg = msg + "| <font color='blue'>Done!</font>"
    return msg


class Downloader(QThread):
    '''单个文件下载线程'''
    download_finished = pyqtSignal(object)
    download_proc = pyqtSignal(str)
    download_failed = pyqtSignal()
    folder_file_failed = pyqtSignal(object, object)
    update = pyqtSignal()

    def __init__(self, disk, task, parent=None):
        super(Downloader, self).__init__(parent)
        self._disk = disk
        self._task = task
        self._stopped = True
        self._mutex = QMutex()

    def stop(self):
        self._mutex.lock()
        self._stopped = True
        self._mutex.unlock()
        self.terminate()

    def _show_progress(self, file_name, total_size, now_size):
        """显示进度条的回调函数"""
        dl_rate = int(1000 * now_size / total_size)
        if dl_rate != self._task.rate:
            self._task.rate = dl_rate
            self.update.emit()
            msg = show_progress(file_name, total_size, now_size)
            self.download_proc.emit(msg)

    def _down_failed(self, code, file):
        """显示下载失败的回调函数"""
        self.folder_file_failed.emit(code, file)

    def __del__(self):
        self._task.run = False
        self.wait()

    def run(self):
        try:
            self._task.run = True
            if is_file_url(self._task.url):  # 下载文件
                res = self._disk.down_file_by_url(self._task.url, self._task.pwd,
                                                  self._task.path, self._show_progress)
            elif is_folder_url(self._task.url):  # 下载文件夹
                res = self._disk.down_dir_by_url(self._task.url, self._task.pwd, self._task.path,
                                                 self._show_progress, mkdir=True, failed_callback=self._down_failed)
            else:
                return None
            if res == 0:
                self._task.rate = 1000
                self.update.emit()
            else:
                self._task.info = why_error(res)
                logger.debug(f"Download : {res=}")
                self.download_failed.emit()
        except TimeoutError:
            self._task.info = "网络连接错误!"
            logger.error("Download TimeOut")
            self.download_failed.emit()
        except Exception as err:
            self._task.info = f"未知错误!{err=}"
            logger.error(f"Download error: {err=}")
            self.download_failed.emit()
        self.download_finished.emit(self._task)


class DownloadManager(QThread):
    '''下载控制器线程,追加下载任务,控制后台下载线程数量'''
    downloaders_msg = pyqtSignal(str, int)
    update = pyqtSignal()
    mgr_finished = pyqtSignal()

    def __init__(self, thread=3, parent=None):
        super(DownloadManager, self).__init__(parent)
        self._disk = None
        self._tasks = {}
        self._queues = []
        self._thread = thread
        self._count = 0
        self._mutex = QMutex()
        self._is_work = False
        self._old_msg = ""
        self.downloaders = {}

    def set_disk(self, disk):
        self._disk = disk

    def set_thread(self, thread):
        self._thread = thread

    def stop_task(self, task):
        """暂停任务"""
        if task.url in self.downloaders and self.downloaders[task.url].isRunning():
            logger.debug(f"Stop job: {task.url}")
            try:
                self._tasks[task.url].pause = True
                self.downloaders[task.url].stop()
                self._tasks[task.url].run = False
            except Exception as err:
                logger.error(f"Stop task: {err=}")
        else:
            logger.debug(f"Stop job: {task.url} is not running!")
        self.update.emit()

    def start_task(self, task):
        """开始已暂停任务"""
        if task.url not in self.downloaders:
            self.add_task(task)
        elif not self.downloaders[task.url].isRunning():
            logger.debug(f"Start job: {task}")
            self.downloaders[task.url].start()
            self._tasks[task.url].run = True
            self._tasks[task.url].pause = False
            self.update.emit()

    def add_task(self, task):
        if task.url not in self._tasks.keys():
            logger.debug(f"DownloadMgr add one: {task=}")
            self._tasks[task.url] = task
        self.start()

    def add_tasks(self, tasks: dict):
        # logger.debug(f"DownloadMgr add: {tasks=}")
        self._tasks.update(tasks)
        self.start()

    def del_task(self, task):
        logger.debug(f"DownloadMgr del: {task.url=}")
        if task.url in self._tasks:
            del self._tasks[task.url]
        if task.url in self.downloaders:
            del self.downloaders[task.url]

    def _task_to_queue(self):
        for task in self._tasks.values():
            if not task.added and not task.pause and task not in self._queues:
                self._queues.append(task)

    def __del__(self):
        self.wait()

    def _ahead_msg(self, msg):
        if self._old_msg != msg:
            if self._count == 1:
                self.downloaders_msg.emit(msg, 0)
            else:
                self.downloaders_msg.emit(f"有{self._count}个下载任务正在运行", 0)
            self._old_msg = msg

    def _ahead_error(self):
        self.update.emit()

    def _ahead_folder_error(self, code, file):
        # 需要单独考虑,不在 task中
        pass

    def _ahead_rate(self):
        self.update.emit()

    def _add_thread(self, task):
        logger.debug(f"DownloadMgr count: {self._count}")
        self._count -= 1
        del self.downloaders[task.url]
        # 发送所有任务完成信号
        for task in self._tasks.values():
            if task.rate < 1000:
                return None
        self.mgr_finished.emit()

    def stop(self):
        self._mutex.lock()
        self._is_work = False
        self._mutex.unlock()

    def run(self):
        if not self._is_work:
            self._mutex.lock()
            self._is_work = True
            while True:
                self._task_to_queue()
                if not self._queues:
                    break
                while self._count >= self._thread:
                    self.sleep(1)
                self._count += 1
                task = self._queues.pop()
                logger.debug(f"DownloadMgr run: {task.url=}")
                self.downloaders[task.url] = Downloader(self._disk, task)
                self.downloaders_msg.emit(f"准备下载:<font color='#FFA500'>{task.name}</font>", 0)
                try:
                    self.downloaders[task.url].download_finished.connect(self._add_thread)
                    self.downloaders[task.url].download_proc.connect(self._ahead_msg)
                    self.downloaders[task.url].update.connect(self._ahead_rate)
                    self.downloaders[task.url].folder_file_failed.connect(self._ahead_folder_error)
                    self.downloaders[task.url].download_failed.connect(self._ahead_error)
                    task.added = True
                    self.downloaders[task.url].start()
                except Exception as err:
                    logger.error(f"DownloadMgr Error: {err=}")
            self._is_work = False
            self._mutex.unlock()