#!/usr/bin/python3

import datetime
import os.path
import subprocess
import sys

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QKeyEvent, QBrush, QColor, QKeySequence
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QWidget, QAction, QStatusBar, QMenu, QMenuBar, QTableWidgetItem, QHBoxLayout, \
    QTableWidget, QMainWindow, QVBoxLayout, QPushButton, QAbstractItemView, QMessageBox, \
    QFileDialog, QComboBox, QLineEdit, QLabel, QScrollArea, QInputDialog, QSplitter, QShortcut
from anytree.exporter import UniqueDotExporter

from mosplan_modules.config import config_file, version
from mosplan_modules.data_store import import_tree_from_file, node_to_list, Node, node_params
from mosplan_modules.design import TableDateTimeEdit, TableCheckBox, WindowForSelectValue
from mosplan_modules.xml_import import import_xml


class NodeTableWidget(QTableWidget):
    move_signal = pyqtSignal(bool)
    open_comment_signal = pyqtSignal(int)

    def __init__(self, *__args):
        super().__init__(*__args)
        self.comment_shortcut = QShortcut(QKeySequence('Ctrl+K'), self)
        self.comment_shortcut.activated.connect(self.send_comment_signal)

    def keyPressEvent(self, a0: QKeyEvent):
        if a0.key() == Qt.Key_Tab:
            self.move_signal.emit(True)
            a0.ignore()
        elif a0.key() == Qt.Key_Backspace:
            self.move_signal.emit(False)
            a0.ignore()
        else:
            a0.accept()

    def send_comment_signal(self):
        self.open_comment_signal.emit(self.currentRow())


