#!/usr/bin/python3
import csv
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

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: -
        """
        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)):
            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.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
        elif player1.name.startswith('пусто') and not player2.name.startswith('пусто'):
            player_won, player_lost = player2, player1
        elif final_mark_1 > final_mark_2:
            player_won, player_lost = player1, player2
        else:
            player_won, player_lost = player2, player1

        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 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 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(len(self.tabs[self.tab_index].df)):
                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
            # дорисовка последней линии
            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=',')
        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(len(self.tabs[self.tab_index].df)):
                    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()
        if len(self.tabs[self.tab_index].players) > 1:
            alert('Турнир не завершён.')
            return
        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=None, caption='Укажите имя файла для сохранения',
            directory=os.getenv('HOME'), filter='PDF files (*.pdf)'
        )
        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}')
            painter.drawText(1800, y - 500, f'II. {self.tabs[self.tab_index].second_place}')
            painter.drawText(1800, y - 400, f'III. {self.tabs[self.tab_index].third_place}')
            painter.drawText(1800, y - 300, f'III. {self.tabs[self.tab_index].fourth_place}')

            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=',')

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

        self.tabs[self.tab_index].players = 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
        empty_number = 1
        for i in indexes_order:
            for index in (i, participants_number - i - 1):
                index *= 4
                try:
                    if str(self.tabs[self.tab_index].df.iloc[index][2]) not in ('nan', ''):
                        # порядок столбцов: 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
                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
                        marks[-1] = marks[-1].drop('Регион')
                        marks[-1] = marks[-1].drop('Estimates')
                        if 'Total' in self.tabs[self.tab_index].df.keys():
                            final_mark = marks[-1]['Total']
                            break
                    else:
                        marks.append(0)
                # Добавляются только участники, которые встретились первый раз
                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,
                                                                            x=self.tabs[self.tab_index].x_offset,
                                                                            y=last_y, marks=marks,
                                                                            final_mark=final_mark)
                    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 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=None, caption='Выберите файл с данными соревнования', directory=os.getcwd(),
            filter='CSV files(*.csv)'
        )
        if not control_widget.file_path:
            control_widget.file_path = 'cup.csv'
            if not os.path.isfile(os.path.join(os.getcwd(), 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 __init__(self):
        super().__init__()

        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)

        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())
