#  Copyright (c) 2025  Jeffrey Hsu - JITToolBox
#  #
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  #
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  #
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import random
from typing import Optional

from openpyxl.reader.excel import load_workbook
from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet


class PickerStudent:
    total_time: int = 0

    def __init__(self, name: str, so: str, position: int, scores: list[int]):
        self._name = name
        self._so = str(so)
        self._position = position
        self._scores = scores
        self._modify = False
        self._scores = []
        self._scores.extend(scores)
        self._scores_pointer = -1

        self.init_score()

    def init_score(self):
        self._scores += [0] * (self.total_time - len(self._scores))

        for idx, val in enumerate(self._scores):
            if val is None:
                self._scores[idx] = 0
            if self._scores[idx] == 0:
                self._scores_pointer = idx
                break

    def append_score(self, score: int) -> int:
        if self._scores_pointer > self.total_time or self._scores_pointer < 0:
            raise IndexError
        self._scores[self._scores_pointer] = score
        self._scores_pointer += 1
        self.modified()
        return self._scores_pointer - 1

    def change_score(self, new_score: int, index: int):
        if index < 0 or index > self.total_time:
            raise IndexError
        self._scores[index] = new_score
        self.modified()

    @staticmethod
    def pick(student: list['PickerStudent']) -> Optional['PickerStudent']:
        filtered = [item for item in student if item.weight != 100 and item.weight > 0]
        if not filtered:
            return None

        # 计算倒数权重
        weights = [1 / item.weight for item in filtered]
        total = sum(weights)
        r = random.uniform(0, total)

        cumulative = 0
        for item, w in zip(filtered, weights):
            cumulative += w
            if r <= cumulative:
                return item
        return None

    @classmethod
    def set_total_time(cls, total_time: int):
        cls.total_time = total_time

    @property
    def position(self) -> int:
        return self._position

    @property
    def scores(self) -> list[int]:
        return self._scores

    @property
    def modify(self) -> bool:
        return self._modify

    @property
    def weight(self) -> int:
        return int(sum(1 for x in self._scores if x != 0) / self.total_time * 100)

    @property
    def name(self) -> str:
        return self._name

    @property
    def so(self) -> str:
        return self._so

    def saved(self):
        self._modify = False

    def modified(self):
        self._modify = True

    def __str__(self):
        return f"PickerStudent {self._name} at {self.position}, with scores: {','.join(str(x) for x in self._scores if x)}"

    def __repr__(self):
        return self.__str__()


class PickerExcel:
    wb: Optional[Workbook]
    ws: Optional[Worksheet]
    path: str = ''
    max_time_position = 'M1'
    start_row = 5

    def __init__(self, path: str):
        self.open(path)

    @classmethod
    def open(cls, path: str):
        cls.path = path
        cls.wb = load_workbook(path, keep_vba=True)
        cls.ws = cls.wb.active
        PickerStudent.set_total_time(cls.ws[cls.max_time_position].value)

    @classmethod
    def read_student(cls, path: Optional[str] = None) -> list[PickerStudent]:
        if path:
            cls.open(path)

        if cls.wb and cls.ws:
            ret = []
            for index, row in enumerate(cls.ws.iter_rows(
                    min_row=cls.start_row,
                    max_col=4 + cls.ws[cls.max_time_position].value,
                    values_only=True)
            ):
                if (name := row[2]) is None:
                    break
                ret.append(PickerStudent(name, row[1], cls.start_row + index, row[4:]))
            return ret
        raise Exception('No Workbook or Worksheet')

    @classmethod
    def read_total_time(cls, path: Optional[str] = None) -> int:
        if path:
            cls.open(path)

        if cls.wb and cls.ws:
            val = cls.ws[cls.max_time_position].value
            if isinstance(val, int):
                return val
            raise Exception(f'总次数读取错误，期待类型 {type(1)} 但实际为 {type(val)}\n'
                            f'可能的解决方法：\n'
                            f'1、确保总次数位于 \'{cls.max_time_position}\' 单元格；\n'
                            f'2、确保选择了正确的 Excel 文件。')
        raise Exception('No Workbook or Worksheet')

    @classmethod
    def save_total_time(cls, path: Optional[str] = None, value: Optional[int] = None) -> None:
        if path:
            cls.open(path)

        if cls.wb and cls.ws and value:
            cls.ws[cls.max_time_position].value = value
            cls.save()

    @classmethod
    def write_back(cls, student: PickerStudent):
        start_col = 5  # E列
        row = student.position

        for i, val in enumerate(student.scores):
            cls.ws.cell(row=row, column=start_col + i, value=val if val != 0 else '')

        student.saved()
        cls.save()

    @classmethod
    def write_all(cls, students: list[PickerStudent]):
        for student in students:
            if student.modify:
                cls.write_back(student)

    @classmethod
    def save(cls):
        if cls.ws:
            cls.wb.save(cls.path)


if __name__ == '__main__':
    ...
