#!/usr/bin/python3
import csv
import datetime
import os
import sys
from math import log, ceil

import pandas as pd
from PyQt6.QtCore import QPoint, Qt, QRect
from PyQt6.QtGui import QPainter, QFont, QPixmap, QColor, QBrush, QAction, QFontMetrics, QIcon
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget,
                             QVBoxLayout, QPushButton, QFileDialog, QScrollArea, QLabel, QMenu, QMenuBar, QTabWidget,
                             QMessageBox)
from python3_mos_pyqt_dialogs.pyqt6_dialogs_functions import alert, question

from tournament_grid_modules.config import *
from tournament_grid_modules.tournament_grid_classes import (DraggableLabel, Player, DrawableImage, ClickableLabel,
                                                             MarksEditWindow, SettingsWindow)
from tournament_grid_modules.tournament_grid_functions import save_pixmap_to_pdf, \
    check_if_any_participant_exist_in_tour


class MainWindow(QMainWindow):

    def count_round(self):
        """
        Подсчёт очков текущего тура и переход в следующий с увеличением переменной tour для текущей вкладки
        :return: -
        """
        # на случай, если до конца турнира экспортировали pdf
        for label in self.tabs[self.tab_index].draw_widget.children():
            if type(label) is DraggableLabel and label.text().strip() == '':
                label.setText('пусто')
        try:
            self.re_read_marks()
            self.tab_index = self.tab_widget.currentIndex()
            if len(self.tabs[self.tab_index].players) == 1:
                alert('Турнир завершён.')
                return

            self.tabs[self.tab_index].players_to_remove = []
            self.tabs[self.tab_index].new_draggable_labels = []
            empty_number = 1
            for i in range(len(self.tabs[self.tab_index].draggable_labels)):
                if i % 2 == 1:
                    continue
                player1_name = self.tabs[self.tab_index].draggable_labels[i].text().split('. ')[-1]
                if player1_name == 'пусто':
                    player1_name = f'пусто{empty_number}'
                    empty_number += 1
                player1 = self.tabs[self.tab_index].players[player1_name]
                player2_name = self.tabs[self.tab_index].draggable_labels[i + 1].text().split('. ')[-1]
                if player2_name == 'пусто':
                    player2_name = f'пусто{empty_number}'
                    empty_number += 1
                player2 = self.tabs[self.tab_index].players[player2_name]
                if not self.connect_two_players(player1, player2):
                    return
            for i in range(len(self.tabs[self.tab_index].draggable_labels)):
                name = self.tabs[self.tab_index].draggable_labels[i].text().split('. ')[-1]
                if name in self.tabs[self.tab_index].players_to_remove and name in self.tabs[
                    self.tab_index].players.keys():
                    self.tabs[self.tab_index].players.pop(name)
            # пустых участников нужно удалять по умолчанию, у них нет возможности выйти в следующий тур
            current_names = list(self.tabs[self.tab_index].players.keys())
            for empty_name in current_names:
                if empty_name.startswith('пусто'):
                    self.tabs[self.tab_index].players.pop(empty_name)
            self.tabs[self.tab_index].draggable_labels = self.tabs[self.tab_index].new_draggable_labels

            self.tabs[self.tab_index].tour += 1
        except Exception as e:
            alert(message=f'Возникла ошибка: {e}. Проверьте файл с оценками на корректность.')

    def final_mark(self, player):
        """
        Вычисление результирующей оценки по набору оценок участника и по правилам соревнований
        :param player: участник с известным набором оценок
        :return: вещественное число - результирующая оценка
        """
        if player.name.startswith('пусто'):
            return 0
        for i in range(len(player.marks)):
            if 'Total' in player.marks[i]:
                player.marks[i] = player.marks[i].drop('Total')
        judges = len(player.marks[0])
        if judges <= 3:
            tfirst = (sum(player.marks[0]) + sum(player.marks[1])) / judges
            tsecond = (sum(player.marks[2]) + sum(player.marks[3])) / judges
        else:
            tfirst = (sum(player.marks[0]) - max(player.marks[0]) - min(player.marks[0]) + sum(player.marks[1]) - max(
                player.marks[1]) - min(player.marks[1])) / (judges - 2)
            tsecond = (sum(player.marks[2]) - max(player.marks[2]) - min(player.marks[2]) + sum(player.marks[3]) - max(
                player.marks[3]) - min(player.marks[3])) / (judges - 2)
        return (tfirst + tsecond) / 2

    def connect_two_players(self, player1, player2):
        """
        Вычисление победителя из двух игроков и отрисовка на сетке
        :param player1: первый участник
        :param player2: второй участник
        :return: True при отработке без ошибок, иначе False
        """
        final_mark_1, final_mark_2 = player1.final_mark, player2.final_mark
        if player1.name.startswith('пусто'):
            final_mark_1 = 0
        if player2.name.startswith('пусто'):
            final_mark_2 = 0
        if (player1.marks is None and final_mark_1 == -1) or (player2.marks is None and final_mark_2 == -1):
            alert(f'Для участника {player1.name if player1.marks is None else player2.name} не найдены оценки '
                  f'для тура {self.tabs[self.tab_index].tour}. Проверьте заполнение таблицы и перезапустите программу.')
            return False

        if final_mark_1 == -1:
            final_mark_1 = self.final_mark(player1)
        if final_mark_2 == -1:
            final_mark_2 = self.final_mark(player2)
        if player2.name.startswith('пусто') and not player1.name.startswith('пусто'):
            player_won, player_lost = player1, player2
            self.tabs[self.tab_index].all_players_without_removal[player1.name].points += 1
        elif player1.name.startswith('пусто') and not player2.name.startswith('пусто'):
            player_won, player_lost = player2, player1
            self.tabs[self.tab_index].all_players_without_removal[player2.name].points += 1
        elif final_mark_1 > final_mark_2:
            player_won, player_lost = player1, player2
            self.tabs[self.tab_index].all_players_without_removal[player1.name].points += 1
            self.tabs[self.tab_index].all_players_without_removal[player2.name].points += 1
        else:
            player_won, player_lost = player2, player1
            self.tabs[self.tab_index].all_players_without_removal[player1.name].points += 1
            self.tabs[self.tab_index].all_players_without_removal[player2.name].points += 1

        max_x = max(player1.x, player2.x)
        if player_won is player1:
            color = Qt.GlobalColor.blue
            self.tabs[self.tab_index].draw_widget.drawLine(player1.x, player1.y,
                                                           max_x + self.tabs[self.tab_index].x_offset, player1.y, color)
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset, player1.y,
                                                           max_x + self.tabs[self.tab_index].x_offset,
                                                           (player1.y + player2.y) // 2, color)
            # на проигравшем ставим крест)
            color = Qt.GlobalColor.red
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset - 10,
                                                           player2.y - 10,
                                                           max_x + self.tabs[self.tab_index].x_offset + 10,
                                                           player2.y + 10, color)
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset - 10,
                                                           player2.y + 10,
                                                           max_x + self.tabs[self.tab_index].x_offset + 10,
                                                           player2.y - 10, color)
        else:
            color = Qt.GlobalColor.red
            self.tabs[self.tab_index].draw_widget.drawLine(player2.x, player2.y,
                                                           max_x + self.tabs[self.tab_index].x_offset, player2.y, color)
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset,
                                                           (player1.y + player2.y) // 2,
                                                           max_x + self.tabs[self.tab_index].x_offset,
                                                           player2.y, color)
            color = Qt.GlobalColor.blue
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset - 10,
                                                           player1.y - 10,
                                                           max_x + self.tabs[self.tab_index].x_offset + 10,
                                                           player1.y + 10, color)
            self.tabs[self.tab_index].draw_widget.drawLine(max_x + self.tabs[self.tab_index].x_offset - 10,
                                                           player1.y + 10,
                                                           max_x + self.tabs[self.tab_index].x_offset + 10,
                                                           player1.y - 10, color)
        # по неизвестной причине нужно выводить оценки в 10 раз меньше полученных
        # если участника не было, можно не выводить оценки
        try:
            divider = 100 if final_mark_1 > 100 else 10
            marks1_label = ClickableLabel(f'{final_mark_1 / divider:.3f}', self.tabs[self.tab_index].draw_widget)
            marks1_label.setStyleSheet(f"color: 'blue'; font-size: {self.marks_size_px}px")
            marks1_label.move(player1.x + self.tabs[self.tab_index].x_offset + 10, player1.y)
            marks1_label.show()
        except Exception:
            pass
        try:
            divider = 100 if final_mark_2 > 100 else 10
            marks2_label = ClickableLabel(f'{final_mark_2 / divider:.3f}', self.tabs[self.tab_index].draw_widget)
            marks2_label.setStyleSheet(f"color: 'red'; font-size: {self.marks_size_px}px")
            marks2_label.move(player1.x + self.tabs[self.tab_index].x_offset + 10, player2.y - 30)
            marks2_label.show()
        except Exception:
            pass
        reason_label = QLabel('CC', self.tabs[self.tab_index].draw_widget)
        if final_mark_2 == 0 or final_mark_1 == 0:
            reason_label.setText('СС')
        else:
            reason_label.setText('ФС')
        reason_label.setStyleSheet(f"color: 'black'; font-size: {self.marks_size_px}px")
        reason_label.move(player1.x + self.tabs[self.tab_index].x_offset + 210, player1.y)
        reason_label.show()

        try:
            if player1.name.startswith('пусто'):
                raise ValueError
            if player1.name not in self.tabs[self.tab_index].marks_labels.keys():
                self.tabs[self.tab_index].marks_labels[player1.name] = {self.tabs[self.tab_index].tour: marks1_label}
            else:
                self.tabs[self.tab_index].marks_labels[player1.name][self.tabs[self.tab_index].tour] = marks1_label
            self.tabs[self.tab_index].marks_labels[player1.name][self.tabs[self.tab_index].tour].clicked_signal.connect(
                lambda name=player1.name, tour=self.tabs[self.tab_index].tour: self.mark_click_function(name, tour))
        except Exception:
            pass
        try:
            if player2.name.startswith('пусто'):
                raise ValueError
            if player2.name not in self.tabs[self.tab_index].marks_labels.keys():
                self.tabs[self.tab_index].marks_labels[player2.name] = {self.tabs[self.tab_index].tour: marks2_label}
            else:
                self.tabs[self.tab_index].marks_labels[player2.name][self.tabs[self.tab_index].tour] = marks2_label
            self.tabs[self.tab_index].marks_labels[player2.name][self.tabs[self.tab_index].tour].clicked_signal.connect(
                lambda name=player2.name, tour=self.tabs[self.tab_index].tour: self.mark_click_function(name, tour))
        except Exception:
            pass

        self.tabs[self.tab_index].players[player_won.name].x = max_x + self.tabs[self.tab_index].x_offset
        self.tabs[self.tab_index].players[player_won.name].y = (player1.y + player2.y) // 2

        # Оценки на следующий тур
        # Ищется {tour + 1}-е вхождение участника в таблицу результатов
        cnt = 0
        try:
            for i in range(0, len(self.tabs[self.tab_index].df), 4):
                if self.tabs[self.tab_index].df.iloc[i][2] == player_won.name:
                    cnt += 1
                    if cnt == self.tabs[self.tab_index].tour + 1:
                        temp_marks = []
                        for row in range(i, i + 4):
                            temp_marks.append(
                                self.tabs[self.tab_index].df.iloc[row][3:])
                        self.tabs[self.tab_index].players[player_won.name].marks = temp_marks
                        break
            else:
                self.tabs[self.tab_index].players[player_won.name].marks = None
        except Exception:
            self.tabs[self.tab_index].players[player_won.name].marks = None

        self.tabs[self.tab_index].players_to_remove.append(player_lost.name)
        # Со второго тура имена участников показывать не нужно
        label = DraggableLabel(text=player_won.name, parent=self.tabs[self.tab_index].draw_widget,
                               font_size=self.font_size_px, show_label=False)
        label.move(self.tabs[self.tab_index].players[player_won.name].x,
                   self.tabs[self.tab_index].players[player_won.name].y)
        label.show()
        self.tabs[self.tab_index].new_draggable_labels.append(label)

        # словарь для определения 3 места, заполняется в полуфинале
        if len(self.tabs[self.tab_index].players) == 4:
            self.tabs[self.tab_index].finalists[player_won.name] = player_lost.name

        # определение 3 места
        elif len(self.tabs[self.tab_index].players) == 2:
            self.tabs[self.tab_index].fourth_place = self.tabs[self.tab_index].finalists[player_lost.name]
            self.tabs[self.tab_index].third_place = self.tabs[self.tab_index].finalists[player_won.name]
            self.tabs[self.tab_index].second_place = player_lost.name
            self.tabs[self.tab_index].first_place = player_won.name

            self.tabs[self.tab_index].all_players_without_removal[self.tabs[self.tab_index].fourth_place].points += 20
            self.tabs[self.tab_index].all_players_without_removal[self.tabs[self.tab_index].third_place].points += 20
            self.tabs[self.tab_index].all_players_without_removal[self.tabs[self.tab_index].second_place].points += 50
            self.tabs[self.tab_index].all_players_without_removal[self.tabs[self.tab_index].first_place].points += 120

            csv_data = []
            for name, item in self.tabs[self.tab_index].all_players_without_removal.items():
                if not name.startswith('пусто'):
                    csv_data.append([name, item.region, item.points])
            source_file = self.tabs[self.tab_index].file_path
            source_dir = os.path.dirname(source_file)
            filename = os.path.basename(source_file).replace('.csv', '') + '-points.csv'

            with open(os.path.join(source_dir, filename), 'w', newline='') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerows(csv_data)
            print('Баллы всех участников записаны в файл.')
            # дорисовка последней линии
            color = Qt.GlobalColor.red if player_won is player2 else Qt.GlobalColor.blue
            self.tabs[self.tab_index].draw_widget.drawLine(
                max_x + self.tabs[self.tab_index].x_offset,
                player_won.y,
                max_x + self.tabs[self.tab_index].x_offset * 2,
                player_won.y,
                color
            )
        return True

    def mark_click_function(self, player, tour):
        """
        Поиск и замена в таблице оценок списка оценок участника player, которая встретилась (tour)-й раз,
        после чего изменённые оценки сохраняются в excel и таблица результатов перестраивается
        :param player: участник
        :param tour: номер тура
        """
        cnt = 0
        for i in range(len(self.tabs[self.tab_index].df)):
            if self.tabs[self.tab_index].df.iloc[i][2] == player:
                cnt += 1
                if cnt == tour:
                    marks_list = []
                    try:
                        for row in range(i, i + 4):
                            if str(self.tabs[self.tab_index].df.iloc[row][5]) not in ('nan', ''):
                                alert('Данная оценка является итоговой и не подлежит редактированию.')
                                return
                            # Первые 6 столбцов - Id, Number, Player, регион, Estimates, Total
                            marks_list.append(list(self.tabs[self.tab_index].df.iloc[row][6:]))
                    except Exception:
                        judges_number = len([j for j in self.tabs[self.tab_index].df.keys() if j.startswith('Point')])
                        marks_list = [[None] * judges_number] * 4
                    self.tabs[self.tab_index].marks_window = MarksEditWindow(
                        marks=marks_list, player=player, tour=tour
                    )
                    self.tabs[self.tab_index].marks_window.show()
                    self.tabs[self.tab_index].marks_window.marks_signal.connect(
                        lambda marks: self.update_marks_in_excel(
                            marks_list=marks, row_number=i + 1
                        ))
                    break
        else:
            alert(message=f'Участник {player} записан в файле менее {tour} раз.')

    def re_read_marks(self):
        """
        Перечитывание оценок перед каждым новым туром
        :return: -
        """
        self.tab_index = self.tab_widget.currentIndex()
        try:
            self.tabs[self.tab_index].df = pd.read_csv(self.tabs[self.tab_index].file_path, sep=';', decimal=',',
                                                       index_col=False)
        except FileNotFoundError:
            print(f"Файл {self.tabs[self.tab_index].file_path} не найден!")
            return
        players = self.tabs[self.tab_index].players
        tour = self.tabs[self.tab_index].tour
        for player in players:
            try:
                name = players[player].name
                # в файле может быть не прописана суммарная оценка для очередного тура,
                # тогда по этому значению будет понятно, что необходимо её высчитывать по судейским оценкам
                self.tabs[self.tab_index].players[name].final_mark = -1
                cnt = 0
                for index in range(0, len(self.tabs[self.tab_index].df), 4):
                    if self.tabs[self.tab_index].df.iloc[index][2] == name:
                        cnt += 1
                        if cnt == tour:
                            marks = []
                            for row in range(index, index + 4):
                                try:
                                    marks.append(
                                        self.tabs[self.tab_index].df.iloc[row][3:])
                                # Участник пришёл, но не вышел на тур
                                except IndexError:
                                    self.tabs[self.tab_index].players[name].final_mark = 0
                                marks[-1] = marks[-1].drop('Регион')
                                marks[-1] = marks[-1].drop('Estimates')
                                if 'Total' in self.tabs[self.tab_index].df.keys() and str(
                                        marks[-1]['Total']).lower() != 'nan':
                                    self.tabs[self.tab_index].players[name].final_mark = marks[-1]['Total']
                                    break
                            self.tabs[self.tab_index].players[name].marks = marks
                            break
            except Exception:
                self.tabs[self.tab_index].players[name].marks = None

    def update_marks_in_excel(self, marks_list=None, row_number=1):
        """
        Заменяет оценки в файле Excel при обновлении по щелчку
        :param marks_list: список оценок
        :param row_number: номер строки, с которой нужно начинать замену
        """
        self.tab_index = self.tab_widget.currentIndex()
        # workbook = openpyxl.load_workbook(self.tabs[self.tab_index].file_path)
        # worksheet = workbook.worksheets[0]
        with open(self.tabs[self.tab_index].file_path, 'r', encoding='utf-8') as file:
            reader = csv.reader(file, delimiter=';')
            data = list(reader)

        column_start = 3  # Начинаем запись со второго столбца

        # Проходим по каждому значению и записываем в соответствующую ячейку
        for row in range(len(marks_list)):
            for idx, value in enumerate(marks_list[row]):
                column = column_start + idx
                data[row + row_number][column] = value

        # Сохраняем изменения обратно в тот же файл
        # workbook.save(self.tabs[self.tab_index].file_path)
        with open(self.tabs[self.tab_index].file_path, 'w', newline='', encoding='utf-8') as file:
            writer = csv.writer(file, delimiter=';')
            writer.writerows(data)

        self.init_players()
        saved_tour_number = self.tabs[self.tab_index].tour
        self.tabs[self.tab_index].tour = 1
        for i in range(saved_tour_number - 1):
            self.count_round()

        alert('Оценки успешно сохранены.')

    def write_participants_into_table(self):
        """
        Запись участников текущего тура в таблицу
        :return: -
        """
        self.tab_index = self.tab_widget.currentIndex()
        filename = self.tabs[self.tab_index].file_path
        participants = list(self.tabs[self.tab_index].players.keys())
        tour = self.tabs[self.tab_index].tour
        if check_if_any_participant_exist_in_tour(filename, participants, tour):
            return
        # workbook = openpyxl.load_workbook(filename)
        # worksheet = workbook.worksheets[0]
        with open(self.tabs[self.tab_index].file_path, 'r', encoding='utf-8') as file:
            reader = csv.reader(file, delimiter=';')
            data = list(reader)

        # первая свободная строка
        row = len(data)

        for i in range(len(participants)):
            participant = participants[i]
            # поиск номера и региона текущего участника
            number = None
            region = None
            try:
                for line in data:
                    if line[2] == participant:
                        if line[1]:
                            number = line[1]
                        if line[3]:
                            region = line[3]
            except:
                pass
            for j in range(4):
                data.append([0] + [''] * 8)
                # повтор участников 4 раза
                data[row + i * 4 + j][2] = participant
                if number:
                    data[row + i * 4 + j][1] = number
                if region:
                    data[row + i * 4 + j][3] = region
            # worksheet[f'B{row + i * 4}'].value = participants[i]
        # workbook.save(filename)
        with open(self.tabs[self.tab_index].file_path, 'w', newline='', encoding='utf-8') as file:
            writer = csv.writer(file, delimiter=';')
            writer.writerows(data)
        alert(message=f'Участники тура {tour} записаны в файл.')

    def render(self):
        """
        Сохранение результатов турнира в pdf
        :return: -
        """
        self.tab_index = self.tab_widget.currentIndex()
        tournament_not_finished = False
        if len(self.tabs[self.tab_index].players) > 1:
            if not question('Турнир не завершён. Экспортировать pdf?'):
                return
            else:
                tournament_not_finished = True
        # при экспорте нужно убрать надписи "пусто"
        for label in self.tabs[self.tab_index].draw_widget.children():
            if type(label) is DraggableLabel and label.text().startswith('пусто'):
                label.setText('')
        imageVar = self.tabs[self.tab_index].draw_widget.grab(
            QRect(1, 1, self.tabs[self.tab_index].draw_widget.width - 2,
                  self.tabs[self.tab_index].draw_widget.height - 2))
        filename, ok = QFileDialog.getSaveFileName(
            parent=self, caption='Укажите имя файла для сохранения',
            directory=os.getenv('HOME'), filter='PDF files (*.pdf)'
        )
        # filename = subprocess.run(
        #     f"kdialog --getsavefilename --title='Укажите имя файла для сохранения' "
        #     f"{self.working_folder} 'PDF files(*.pdf)'",
        #     shell=True, capture_output=True
        # ).stdout.decode().strip()
        if ok:
            if not filename.endswith('.pdf'):
                filename += '.pdf'

            height_increase = 0
            c2_offset = 100 if competition_name2 else 0
            new_image_height = image_height
            if self.participants_number == 32:
                height_increase = c2_offset + 500
                new_image_height = image_height + height_increase
            image_to_save = QPixmap(image_width, new_image_height)
            image_to_save.fill(QColor('white'))
            painter = QPainter(image_to_save)
            # 400 - чтобы сверху влезли заголовки

            painter.drawPixmap(0, 400 + c2_offset, imageVar)
            font = QFont("Arial", self.header_font_size_px)
            painter.setFont(font)

            painter.drawText(QPoint(self.get_x_for_centered_text(header, font), 100), header)
            painter.drawText(QPoint(self.get_x_for_centered_text(competition_name, font), 200), competition_name)
            if competition_name2:
                painter.drawText(QPoint(self.get_x_for_centered_text(competition_name2, font), 200 + c2_offset),
                                 competition_name2)
            painter.drawText(QPoint(self.get_x_for_centered_text(date_and_place, font, True), 300 + c2_offset),
                             date_and_place)
            painter.drawText(QPoint(100, 400 + c2_offset), competition_program)

            y = 3000
            if self.participants_number >= 32:
                y = c2_offset + imageVar.height()
            painter.setFont(QFont("Arial", self.marks_size_px))
            for line in comment:
                painter.drawText(QPoint(100, y), line)
                y += 50
            painter.setFont(QFont("Arial", self.font_size_px))
            painter.drawText(QPoint(100, y + 50), f'Главный судья ___________{referee}')
            painter.drawText(QPoint(100, y + 150), f'Главный секретарь _____________{secretary}')

            painter.drawText(1800, y - 600,
                             f'I. {self.tabs[self.tab_index].first_place if not tournament_not_finished else ""}')
            painter.drawText(1800, y - 500,
                             f'II. {self.tabs[self.tab_index].second_place if not tournament_not_finished else ""}')
            painter.drawText(1800, y - 400,
                             f'III. {self.tabs[self.tab_index].third_place if not tournament_not_finished else ""}')
            painter.drawText(1800, y - 300,
                             f'III. {self.tabs[self.tab_index].fourth_place if not tournament_not_finished else ""}')

            painter.end()

            if save_pixmap_to_pdf(image_to_save, filename, height_increase=height_increase):
                alert(f'Сохранено в {filename}.')
            else:
                alert("Ошибка при сохранении PDF!")

    def get_x_for_centered_text(self, text, font, right=False):
        """
        Вычисляет x начала текста для выравнивания по центру
        :param text: текст для выравнивания
        :param font: шрифт
        :param right: если True, то выравнивание не по центру, а по правому краю
        :return: целое число - горизонтальная координата начала текста
        """
        try:
            fm = QFontMetrics(font)
            width = fm.horizontalAdvance(text)
            if right:
                return image_width - width - 100
            return (image_width - width) // 2
        except Exception as e:
            print(f'Не удалось выровнять текст по центру. Ошибка: {e}')
            return 100

    def init_players(self):
        self.participants_number = 2
        self.marks_size_px = marks_size_px
        self.font_size_px = font_size_px
        self.header_font_size_px = header_font_size_px
        try:
            self.tabs[self.tab_index].df = pd.read_csv(self.tabs[self.tab_index].file_path, sep=';', decimal=',',
                                                       index_col=False)

        except FileNotFoundError:
            print(f"Файл {self.tabs[self.tab_index].file_path} не найден!")

        self.tabs[self.tab_index].players = dict()
        self.tabs[self.tab_index].all_players_without_removal = dict()
        self.tabs[self.tab_index].draggable_labels = []
        last_y = self.tabs[self.tab_index].y_offset

        painter = QPainter(self.tabs[self.tab_index].draw_widget.image)
        painter.setBrush(QBrush(Qt.GlobalColor.white))
        painter.drawRect(0, 0, self.tabs[self.tab_index].draw_widget.image.width(),
                         self.tabs[self.tab_index].draw_widget.image.height())
        painter.end()

        for child in self.tabs[self.tab_index].draw_widget.children():
            if type(child) in (ClickableLabel, DraggableLabel):
                child.setParent(None)

        # подсчёт количества участников для жеребъёвки 1-8, 2-7 и т.д
        players = set([str(i) for i in self.tabs[self.tab_index].df['Player']])
        if 'nan' in players:
            players.remove('nan')
        if '' in players:
            players.remove('')
        participants_number = len(players)
        # for i in range(len(self.tabs[self.tab_index].df)):
        #     if str(self.tabs[self.tab_index].df.iloc[i][0]) != 'nan':
        #         participants_number += 1

        participants_number = 2 ** ceil(log(participants_number, 2))

        # Закономерность в порядке участников определить не удалось.
        # Она переписана из протоколов прошедших соревнований.
        # indexes_order = [i for i in range(0, participants_number // 2, 2)]
        # indexes_order.extend([i for i in range(participants_number // 2 - 1, 0, -2)])
        if participants_number == 2:
            indexes_order = [0]
        elif participants_number == 4:
            indexes_order = [0, 2]
        elif participants_number == 8:
            indexes_order = [0, 4, 2, 6]
        elif participants_number == 16:
            indexes_order = [0, 8, 4, 12, 2, 10, 6, 14]
        elif participants_number == 32:
            indexes_order = [0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 30]
            self.font_size_px = min(font_size_px, 25)
            self.marks_size_px = min(marks_size_px, 20)
            self.tabs[self.tab_index].y_offset = min(self.tabs[self.tab_index].y_offset, 80)
        elif participants_number == 64:
            indexes_order = [0, 31, 15, 16, 8, 23, 7, 24, 4, 27, 11, 20, 12, 19, 3, 28, 2, 29, 13, 18, 10, 21, 5, 26, 6,
                             25, 9, 22, 14, 17, 30, 62]
            self.font_size_px = min(font_size_px, 25)
            self.marks_size_px = min(marks_size_px, 20)
            self.tabs[self.tab_index].y_offset = min(self.tabs[self.tab_index].y_offset, 80)
        else:
            alert(message='Количество участников слишком велико.')
            sys.exit(0)

        self.participants_number = participants_number
        last_index_for_first_tour = len(players) * 4 - 1
        empty_number = 1
        for i in indexes_order:
            for index in (i, participants_number - i - 1):
                index *= 4
                try:
                    # в таблице сразу могут быть результаты 2 и следующих туров, они не должны считываться при построении диаграммы
                    if str(self.tabs[self.tab_index].df.iloc[index][2]) not in (
                    'nan', '') and index <= last_index_for_first_tour:
                        # порядок столбцов: ID	Number	Player	Регион Estimates Total (отдельные оценки)
                        player_name = self.tabs[self.tab_index].df.iloc[index][2]
                    else:
                        player_name = f'пусто{empty_number}'
                        empty_number += 1
                except Exception:
                    player_name = f'пусто{empty_number}'
                    empty_number += 1
                marks = []
                final_mark = -1
                region = ''
                for row in range(index, index + 4):
                    if not player_name.startswith('пусто'):
                        try:
                            marks.append(
                                self.tabs[self.tab_index].df.iloc[row][3:])
                        # Участник пришёл на соревнования, но не вышел
                        except IndexError:
                            final_mark = 0
                            break
                        region = marks[-1]['Регион']
                        marks[-1] = marks[-1].drop('Регион')
                        marks[-1] = marks[-1].drop('Estimates')
                        if 'Total' in self.tabs[self.tab_index].df.keys() and str(
                                self.tabs[self.tab_index].df['Total'][index]) != 'nan':
                            final_mark = marks[-1]['Total']
                            break
                        elif 'Total' in self.tabs[self.tab_index].df.keys():
                            marks[-1] = marks[-1].drop('Total')
                        # если оценки не удалось считать из таблицы
                        for i in marks[-1]:
                            if str(i) == 'nan':
                                marks = []
                                break
                    if not marks:
                        marks = None
                        break
                # Добавляются только участники, которые встретились первый раз
                if player_name not in self.tabs[self.tab_index].players.keys() or player_name.startswith('пусто'):
                    self.tabs[self.tab_index].players[player_name] = Player(name=player_name,
                                                                            region=region,
                                                                            x=self.tabs[self.tab_index].x_offset,
                                                                            y=last_y, marks=marks,
                                                                            final_mark=final_mark)
                    self.tabs[self.tab_index].all_players_without_removal[player_name] = Player(name=player_name,
                                                                                                points=0,
                                                                                                region=region)
                    label = DraggableLabel(
                        f'{int(self.tabs[self.tab_index].df.iloc[index][1])}. {self.tabs[self.tab_index].df.iloc[index][2]}' if not player_name.startswith(
                            'пусто') else 'пусто',
                        parent=self.tabs[self.tab_index].draw_widget, font_size=self.font_size_px)
                    label.move(self.tabs[self.tab_index].x_offset, last_y)
                    label.show()
                    # Обычно регион - последний столбец, но иногда там может быть средняя оценка
                    # region_index = -1 if self.tabs[self.tab_index].df.axes[-1][-1].strip() == 'Регион' else -2
                    region_label = QLabel(
                        self.tabs[self.tab_index].df.iloc[index]['Регион'] if not player_name.startswith(
                            'пусто') else '',
                        self.tabs[self.tab_index].draw_widget)
                    region_label.setStyleSheet(f"color: black; font-size: {self.marks_size_px}px")
                    region_label.move(self.tabs[self.tab_index].x_offset, last_y + y_offset // 2)
                    region_label.show()
                    self.tabs[self.tab_index].draggable_labels.append(label)
                    last_y += self.tabs[self.tab_index].y_offset

        # Прорисовка всей сетки заранее
        for label in self.tabs[self.tab_index].draggable_labels:
            self.tabs[self.tab_index].x_offset = max(self.tabs[self.tab_index].x_offset, label.width())
        x_offset_for_init = self.tabs[self.tab_index].x_offset
        players_for_init_grid = [
            Player(value.name, value.x, value.y, value.marks) for key, value in
            self.tabs[self.tab_index].players.items()
        ]
        while len(players_for_init_grid) > 1:
            new_players = []
            for i in range(0, len(players_for_init_grid), 2):
                player1, player2 = players_for_init_grid[i], players_for_init_grid[i + 1]
                max_x = max(player1.x, player2.x)
                self.tabs[self.tab_index].draw_widget.drawLine(player1.x, player1.y, max_x + x_offset_for_init,
                                                               player1.y)
                self.tabs[self.tab_index].draw_widget.drawLine(player2.x, player2.y, max_x + x_offset_for_init,
                                                               player2.y)
                self.tabs[self.tab_index].draw_widget.drawLine(max_x + x_offset_for_init, player1.y,
                                                               max_x + x_offset_for_init, player2.y)
                player1.x = max_x + x_offset_for_init
                player1.y = (player1.y + player2.y) // 2
                new_players.append(player1)
            players_for_init_grid = new_players.copy()
        self.tabs[self.tab_index].draw_widget.drawLine(
            players_for_init_grid[0].x, players_for_init_grid[0].y,
            players_for_init_grid[0].x + x_offset_for_init, players_for_init_grid[0].y
        )

    def swap_dragged(self, label1, label2):
        """
        Меняет местами двух участников одного тура при перетаскивании
        :param label1: DraggableLabel первого участника
        :param label2: DraggableLabel второго участника
        """
        self.tab_index = self.tab_widget.currentIndex()
        try:
            ind1, ind2 = self.tabs[self.tab_index].draggable_labels.index(label1), self.tabs[
                self.tab_index].draggable_labels.index(label2)
            name1, name2 = self.tabs[self.tab_index].draggable_labels[ind1].text().split('. ')[-1], \
                self.tabs[self.tab_index].draggable_labels[ind2].text().split('. ')[-1]
            self.tabs[self.tab_index].players[name1].x, self.tabs[self.tab_index].players[name2].x = \
                self.tabs[self.tab_index].players[name2].x, self.tabs[self.tab_index].players[name1].x
            self.tabs[self.tab_index].players[name1].y, self.tabs[self.tab_index].players[name2].y = \
                self.tabs[self.tab_index].players[name2].y, self.tabs[self.tab_index].players[name1].y
        except Exception as e:
            print(f'Возникла ошибка {e}')

    def open_settings(self):
        """
        Открытие окна настроек
        """
        self.settings_window = SettingsWindow()
        self.settings_window.show()

    def get_file_path(self):
        """Быстрый метод открытия файла"""
        # return subprocess.run(
        #     "kdialog --getopenfilename --title='Выберите файл с данными соревнования' ~ 'CSV Files(*.csv)'",
        #     shell=True, capture_output=True
        # ).stdout.decode().strip()
        filename, ok = QFileDialog.getOpenFileName(
            parent=self, caption='Выбарите файл с данными соревнования', filter='CSV Files(*.csv)',
            directory=self.working_folder
        )
        return filename if ok else None

    def create_tab(self):
        """
        Создание новой вкладки
        :return: -
        """
        control_widget = QWidget()

        self.tabs.append(control_widget)
        self.tab_widget.addTab(control_widget, str(len(self.tabs)))
        self.tab_index = len(self.tabs) - 1

        layout = QVBoxLayout()

        control_widget.draggable_labels = None
        control_widget.players = None

        # Создаем виджет для рисования
        control_widget.draw_widget = DrawableImage(image_width, image_height)

        # control_widget.file_path, _ = QFileDialog.getOpenFileName(
        #     parent=self, caption='Выберите файл с данными соревнования', directory=self.working_folder,
        #     filter='CSV files(*.csv)', options=options
        # )
        control_widget.file_path = self.get_file_path()

        if not control_widget.file_path:
            control_widget.file_path = 'cup.csv'
            if not os.path.isfile(os.path.join(self.working_folder, control_widget.file_path)):
                alert(message='Не выбран файл с данными.')
                return

        control_widget.tour = 1
        control_widget.x_offset = x_offset
        control_widget.y_offset = y_offset  # поля
        # словарь всех оценок. Данные хранятся в формате
        # {участник: {1: метка, 2: метка}}, где 1, 2 - номера туров; метка - label с оценками на виджете
        control_widget.marks_labels = dict()

        # Словарь для определения 3 места - это игрок, проигравший победителю.
        # Словарь заполняется в момент определения финалистов,
        # по ключу финалист будет храниться проигравший ему полуфиналист.
        control_widget.finalists = dict()
        control_widget.first_place = None
        control_widget.second_place = None
        control_widget.third_place = None
        control_widget.fourth_place = None

        try:
            self.init_players()
        except Exception as e:
            alert(message=f'Ошибка чтения файла {control_widget.file_path}: {e}. Программа завершит работу.')
            sys.exit(0)

        scroll_area_for_image = QScrollArea()
        scroll_area_for_image.setWidget(control_widget.draw_widget)
        layout.addWidget(scroll_area_for_image)

        buttons_layout = QVBoxLayout()

        count_button = QPushButton('Посчитать результаты тура')
        count_button.clicked.connect(self.count_round)
        buttons_layout.addWidget(count_button)

        write_participants_button = QPushButton('Запись участников в файл')
        write_participants_button.clicked.connect(self.write_participants_into_table)
        buttons_layout.addWidget(write_participants_button)

        render_button = QPushButton('Сохранить изображение')
        render_button.clicked.connect(self.render)
        buttons_layout.addWidget(render_button)

        layout.addLayout(buttons_layout)

        control_widget.setLayout(layout)
        self.tab_widget.setCurrentIndex(self.tab_index)

    def remove_tab(self):
        """
        Удаление вкладки
        :return: -
        """
        self.tab_index = self.tab_widget.currentIndex()
        dlg = QMessageBox()
        dlg.setWindowTitle('Внимание!')
        dlg.setText(f'Вы действительно хотите удалить соревнование {self.tab_widget.tabText(self.tab_index)}?')
        dlg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)
        if dlg.exec() != QMessageBox.StandardButton.Ok:
            return
        self.tab_widget.removeTab(self.tab_index)
        self.tabs.remove(self.tabs[self.tab_index])
        self.tab_index = 0
        self.tab_widget.setCurrentIndex(0)

    def count_region_function(self):
        filenames, ok = QFileDialog.getOpenFileNames(
            parent=self,
            caption='Выберите таблицы для сведения',
            directory=self.working_folder,
            filter='CSV files(*.csv)'
        )
        if not ok or not filenames:
            return
        # print(filenames)
        regions_pts = dict()
        for file in filenames:
            with open(file, 'r', encoding='utf-8') as inp:
                csv_reader = csv.reader(inp)
                for row in csv_reader:
                    try:
                        if not row[1].strip():
                            continue
                        if row[1] in regions_pts:
                            regions_pts[row[1]] += int(row[2])
                        else:
                            regions_pts[row[1]] = int(row[2])
                    except Exception:
                        print(f'Файл {file} повреждён, строка {row} некорректна.')

        csv_data = []
        for region, pts in regions_pts.items():
            csv_data.append([region, pts])
        if not csv_data:
            alert('Все указанные файлы повреждены или имеют некорректный формат.')
            return
        csv_data.sort(key=lambda x: -x[1])
        output_filename = f"{datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}-regions.csv"
        with open(
                os.path.join(
                    os.path.dirname(file),
                    output_filename
                ), 'w', newline=''
        ) as csvfile:
            writer = csv.writer(csvfile)
            writer.writerows(csv_data)
        alert(f'Сумма по регионам записана в файл {output_filename}.')

    def __init__(self):
        super().__init__()
        self.working_folder = os.getcwd()

        self.setWindowTitle(f"Турнирная сетка {version}")
        self.setGeometry(100, 100, 800, 650)

        self.tab_widget = QTabWidget()
        self.tabs = []
        self.tab_index = 0
        self.tab_widget.blockSignals(True)

        # Создание вкладки
        self.create_tab()
        self.setCentralWidget(self.tab_widget)
        self.tab_widget.blockSignals(False)

        window_menu = QMenuBar()
        self.setMenuBar(window_menu)

        file_menu = QMenu('Файл', self)
        window_menu.addMenu(file_menu)

        settings_option = QAction('Настройки', self)
        settings_option.triggered.connect(self.open_settings)
        file_menu.addAction(settings_option)

        add_tab_option = QAction('Добавить соревнование', self)
        add_tab_option.triggered.connect(self.create_tab)
        file_menu.addAction(add_tab_option)

        remove_tab_option = QAction('Удалить текущее соревнование', self)
        remove_tab_option.triggered.connect(self.remove_tab)
        file_menu.addAction(remove_tab_option)

        count_region_option = QAction('Сумма по регионам...', self)
        count_region_option.triggered.connect(self.count_region_function)
        file_menu.addAction(count_region_option)

        exit_option = QAction('Выход', self)
        exit_option.triggered.connect(lambda: self.close())
        file_menu.addAction(exit_option)

        self.setWindowIcon(QIcon.fromTheme('sport_section'))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
