import os, posixpath
from collections import defaultdict

from PyQt5.QtWidgets import QListWidgetItem, QAbstractItemView
from PyQt5.QtCore import Qt, pyqtSignal

from pyqt_files_already_exists_dialog import FilesAlreadyExistDialog
from pyqt_tooltip_list_widget import ToolTipListWidget


class FileListWidget(ToolTipListWidget):
    added = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.__initVal()
        self.__initUi()

    def __initVal(self):
        self.__exists_dialog_not_ask_again_flag = False
        self.__duplicate_flag = True

        self.__extensions = []
        self.__basename_absname_dict = defaultdict(str)
        self.__show_filename_only_flag = False

    def __initUi(self):
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setAcceptDrops(True)

    def isDuplicateEnabled(self) -> bool:
        return self.__duplicate_flag

    def setDuplicateEnabled(self, f: bool):
        self.__duplicate_flag = f

    def setExtensions(self, extensions: list):
        self.__extensions = extensions

    def __addFilename(self, filename: str):
        basename = os.path.basename(filename)
        self.__basename_absname_dict[basename] = filename
        if self.isFilenameOnly():
            self.addItem(basename)
        else:
            self.addItem(filename)

    def __addFilenames(self, filenames: list, cur_filename: str = ''):
        self.added.emit(filenames)
        for filename in filenames:
            self.__addFilename(filename)
        self.__setCurrentFilename(filenames, cur_filename)

    def __setCurrentFilename(self, filenames: list, cur_filename: str = ''):
        cur_filename = filenames[0] if cur_filename == '' else cur_filename
        items = self.findItems(cur_filename, Qt.MatchFixedString)
        if items:
            r = self.row(items[0])
            self.setCurrentRow(r)

    def __execDuplicateFilenamesDialog(self, duplicate_filenames: list):
        dialog = FilesAlreadyExistDialog()
        dialog.setDontAskAgainChecked(self.__exists_dialog_not_ask_again_flag)
        dialog.setExistFiles(duplicate_filenames)
        reply = dialog.exec()

    def __getFilenamesInDirectory(self, dirname: str) -> list:
        filenames = [os.path.join(dirname, filename).replace(os.path.sep, posixpath.sep) for filename in
                     os.listdir(dirname)]
        return filenames

    def setDirectory(self, dirname: str, cur_filename: str = ''):
        filenames = self.__getFilenamesInDirectory(dirname)
        self.setFilenames(filenames, cur_filename)

    def addDirectory(self, dirname: str, cur_filename: str = ''):
        filenames = self.__getFilenamesInDirectory(dirname)
        self.addFilenames(filenames, cur_filename)

    def setFilenames(self, filenames: list, cur_filename: str = ''):
        self.clear()
        self.addFilenames(filenames, cur_filename=cur_filename)

    def addFilenames(self, filenames: list, cur_filename: str = ''):
        filenames = self.__getExtFilteredFiles(filenames)
        if self.isDuplicateEnabled():
            self.__addFilenames(filenames, cur_filename)
        else:
            duplicate_filenames, not_duplicate_filenames = self.__getDuplicateItems(filenames)
            if duplicate_filenames:
                self.__execDuplicateFilenamesDialog(duplicate_filenames)
                self.__addFilenames(not_duplicate_filenames)
            else:
                self.__addFilenames(filenames)

    def setFilenameOnly(self, f: bool):
        self.__show_filename_only_flag = f
        self.__execShowingBaseName(f)

    def remove(self, item: QListWidgetItem):
        filename = item.text()
        self.takeItem(self.row(item))
        self.__basename_absname_dict.pop(os.path.basename(filename))

    def getSelectedFilenames(self) -> list:
        items = self.selectedItems()
        filenames = [item.text() for item in items]
        return filenames

    def removeSelectedRows(self):
        items = self.selectedItems()
        if items:
            removed_start_idx = self.row(items[0])
            cur_idx = removed_start_idx - 1
            if removed_start_idx == 0:
                cur_idx = 0
            items = list(reversed(items))
            for item in items:
                self.remove(item)
            self.setCurrentRow(cur_idx)

    def clear(self):
        for i in range(self.count() - 1, -1, -1):
            self.remove(self.item(i))
        super().clear()

    def isFilenameOnly(self) -> bool:
        return self.__show_filename_only_flag

    # refactoring needed
    def __getDuplicateItems(self, filenames: list):
        exists_file_lst = []
        not_exists_file_lst = []
        filenames_to_find = filenames
        for filename_to_find in filenames_to_find:
            if self.isFilenameOnly():
                items = self.findItems(os.path.basename(filename_to_find), Qt.MatchFixedString)
            else:
                items = self.findItems(filename_to_find, Qt.MatchFixedString)
            if items:
                exists_file_lst.append(filename_to_find)
            else:
                not_exists_file_lst.append(filename_to_find)
        return exists_file_lst, not_exists_file_lst

    def getAbsFilename(self, basename: str):
        return self.__basename_absname_dict[basename]

    def getFilenameFromRow(self, r: int) -> str:
        if self.isFilenameOnly():
            return self.getAbsFilename(self.item(r).text())
        else:
            return self.item(r).text()

    def __getExtFilteredFiles(self, lst):
        if len(self.__extensions) > 0:
            return list(filter(None, map(lambda x: x if os.path.splitext(x)[-1] in self.__extensions else None, lst)))
        else:
            return lst

    def __getUrlsFilenames(self, urls):
        return list(map(lambda x: x.path()[1:], urls))

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.accept()

    def dragMoveEvent(self, e):
        pass

    def dropEvent(self, e):
        filenames = [file for file in self.__getExtFilteredFiles(
            self.__getUrlsFilenames(e.mimeData().urls())) if file]
        self.addFilenames(filenames)
        super().dropEvent(e)

    def __execShowingBaseName(self, f: bool):
        self.__show_filename_only_flag = f
        items = [self.item(i) for i in range(self.count())]
        if f:
            for item in items:
                absname = item.text()
                basename = os.path.basename(absname)
                item.setText(basename)
        else:
            for item in items:
                basename = item.text()
                absname = self.__basename_absname_dict[basename]
                item.setText(absname)