class MyWindow(QMainWindow):
    def initUI(self, MainWindow):
        MainWindow.showMaximized()
        self.verticalLayoutWidget = QWidget()
        self.verticalLayoutWidget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
        self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)

        self.splitter = QSplitter()
        self.splitter.setOrientation(Qt.Horizontal)

        self.tableWidget = NodeTableWidget()

        self.horizontalHeaders = ['№', 'Этап', 'Продолжительность', 'Дата начала', 'Дата окончания',
                                  'Предшествующие этапы', 'Ответственный', 'Выполнено']
        self.tableWidget.setColumnCount(len(self.horizontalHeaders))
        self.tableWidget.verticalHeader().setStretchLastSection(False)

        self.filename = '/tmp/tasks1.mosplan'
        if not os.path.isfile(self.filename):
            self.create_temp_tree()

        self.tableWidget.itemChanged.connect(self.update_table_data_from_item)
        self.tableWidget.move_signal.connect(self.move_node)
        self.tableWidget.open_comment_signal.connect(self.edit_comment)
        self.tableWidget.selectionModel().selectionChanged.connect(self.show_comment)
        self.tableWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tableWidget.customContextMenuRequested.connect(self.on_customContextMenuRequested)
        self.tableWidget.setEditTriggers(QAbstractItemView.AllEditTriggers)
        self.tableWidget.setHorizontalHeaderLabels(self.horizontalHeaders)
        self.splitter.addWidget(self.tableWidget)

        self.graphicsView = QWebEngineView()
        self.graphicsView.setObjectName(u"graphicsView")

        self.update_tree_from_file()
        self.update_gantt_diagram()

        self.splitter.addWidget(self.graphicsView)
        self.graphicsView.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addWidget(self.splitter)
        self.splitter.setSizes([self.screen().geometry().width() // 2, self.screen().geometry().width() // 2])
        self.splitter.splitterMoved.connect(self.adjust_table_size)

        self.comment_scroll_area = QScrollArea()
        self.comment_widget = QLabel('')
        self.comment_widget.setWordWrap(True)
        self.comment_widget.setOpenExternalLinks(True)
        self.comment_widget.setFixedWidth(self.comment_scroll_area.width())
        self.comment_scroll_area.setWidget(self.comment_widget)
        self.comment_scroll_area.setMaximumHeight(50)
        self.verticalLayout.addWidget(self.comment_scroll_area)

        self.low_layout = QHBoxLayout()
        self.table_rows_adding_buttons_layout = QHBoxLayout()
        self.add_node_button = QPushButton('+')
        self.add_node_button.clicked.connect(self.add_node)
        self.table_rows_adding_buttons_layout.addWidget(self.add_node_button)

        self.remove_node_button = QPushButton('-')
        self.remove_node_button.clicked.connect(self.remove_node)
        self.table_rows_adding_buttons_layout.addWidget(self.remove_node_button)

        self.move_left_node_button = QPushButton('←')
        self.move_left_node_button.clicked.connect(lambda: self.move_node(False))
        self.table_rows_adding_buttons_layout.addWidget(self.move_left_node_button)

        self.move_right_node_button = QPushButton('→')
        self.move_right_node_button.clicked.connect(lambda: self.move_node(True))
        self.table_rows_adding_buttons_layout.addWidget(self.move_right_node_button)

        self.table_rows_adding_buttons_layout.addStretch()

        self.buttons_layout = QHBoxLayout()
        self.buttons_layout.addStretch()

        self.change_diagram_visibility_button = QPushButton('Скрыть диаграмму')
        self.change_diagram_visibility_button.clicked.connect(self.change_diagram_visibility)
        self.buttons_layout.addWidget(self.change_diagram_visibility_button)

        create_button = QPushButton('Создать')
        create_button.clicked.connect(self.create_function)
        self.buttons_layout.addWidget(create_button)

        open_button = QPushButton('Открыть файл')
        open_button.clicked.connect(self.open_function)
        self.buttons_layout.addWidget(open_button)

        xml_import_button = QPushButton('Импорт xml')
        xml_import_button.setToolTip('Выберите экспортированный из Microsoft Project или ProjectLibre файл xml')
        xml_import_button.clicked.connect(self.xml_import_function)
        self.buttons_layout.addWidget(xml_import_button)

        close_button = QPushButton('Выход из программы')
        close_button.clicked.connect(self.close_function)
        self.buttons_layout.addWidget(close_button)

        if subprocess.run(f'py-ini-config get {config_file} -n diagram_is_visible',
                          shell=True, capture_output=True).stdout.decode().strip() == 'False':
            self.change_diagram_visibility()

        self.low_layout.addLayout(self.table_rows_adding_buttons_layout)
        self.low_layout.addLayout(self.buttons_layout)
        self.verticalLayout.addLayout(self.low_layout)

        MainWindow.setCentralWidget(self.verticalLayoutWidget)
        self.menubar = QMenuBar(MainWindow)
        self.menu = QMenu('Настройки', self.menubar)
        MainWindow.setMenuBar(self.menubar)
        self.action_days_to_delay = QAction('Дни до выполнения задачи', MainWindow)
        self.action_days_to_delay.triggered.connect(self.set_days_to_delay)
        self.action_hide_done_tasks = QAction('Скрывать выполненные задачи', MainWindow)
        self.action_hide_done_tasks.setCheckable(True)
        self.action_hide_done_tasks.triggered.connect(self.change_done_tasks_visibility)

        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.menubar.addAction(self.menu.menuAction())
        self.menu.addAction(self.action_days_to_delay)
        self.menu.addAction(self.action_hide_done_tasks)
        self.adjust_table_size()
        self.setWindowTitle(f'Планировщик задач МОСплан ver. {version}')

    @QtCore.pyqtSlot(QtCore.QPoint)
    def on_customContextMenuRequested(self, pos):
        it = self.tableWidget.itemAt(pos)
        if it is None: return
        row = it.row()

        menu = QtWidgets.QMenu()
        edit_comment_action = menu.addAction("Изменить комментарий")
        action = menu.exec_(self.tableWidget.viewport().mapToGlobal(pos))
        if action == edit_comment_action:
            self.edit_comment(row)

    def edit_comment(self, row):
        input_dialog = QInputDialog(self)
        input_dialog.setInputMode(QInputDialog.TextInput)
        input_dialog.setWindowTitle("Введите новый комментарий")
        input_dialog.setLabelText(f'Комментарий для задачи "{self.root.descendants[row].name}": ')
        input_dialog.setTextValue(self.root.descendants[row].comment)
        input_dialog.setTextEchoMode(QLineEdit.Normal)
        input_dialog.resize(800, 70)
        input_dialog.show()
        if input_dialog.exec_() == input_dialog.Accepted:
            text = input_dialog.textValue()
            if text != '':
                self.root.descendants[row].comment = text
                self.save_tree_to_file(self.filename)
                self.show_comment()

    def change_done_tasks_visibility(self):
        if self.action_hide_done_tasks.isChecked():
            self.is_done_tasks_visible = False
        else:
            self.is_done_tasks_visible = True
        self.update_gantt_diagram()

    def set_days_to_delay(self):
        window_for_select_value = WindowForSelectValue()
        if window_for_select_value.exec_():
            self.time_to_delay = window_for_select_value.spinbox.value()
            self.update_tree_from_file()

    def show_comment(self):
        row = self.tableWidget.currentRow()
        comment = self.root.descendants[row].comment
        words = comment.split()
        for word in words:
            if '://' in word:
                comment = comment.replace(word, f'<a href="{word}">{word}</a>')
        self.comment_widget.setText(comment)
        self.comment_widget.adjustSize()

    def change_diagram_visibility(self):
        if self.diagram_is_visible:
            self.splitter.widget(1).setParent(None)
            self.change_diagram_visibility_button.setText('Показать диаграмму')
        else:
            self.splitter.addWidget(self.graphicsView)
            self.change_diagram_visibility_button.setText('Скрыть диаграмму')
        self.diagram_is_visible = not self.diagram_is_visible
        if not os.path.isfile(config_file):
            subprocess.run(f'py-ini-config set -c {config_file} diagram_is_visible {self.diagram_is_visible} -n',
                           shell=True)
        else:
            subprocess.run(f'py-ini-config set {config_file} diagram_is_visible {self.diagram_is_visible} -n',
                           shell=True)
        self.update_gantt_diagram()
        self.adjust_table_size()

    def update_table_data_from_datetime(self):
        row, column = self.sender().row, self.sender().column
        node = self.root.descendants[row]
        if column == 3:
            node.start = self.tableWidget.cellWidget(row, 3).date().toPyDate()
        if column == 4:
            node.finish = self.tableWidget.cellWidget(row, 4).date().toPyDate()
        if type(node.start) is datetime.datetime:
            node.start = node.start.date()
        if type(node.finish) is datetime.datetime:
            node.finish = node.finish.date()
        if node.finish >= node.start:
            duration = str((node.finish - node.start).days + 1)
        else:
            duration = 'Проверьте даты'
        self.tableWidget.setItem(row, 2, QTableWidgetItem(duration))
        self.save_tree_to_file(self.filename)
        self.change_row_color(row)
        for child in node.children:
            child_row = self.root.descendants.index(child)
            self.change_row_color(child_row)
        self.update_gantt_diagram()

    def update_table_data_from_item(self, item):
        row, column = item.row(), item.column()
        node = self.root.descendants[row]
        if column == 1:
            node.name = item.text().strip()
            item.setText('\t' * (node.depth - 1) + node.name)
        elif column == 5:
            if item.text:
                try:
                    entered_numbers = list(map(int, item.text().split(',')))
                    assert all(1 <= i <= len(self.root.descendants) for i in entered_numbers)
                    node.previous = self.previous_numbers_to_ids(item.text())
                except AssertionError:
                    entered_numbers = [i for i in entered_numbers if 1 <= i <= len(self.root.descendants)]
                    node.previous = self.previous_numbers_to_ids(item.text())
                    self.tableWidget.setItem(
                        row, column, QTableWidgetItem(', '.join([str(i) for i in entered_numbers])))
                except Exception:
                    print(f'Не удалось обработать текст {item.text()}')
                    self.update_tree_from_file()
                    return
            else:
                node.previous = None
        self.save_tree_to_file(self.filename)
        self.update_gantt_diagram()

    def update_table_data_from_worker_list(self):
        try:
            row = self.tableWidget.findChildren(QLineEdit).index(self.sender()) // 3
        except Exception as e:
            print(f'Error occured: {e}')
            return
        node = self.root.descendants[row]
        node.worker = self.sender().text()
        if node.worker not in self.all_workers:
            self.all_workers.add(node.worker)
            self.add_worker_to_lists(node.worker)
        self.save_tree_to_file(self.filename)
        self.update_gantt_diagram()

    def update_table_data_from_checkbox(self):
        try:
            row = self.tableWidget.findChildren(TableCheckBox).index(self.sender().parent())
        except Exception as e:
            print(f'Error occured: {e}')
            return
        node = self.root.descendants[row]
        node.done = self.sender().parent().state()
        self.save_tree_to_file(self.filename)
        self.update_gantt_diagram()

    def save_tree_to_file(self, filename):
        if self.filename:
            filename = self.filename
        exporter = UniqueDotExporter(self.root, nodeattrfunc=node_params)
        try:
            exporter.to_dotfile(filename)
        except Exception as e:
            dlg = QMessageBox()
            dlg.setWindowTitle('Внимание!')
            dlg.setText(f'Ошибка записи в файл {e}')
            dlg.exec()
            sys.exit(1)
        # Необходимо считать и второй раз записать в файл, чтобы у узлов прописались уникальные ID от UniqueDotExporter
        self.root = import_tree_from_file(self.filename)
        # При новом импорте обновляются ID, но имена должны сохраниться. Иначе - сообщение об ошибке
        exporter = UniqueDotExporter(self.root, nodeattrfunc=node_params)
        exporter.to_dotfile(filename)
        # self.update_gantt_diagram()

    def add_worker_to_lists(self, new_worker: str):
        for worker in self.tableWidget.findChildren(QComboBox):
            if new_worker not in [worker.itemText(i) for i in range(worker.count())]:
                worker.addItem(new_worker)
        # TODO разобраться с Enter

    def update_tree_from_file(self):
        try:
            self.tableWidget.itemChanged.disconnect()
        except Exception:
            pass
        self.all_workers = set()
        self.tableWidget.clear()
        self.tableWidget.setHorizontalHeaderLabels(self.horizontalHeaders)
        self.root = import_tree_from_file(self.filename)
        self.tableWidget.setRowCount(len(self.root.descendants))
        for i, node in enumerate(self.root.descendants):
            self.set_node_data_to_table_row(node, i)
            self.all_workers.add(node.worker)
        for worker in self.all_workers:
            self.add_worker_to_lists(worker)
        self.tableWidget.itemChanged.connect(self.update_table_data_from_item)
        # self.update_gantt_diagram()

    def move_node(self, direction: bool = True):
        # True - move right, False - move left
        row = self.tableWidget.currentRow()
        node = self.root.descendants[row]
        if direction:
            other_row = row - 1
            while other_row >= 0 and node.parent != self.root.descendants[other_row].parent:
                other_row -= 1
            if other_row != -1:
                node.parent = self.root.descendants[other_row]
        else:
            if node.parent != self.root:
                node.parent = node.parent.parent
        self.save_tree_to_file(self.filename)
        self.update_tree_from_file()
        self.update_gantt_diagram()

    def previous_ids_to_numbers(self, ids: str):
        ids = ids.replace(' ', '').split(',')
        numbers = []
        for i in range(len(ids)):
            numbers.append(str([node.id for node in self.root.descendants].index(ids[i]) + 1))
        return ', '.join(numbers)

    def previous_numbers_to_ids(self, numbers: str):
        numbers = numbers.replace(' ', '').split(',')
        ids = []
        for i in numbers:
            try:
                ids.append(self.root.descendants[int(i) - 1].id)
            except Exception as e:
                print(f'Не найдено узла с номером {i}, ошибка {e}')
                continue
        return ', '.join(ids)

    def set_node_data_to_table_row(self, node, i):
        node_values = node_to_list(node)
        self.tableWidget.setItem(i, 0, QTableWidgetItem(str(i + 1)))
        self.tableWidget.item(i, 0).setFlags(Qt.ItemIsEnabled)
        self.tableWidget.setItem(i, 1, QTableWidgetItem('\t' * (node.depth - 1) + node_values[0]))
        self.tableWidget.setItem(i, 2, QTableWidgetItem(node_values[1]))
        self.tableWidget.item(i, 2).setFlags(Qt.ItemIsEnabled)
        self.tableWidget.setCellWidget(i, 3, TableDateTimeEdit(node.start))
        self.tableWidget.cellWidget(i, 3).setCalendarPopup(True)
        self.tableWidget.cellWidget(i, 3).setDisplayFormat('dd.MM.yyyy')
        self.tableWidget.cellWidget(i, 3).set_row(i)
        self.tableWidget.cellWidget(i, 3).set_column(3)
        self.tableWidget.cellWidget(i, 3).dateChanged.connect(self.update_table_data_from_datetime)
        self.tableWidget.setCellWidget(i, 4, TableDateTimeEdit(node.finish))
        self.tableWidget.cellWidget(i, 4).setCalendarPopup(True)
        self.tableWidget.cellWidget(i, 4).setDisplayFormat('dd.MM.yyyy')
        self.tableWidget.cellWidget(i, 4).set_row(i)
        self.tableWidget.cellWidget(i, 4).set_column(4)
        self.tableWidget.cellWidget(i, 4).dateChanged.connect(self.update_table_data_from_datetime)
        if node_values[4]:
            self.tableWidget.setItem(i, 5, QTableWidgetItem(self.previous_ids_to_numbers(node_values[4])))
        self.tableWidget.setCellWidget(i, 6, QComboBox())
        self.tableWidget.cellWidget(i, 6).setEditable(True)
        self.tableWidget.cellWidget(i, 6).addItem(node_values[5])
        self.tableWidget.cellWidget(i, 6).lineEdit().editingFinished.connect(self.update_table_data_from_worker_list)
        self.tableWidget.setCellWidget(i, 7, TableCheckBox())
        self.tableWidget.cellWidget(i, 7).set_state(node_values[6])
        self.tableWidget.cellWidget(i, 7).checkbox.stateChanged.connect(self.update_table_data_from_checkbox)
        self.change_row_color(i)

    def change_row_color(self, row):
        if self.tableWidget.item(row, 1):
            if type(self.root.descendants[row].finish) is datetime.datetime:
                self.root.descendants[row].finish = self.root.descendants[row].finish.date()
            # Если задача просрочена
            if self.root.descendants[row].finish <= datetime.datetime.today().date():
                self.tableWidget.item(row, 1).setBackground(QBrush(Qt.red))
            # Если осталась неделя
            elif self.root.descendants[row].finish <= datetime.datetime.today().date() + \
                    datetime.timedelta(self.time_to_delay):
                self.tableWidget.item(row, 1).setBackground(QBrush(QColor('#762176')))
            # Если родитель заканчивается раньше, чем вложенная задача
            elif self.root.descendants[row].parent is not self.root and \
                    self.root.descendants[row].finish > self.root.descendants[row].parent.finish:
                self.tableWidget.item(row, 1).setBackground(QBrush(QColor('#767621')))
            else:
                self.tableWidget.item(row, 1).setBackground(QBrush(self.tableWidget.item(row, 0).background().color()))

    def adjust_table_size(self):
        size = self.splitter.sizes()[0]
        self.tableWidget.setColumnWidth(0, 30)
        self.tableWidget.setColumnWidth(1, 300)
        self.tableWidget.setColumnWidth(self.tableWidget.columnCount() - 1, 100)
        for column in range(2, self.tableWidget.columnCount() - 1):
            self.tableWidget.setColumnWidth(column, (size - 430) // (self.tableWidget.columnCount() - 3))

        self.tableWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)

    def add_node(self):
        new_node = Node()
        selected = self.tableWidget.selectedItems()
        if selected:
            self.tableWidget.insertRow(self.tableWidget.currentRow() + 1)
            current_level = list(self.root.descendants[self.tableWidget.currentRow()].parent.children)
            index_to_insert = current_level.index(self.root.descendants[self.tableWidget.currentRow()]) + 1
            current_level.insert(index_to_insert, new_node)
            self.root.descendants[self.tableWidget.currentRow()].parent.children = tuple(current_level)
        else:
            self.tableWidget.setRowCount(self.tableWidget.rowCount() + 1)
            if self.tableWidget.rowCount() == 1:
                new_node.parent = self.root
            else:
                new_node.parent = self.root.descendants[-1].parent
        self.save_tree_to_file(self.filename)
        self.update_tree_from_file()

    def remove_node(self):
        row = self.tableWidget.currentRow()
        reply = QMessageBox.question(None, 'Внимание!',
                                     f'Вы действительно хотите удалить задачу "{self.root.descendants[row].name}" '
                                     f'и все вложенные задачи?',
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.root.descendants[row].parent = None
            self.save_tree_to_file(self.filename)
            self.update_tree_from_file()

    def create_function(self):
        fileName, ok = QFileDialog.getSaveFileName(self, "Выберите имя файла для хранения базы", "",
                                                   "mosplan files (*.mosplan)")
        if ok:
            self.filename = fileName
            try:
                with open(fileName, 'w') as out:
                    pass
            except Exception as e:
                dlg = QMessageBox()
                dlg.setWindowTitle('Внимание!')
                dlg.setText(f'Не удалось выбрать файл {fileName} для хранения базы, ошибка {e}')
                dlg.exec()
                return
            if not os.path.isfile(fileName) or os.path.getsize(fileName) != 0:
                print(f'Не удалось выбрать файл {fileName} для хранения базы.')
                return
            self.tableWidget.clear()
            self.tableWidget.setHorizontalHeaderLabels(self.horizontalHeaders)
            self.tableWidget.setRowCount(0)
            self.root = Node(id='0x0', name='root')
            self.save_tree_to_file(self.filename)
            self.add_node()

    def open_function(self, given_filename=''):
        if not given_filename:
            fileName, ok = QFileDialog.getOpenFileName(self, "Выберите файл", "", "mosplan files (*.mosplan)")
        else:
            fileName, ok = given_filename, True
        if ok:
            try:
                self.root = import_tree_from_file(fileName)
            except Exception as e:
                print(f'Не удалось загрузить базу из файла {fileName}, ошибка: {e}')
                return
            self.filename = fileName
            self.update_tree_from_file()
            self.update_gantt_diagram()

    def xml_import_function(self):
        fileName, ok = QFileDialog.getOpenFileName(self, "Выберите файл", "", "xml files (*.xml)")
        if ok:
            try:
                root = import_xml(fileName)
            except Exception as e:
                print(f'Не удалось импортировать базу из файла {fileName}, ошибка: {e}')
                return
            if root:
                mosplan_filename, ok = QFileDialog.getSaveFileName(
                    self, 'Выберите файл для сохранения:', f'{fileName[:-4]}.mosplan', "mosplan files (*.mosplan)"
                )
                if ok:
                    self.root = root
                    self.filename = mosplan_filename
                    self.save_tree_to_file(self.filename)
                    self.update_tree_from_file()
                    self.update_gantt_diagram()
                else:
                    return
            else:
                print(f'При импорте возникла ошибка.')
                return

    def close_function(self):
        reply = QMessageBox.question(None, 'Внимание!', 'Выйти из программы?', QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.close()
        else:
            return

    def create_temp_tree(self):
        from anytree.exporter import UniqueDotExporter
        root = Node(name='root')
        stage1 = Node('', 'Первый этап', '2024.06.11', '2024.07.19', 'Артём', parent=root)

        a = Node('', 'Начало', '2024.06.11', '2024.06.12', 'Артём', parent=stage1)
        b = Node('', 'Получить ТЗ', '2024.06.11', '2024.06.11', 'Я', parent=stage1)
        c = Node('', 'Выпить кофе', '2024.06.12', '2024.06.12', 'Милена', parent=stage1)
        d = Node('', 'Съесть печеньку', '2024.06.12', '2024.06.12', 'Милена', parent=c)
        e = Node('', 'Съесть печеньку', '2024.06.11', '2024.06.11', 'Миша', parent=stage1)
        exporter = UniqueDotExporter(root, nodeattrfunc=node_params)
        exporter.to_dotfile(self.filename)

    def update_gantt_diagram(self):
        if self.diagram_is_visible:
            import plotly.express as px
            import pandas as pd
            frame = []
            for node in self.root.descendants:
                if self.is_done_tasks_visible or not node.done:
                    frame.append(
                        dict(Задача=node.name, Начало=node.start, Окончание=node.finish, Ответственный=node.worker,
                             Выполнено='да' if node.done else 'нет'))
            df = pd.DataFrame(frame)

            # Уникальный ID задачи, при том, что их названия и даже исполнители могут совпадать
            df['id'] = df['Задача'] + df['Ответственный'] + str(df['Начало']) + str(df['Окончание'])
            df = df.sort_values('Начало').reset_index()

            fig = px.timeline(df, x_start="Начало", x_end="Окончание", y='id', color="Ответственный",
                              title="Диаграмма Ганта", text=['Выполнено' if i == 'да' else '' for i in df['Выполнено']])
            fig.add_vline(x=datetime.datetime.today())

            fig.update_yaxes(autorange="reversed", title='Задача', tickvals=df['id'], ticktext=df['Задача'])
            self.graphicsView.setHtml(fig.to_html(include_plotlyjs='cdn'))

    def __init__(self, file_to_open=None):
        QMainWindow.__init__(self)
        self.diagram_is_visible = True
        self.is_done_tasks_visible = True
        if not os.path.isfile(config_file):
            subprocess.run(f'py-ini-config set -c {config_file} time_to_delay 7 -n',
                           shell=True)
            self.time_to_delay = 7
        else:
            try:
                self.time_to_delay = int(subprocess.run(f'py-ini-config get {config_file} -n time_to_delay',
                                                        shell=True, capture_output=True).stdout.decode())
            except Exception:
                self.time_to_delay = 7
                subprocess.run(f'py-ini-config set {config_file} time_to_delay 7 -n',
                               shell=True)
        self.initUI(self)
        self.show()
        self.all_workers = set()
        if file_to_open:
            self.open_function(file_to_open)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    if len(sys.argv) > 1:
        window = MyWindow(sys.argv[1])
    else:
        window = MyWindow()
    sys.exit(app.exec_())
