import re
import traceback
from ast import literal_eval
from gc import collect
from math import floor, ceil
from pathlib import Path
from traceback import format_exc

import soxr
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication
from numpy import array
from overrides import overrides
from pandas import read_csv, DataFrame
from yaml import dump, load, FullLoader
import numpy as np
from func.utils.ConfigParams import Filename, Params
from func.utils.PublicFunc import PublicFunc
from func.utils.Constants import Constants
from func.utils.Result import Result
from numpy import float32
from ui.MainWindow.MainWindow_cut_PAIR_FILE import Ui_MainWindow_cut_PAIR_FILE


Config = {

}

ButtonState = {
    "Default": {
        "checkBox_roughCut": True,
        "checkBox_roughResample": False,
        "pushButton_deleteRoughCut": False,
        "pushButton_execute": True,
        "spinBox_OrgBCGShift": False
    },
    "Current": {
        "checkBox_roughCut": True,
        "checkBox_roughResample": False,
        "pushButton_deleteRoughCut": False,
        "pushButton_execute": True,
        "spinBox_OrgBCGShift": False
    }
}


class MainWindow_cut_PAIR_FILE(QMainWindow):

    def __init__(self):
        super(MainWindow_cut_PAIR_FILE, self).__init__()
        self.ui = Ui_MainWindow_cut_PAIR_FILE()
        self.ui.setupUi(self)

        self.root_path = None
        self.sampID = None

        self.__read_config__()

        self.data = None

        self.ui.textBrowser_info.setStyleSheet("QTextBrowser { background-color: rgb(255, 255, 200); }")
        PublicFunc.__styleAllButton__(self, ButtonState)

        # 初始化进度条
        self.ui.progressbar.setStyleSheet(Constants.PROGRESSBAR_STYLE)
        self.progressbar = self.ui.progressbar

        self.msgBox = QMessageBox()
        self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE)

    @overrides
    def show(self, root_path, sampID):
        super().show()
        self.root_path = root_path
        self.sampID = sampID

        PublicFunc.__resetAllButton__(self, ButtonState)

        Config.update({
            "Path": {
                "InputPSGFolder": str(Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID))),
                "SavePSGFolder": str(Path(self.root_path) / Filename.PATH_PSG_ALIGNED / Path(str(self.sampID))),
                "InputAlignInfo": str(Path(self.root_path) / Filename.PATH_LABEL / Path(str(self.sampID))),
                "InputOrgBCGFolder": str(Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID))),
                "SaveOrgBCGFolder": str(Path(self.root_path) / Filename.PATH_ORGBCG_ALIGNED / Path(str(self.sampID)))
            }
        })

        self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
        self.ui.plainTextEdit_label.setPlainText(', '.join(Config["LabelInput"].keys()))

        self.ui.pushButton_execute.clicked.connect(self.__slot_btn_execute__)
        self.ui.checkBox_roughCut.stateChanged.connect(self.__change_approximate_align_mode__)
        self.ui.pushButton_deleteRoughCut.clicked.connect(self.__delete_rough_cut_file__)

        self.ui.spinBox_OrgBCGShift.setEnabled(False)


    @overrides
    def closeEvent(self, event):
        reply = QMessageBox.question(self, '确认', '确认退出吗？', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            PublicFunc.__disableAllButton__(self, ButtonState)

            PublicFunc.statusbar_show_msg(self, PublicFunc.format_status_msg(Constants.SHUTTING_DOWN))
            QApplication.processEvents()

            # 释放资源
            del self.data
            self.deleteLater()
            collect()
            event.accept()
        else:
            event.ignore()

    def __reset__(self):
        ButtonState["Current"].update(ButtonState["Default"].copy())

    def __read_config__(self):
        if not Path(Params.CUT_PAIR_FILE_CONFIG_FILE_PATH).exists():
            with open(Params.CUT_PAIR_FILE_CONFIG_FILE_PATH, "w") as f:
                dump(Params.CUT_PAIR_FILE_CONFIG_NEW_CONTENT, f)

        with open(Params.CUT_PAIR_FILE_CONFIG_FILE_PATH, "r") as f:
            file_config = load(f.read(), Loader=FullLoader)
            Config.update(file_config)

        # 数据回显
        self.ui.spinBox_ECGFreq.setValue(Config["ECGFreq"])


    def  __change_approximate_align_mode__(self):
        # ChannelInput 添加OrgBCGCHannelInput
        if self.ui.checkBox_roughCut.isChecked():
            Config["ChannelInput"].update(Config["OrgBCGChannelInput"])
            Config["ChannelInput"].update(Config["ECGChannelInput"])

            # 修改ChannelSave中的Sync为RoughCut
            for key in Config["ChannelSave"].keys():
                if "Sync" in Config["ChannelSave"][key]:
                    Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("Sync", "RoughCut")

            ButtonState["Default"]["pushButton_deleteRoughCut"] = True
            ButtonState["Default"]["checkBox_roughResample"] = True
            self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
            self.ui.spinBox_OrgBCGShift.setEnabled(True)
            ButtonState["Current"]["pushButton_deleteRoughCut"] = True
            ButtonState["Current"]["checkBox_roughResample"] = True
            PublicFunc.finish_operation(self, ButtonState)

        else:
            # ChannelInput 移除OrgBCGCHannelInput
            for key in Config["OrgBCGChannelInput"].keys():
                if key in Config["ChannelInput"]:
                    Config["ChannelInput"].pop(key)
            for key in Config["ECGChannelInput"].keys():
                if key in Config["ChannelInput"]:
                    Config["ChannelInput"].pop(key)
            # print(Config["ChannelInput"])
            # 修改ChannelSave中的RoughCut为Sync
            for key in Config["ChannelSave"].keys():
                if "RoughCut" in Config["ChannelSave"][key]:
                    Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("RoughCut", "Sync")
            ButtonState["Default"]["pushButton_deleteRoughCut"] = False
            ButtonState["Default"]["checkBox_roughResample"] = False
            self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
            self.ui.spinBox_OrgBCGShift.setEnabled(False)
            ButtonState["Current"]["pushButton_deleteRoughCut"] = False
            ButtonState["Current"]["checkBox_roughResample"] = False
            PublicFunc.finish_operation(self, ButtonState)


    def __slot_btn_execute__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.data = Data(self.root_path, self.sampID)
        Config["ECGFreq"] = self.ui.spinBox_ECGFreq.value()

        # 检查文件是否存在并获取其数据采样率
        PublicFunc.progressbar_update(self, 1, 5, Constants.CUT_PAIR_FILE_GETTING_FILE_AND_FREQ, 0)
        result = self.data.get_file_and_freq()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/5)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            if self.ui.checkBox_roughCut.isChecked():
                Config["BCGFreq"] = self.data.freq["OrgBCG"]
                Config["ECGFreq"] = self.data.freq["ECG"]
            PublicFunc.text_output(self.ui, "(1/5)" + result.info, Constants.TIPS_TYPE_INFO)

        PublicFunc.finish_operation(self, ButtonState)

        # 导入数据
        PublicFunc.progressbar_update(self, 2, 5, Constants.INPUTTING_DATA, 10)
        result = self.data.open_file()
        if not result.status:
            PublicFunc.text_output(self.ui, "(2/5)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(2/5)" + result.info, Constants.TIPS_TYPE_INFO)

        PublicFunc.finish_operation(self, ButtonState)

        if self.ui.checkBox_roughCut.isChecked():
            # 获取或计算粗对齐信息
            PublicFunc.progressbar_update(self, 3, 5, Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO, 20)
            result_approximate = self.data.get_approximately_align_info()
            if not result_approximate:
                PublicFunc.msgbox_output(self, Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_FAILURE, Constants.MSGBOX_TYPE_ERROR)
                PublicFunc.finish_operation(self, ButtonState)
                return
            else:
                PublicFunc.text_output(self.ui, "(3/5)" + Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_FINISHED, Constants.TIPS_TYPE_INFO)

            if self.ui.checkBox_roughResample.isChecked():
                result_resample = self.data.resample_BCG()
                if not result_resample.status:
                    PublicFunc.text_output(self.ui, "(3/5)" + result_resample.info, Constants.TIPS_TYPE_ERROR)
                    PublicFunc.msgbox_output(self, result_resample.info, Constants.MSGBOX_TYPE_ERROR)
                    PublicFunc.finish_operation(self, ButtonState)
                    return
                else:
                    PublicFunc.text_output(self.ui, "(3/5)" + result_resample.info, Constants.TIPS_TYPE_INFO)


            result_approximate = self.data.calc_approximately_align_info(int(self.ui.spinBox_OrgBCGShift.value()))
            if not result_approximate.status:
                PublicFunc.text_output(self.ui, "(3/5)" + result_approximate.info, Constants.TIPS_TYPE_ERROR)
                PublicFunc.msgbox_output(self, result_approximate.info, Constants.MSGBOX_TYPE_ERROR)
                PublicFunc.finish_operation(self, ButtonState)
                return
            else:
                PublicFunc.text_output(self.ui, "(3/5)" + result_approximate.info, Constants.TIPS_TYPE_INFO)

            PublicFunc.finish_operation(self, ButtonState)

        # 切割数据
        PublicFunc.progressbar_update(self, 3, 5, Constants.CUT_PAIR_FILE_CUTTING_DATA, 40)
        PublicFunc.text_output(self.ui, "(3/5)" + str(self.data.alignInfo["cut_index"]), Constants.TIPS_TYPE_INFO)
        result = self.data.cut_data()
        if not result.status:
            PublicFunc.text_output(self.ui, "(3/5)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(3/5)" + result.info, Constants.TIPS_TYPE_INFO)

        PublicFunc.finish_operation(self, ButtonState)

        # 标签映射
        PublicFunc.progressbar_update(self, 4, 5, Constants.CUT_PAIR_FILE_ALIGNING_LABEL, 60)
        result = self.data.align_label()
        if not result.status:
            PublicFunc.text_output(self.ui, "(4/5)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(4/5)" + result.info, Constants.TIPS_TYPE_INFO)

        PublicFunc.finish_operation(self, ButtonState)

        # 保存数据
        PublicFunc.progressbar_update(self, 5, 5, Constants.SAVING_DATA, 70)

        result = self.data.save()

        if not result.status:
            PublicFunc.text_output(self.ui, "(5/5)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(5/5)" + result.info, Constants.TIPS_TYPE_INFO)
            for key, raw in self.data.raw.items():
                info = "保存{}的长度为{}，采样率为{}Hz".format(key, str(len(raw)), str(self.data.freq[key]))
                PublicFunc.text_output(self.ui, info, Constants.TIPS_TYPE_INFO)
            QApplication.processEvents()

        PublicFunc.msgbox_output(self, result.info, Constants.TIPS_TYPE_INFO)
        PublicFunc.finish_operation(self, ButtonState)

    def __delete_rough_cut_file__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.data = Data(self.root_path, self.sampID)

        # 删除粗切割文件
        PublicFunc.progressbar_update(self, 1, 1, Constants.CUT_PAIR_FILE_DELETING_ROUGH_CUT_FILE, 50)
        result = self.data.delete_rough_cut_file()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/1)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(1/1)" + result.info, Constants.TIPS_TYPE_INFO)

        PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_INFO)
        PublicFunc.finish_operation(self, ButtonState)


class Data:

    def __init__(self, root_path, sampID):
        self.actualBCGFreq = None
        self.TimeBiasSecond = None
        self.alignInfo = None

        self.raw = {key: array([]) for key in Config["ChannelInput"]}
        self.freq = {key: 0 for key in Config["ChannelInput"]}

        self.SALabel = None
        self.startTime = None

        self.root_path = root_path
        self.sampID = sampID

    def get_file_and_freq(self):
        try:
            for file_path in Path(Config["Path"]["InputPSGFolder"]).glob('*'):
                if file_path.is_file():
                    file_stem = Path(file_path).stem
                    for key, prefix in Config["ChannelInput"].items():
                        if not prefix.startswith("PSG:"):
                            continue
                        prefix = prefix[len("PSG:"):]
                        if file_stem.startswith(prefix):
                            freq_str = file_stem.rsplit('_', 1)[1]
                            try:
                                freq = int(freq_str)
                                self.freq[key] = freq
                            except ValueError:
                                return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                                             Constants.FAILURE_REASON["Filename_Format_not_Correct"] + f"\n{Config['ChannelInput']}")

            for file_path in Path(Config["Path"]["InputOrgBCGFolder"]).glob('*'):
                if file_path.is_file():
                    file_stem = Path(file_path).stem
                    for key, prefix in Config["ChannelInput"].items():
                        if not prefix.startswith("OrgBCG:"):
                            continue
                        prefix = prefix[len("OrgBCG:"):]
                        if file_stem.startswith(prefix):
                            freq_str = file_stem.rsplit('_', 1)[1]
                            try:
                                freq = int(freq_str)
                                self.freq[key] = freq
                            except ValueError:
                                return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                                             Constants.FAILURE_REASON["Filename_Format_not_Correct"] + f"\n{Config['ChannelInput']}")

            for value in self.freq.values():
                if value == 0:
                    return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                                 Constants.FAILURE_REASON["Filename_Format_not_Correct"] + f"\n{Config['ChannelInput']}")
            if not any((Config["LabelInput"]["SA Label"] + Config["EndWith"]["SA Label"]) in str(file) for file in Path(Config["Path"]["InputPSGFolder"]).glob('*')):
                return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                             Constants.FAILURE_REASON["File_Not_Exist"])
            if not any((Config["StartTime"] + Config["EndWith"]["StartTime"]) in str(file) for file in Path(Config["Path"]["InputPSGFolder"]).glob('*')):
                return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                             Constants.FAILURE_REASON["File_Not_Exist"])
            if not Path(Config["Path"]["InputAlignInfo"]).exists():
                return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                             Constants.FAILURE_REASON["File_Not_Exist"])
        except Exception as e:
            return Result().failure(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FAILURE +
                                         Constants.FAILURE_REASON["Get_File_and_Freq_Excepetion"] + "\n" + format_exc())

        return Result().success(info=Constants.CUT_PAIR_FILE_GET_FILE_AND_FREQ_FINISHED)

    def open_file(self):
        psg_path = str(Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)))
        bcg_path = str(Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)))
        for value in Config["ChannelInput"].values():
            if value.startswith("PSG:"):
                value = value[len("PSG:"):]
                result = PublicFunc.examine_file(psg_path, value, Params.ENDSWITH_TXT)
                if not result.status:
                    return result
            elif value.startswith("OrgBCG:"):
                value = value[len("OrgBCG:"):]
                result = PublicFunc.examine_file(bcg_path, value, Params.ENDSWITH_TXT)
                if not result.status:
                    return result

        if Path(Config["Path"]["InputAlignInfo"]).is_file():
            Config["Path"]["InputAlignInfo"] = str(Path(Config["Path"]["InputAlignInfo"]).parent)

        Config["Path"]["Input_Approximately_Align"] = str(
            Path(Config["Path"]["InputAlignInfo"]) / Path(
                Filename.APPROXIMATELY_ALIGN_INFO + Params.ENDSWITH_CSV))

        Config["Path"]["InputAlignInfo"] = str(
            Path(Config["Path"]["InputAlignInfo"]) / Path(
                Filename.PRECISELY_ALIGN_INFO + Params.ENDSWITH_TXT))

        try:
            for key in Config["ChannelInput"].keys():
                if not Config["ChannelInput"][key].startswith("OrgBCG"):
                    self.raw[key] = read_csv(Path(Config["Path"]["InputPSGFolder"]) / Path((Config["ChannelInput"][key][len("PSG:"):] + str(self.freq[key]) + Config["EndWith"][key])),
                                             encoding=Params.UTF8_ENCODING,
                                             header=None).to_numpy().reshape(-1)

                elif Config["ChannelInput"][key].startswith("OrgBCG"):
                    self.raw[key] = read_csv(Path(Config["Path"]["InputOrgBCGFolder"]) / Path((Config["ChannelInput"][key][len("OrgBCG:"):] + str(self.freq[key]) + Config["EndWith"][key])),
                                                 encoding=Params.UTF8_ENCODING,
                                                 header=None).to_numpy().reshape(-1)

            self.SALabel = read_csv(Path(Config["Path"]["InputPSGFolder"]) / Path((Config["LabelInput"]["SA Label"] + Config["EndWith"]["SA Label"])),
                                    encoding=Params.GBK_ENCODING)
            self.startTime = read_csv(Path(Config["Path"]["InputPSGFolder"]) / Path((Config["StartTime"] + Config["EndWith"]["StartTime"])),
                                      encoding=Params.UTF8_ENCODING,
                                      header=None).to_numpy().reshape(-1)
            if Path(Config["Path"]["InputAlignInfo"]).exists():
                self.alignInfo = read_csv(Path(Config["Path"]["InputAlignInfo"]),
                                          encoding=Params.UTF8_ENCODING,
                                          header=None).to_numpy().reshape(-1)
                cleaned_str = re.sub(r'np\.\w+\(\s*([^)]+?)\s*\)', r'\1', self.alignInfo[0])
                self.alignInfo = literal_eval(cleaned_str)

        except Exception as e:
            return Result().failure(info=Constants.INPUT_FAILURE +
                                         Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.INPUT_FINISHED)


    def get_approximately_align_info(self):
        try:
            df = read_csv(Config["Path"]["Input_Approximately_Align"])
            pos = df["pos"].values[-1]
            ApplyFrequency = df["ApplyFrequency"].values[-1]
            self.TimeBiasSecond = pos / ApplyFrequency
            self.actualBCGFreq = df["estimate_freq"].values[-1] * Config["BCGFreq"]
            return Result().success(info=Constants.INPUT_FINISHED)

        except Exception as e:
            self.TimeBiasSecond = 0
            self.actualBCGFreq = Config["BCGFreq"]
            traceback.print_exc()
            return Result().failure(info=Constants.INPUT_FAILURE
                                            + Constants.FAILURE_REASON["Get_Approximately_Align_Info_Exception"]
                                         + "\n" + format_exc())

    def resample_BCG(self):
        try:
            for key in self.raw.keys():
                if Config["ChannelInput"][key].startswith("OrgBCG:"):
                    # data = self.raw[key]
                    # n_samples = len(data)
                    # duration = n_samples / self.actualBCGFreq
                    #
                    # t_old = np.linspace(0, duration, n_samples, endpoint=False)
                    # n_new = int(np.round(duration * Config["BCGFreq"]))
                    # t_new = np.linspace(0, duration, n_new, endpoint=False)
                    # self.raw[key] = np.interp(t_new, t_old, data)
                    resample_signal = soxr.resample(
                        x=self.raw[key].astype(np.float64),
                        in_rate=self.actualBCGFreq,
                        out_rate=Config["BCGFreq"],
                        quality='VHQ'
                    )
                    print(f"Resampled BCG from {self.actualBCGFreq}Hz to {Config['BCGFreq']}Hz, original length: {len(self.raw[key])}, new length: {len(resample_signal)}")
                    self.raw[key] = resample_signal.astype(self.raw[key].dtype)

                    self.TimeBiasSecond = int(self.TimeBiasSecond * (self.actualBCGFreq / Config["BCGFreq"]))



            return Result().success(info=Constants.CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FINISHED)

        except Exception as e:
            traceback.print_exc()
            print(e)
            return Result().failure(info=Constants.CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FAILURE +
                                         Constants.FAILURE_REASON["Resample_BCG_Exception"] + "\n" + format_exc())


    def calc_approximately_align_info(self, OrgBCGShift=0):
        try:
            # 获取BCG长度
            BCG_freq = Config["BCGFreq"]
            BCG_second = len(self.raw["OrgBCG"]) // BCG_freq

            # 计算ECG长度
            ECG_freq = Config["ECGFreq"]
            ECG_second = len(self.raw["ECG"]) // ECG_freq


            pos = self.TimeBiasSecond

            # 如果pos<0，表示BCG信号比ECG信号提前，需要在开头去除掉一部分BCG信号
            if pos < 0:
                front_BCG = ceil(-pos) - OrgBCGShift
                front_ECG = 0
            else:
                front_BCG = 0
                front_ECG = ceil(pos) + OrgBCGShift

            # 计算剩余长度
            remain_BCG_second = BCG_second - front_BCG
            remain_ECG_second = ECG_second - front_ECG
            remain_second = min(remain_BCG_second, remain_ECG_second)
            back_BCG = front_BCG + remain_second - 1
            back_ECG = front_ECG + remain_second - 1

            self.alignInfo = {
                "cut_index": {
                    "front_BCG": front_BCG * BCG_freq,
                    "back_BCG": back_BCG * BCG_freq,
                    "front_ECG": front_ECG * ECG_freq,
                    "back_ECG": back_ECG * ECG_freq,
                }
            }
            return Result().success(info=Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_CALC_FINISHED)


        except Exception as e:
            traceback.print_exc()
            print(e)
            Result().failure(info=Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_FAILURE +
                             Constants.FAILURE_REASON["Calculate_Approximately_Align_Info_Exception"] + "\n" + format_exc())

    def delete_rough_cut_file(self):
        # 在保存路径中删除RoughCut文件
        try:
            for key in Config["ChannelInput"].keys():
                if Config["ChannelInput"][key].startswith("OrgBCG:"):
                    file_path = Path(Config["Path"]["SaveOrgBCGFolder"]).glob(f"{Config['ChannelSave'][key][len('OrgBCG:'):]}*{Config['EndWith'][key]}")
                    for file in file_path:
                        file.unlink()
                if Config["ChannelInput"][key].startswith("PSG:"):
                    file_path = Path(Config["Path"]["SavePSGFolder"]).glob(f"{Config['ChannelSave'][key][len('PSG:'):]}*{Config['EndWith'][key]}")
                    for file in file_path:
                        file.unlink()
            return Result().success(info=Constants.CUT_PAIR_FILE_DELETE_ROUGH_CUT_FILE_FINISHED)
        except Exception as e:
            return Result().failure(info=Constants.CUT_PAIR_FILE_DELETE_ROUGH_CUT_FILE_FAILURE +
                                         Constants.FAILURE_REASON["Delete_Rough_Cut_File_Exception"] + "\n" + format_exc())

    def cut_data(self):
        try:

            for key, raw in self.raw.items():
                print(f"Cutting data for channel: {key}, original length: {len(raw)}")
                if Config["ChannelInput"][key].startswith("PSG:"):
                    # 转换切割点
                    ECG_freq = Config["ECGFreq"]
                    raw_freq = self.freq[key]
                    duration_second = ((self.alignInfo["cut_index"]["back_ECG"] - self.alignInfo["cut_index"]["front_ECG"]) // ECG_freq) + 1
                    start_index_cut = floor(self.alignInfo["cut_index"]["front_ECG"] * (raw_freq / ECG_freq))
                    end_index_cut = start_index_cut + (duration_second * raw_freq)

                    try:
                        # 切割信号
                        self.raw[key] = self.raw[key][start_index_cut:end_index_cut]
                    except Exception:
                        return Result().failure(info=Constants.CUT_PAIR_FILE_CUT_DATA_FAILURE +
                                                     Constants.FAILURE_REASON["Cut_Data_Length_not_Correct"])
                elif Config["ChannelInput"][key].startswith("OrgBCG:"):
                    # 转换切割点
                    BCG_freq = Config["BCGFreq"]
                    raw_freq = self.freq[key]
                    duration_second = ((self.alignInfo["cut_index"]["back_BCG"] - self.alignInfo["cut_index"]["front_BCG"]) // BCG_freq) + 1
                    start_index_cut = floor(self.alignInfo["cut_index"]["front_BCG"] * (raw_freq / BCG_freq))
                    end_index_cut = start_index_cut + (duration_second * raw_freq)

                    try:
                        # 切割信号
                        self.raw[key] = self.raw[key][start_index_cut:end_index_cut]
                    except Exception:
                        return Result().failure(info=Constants.CUT_PAIR_FILE_CUT_DATA_FAILURE +
                                                     Constants.FAILURE_REASON["Cut_Data_Length_not_Correct"])
        except Exception as e:
            return Result().failure(info=Constants.CUT_PAIR_FILE_CUT_DATA_FAILURE +
                                         Constants.FAILURE_REASON["Cut_Data_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.CUT_PAIR_FILE_CUT_DATA_FINISHED)

    def align_label(self):
        try:
        # 读取SA标签
            self.SALabel = self.SALabel.loc[:, ~self.SALabel.columns.str.contains("^Unnamed")]
            self.SALabel = self.SALabel[self.SALabel["Event type"].isin(Params.CUT_PAIR_FILE_SALABEL_EVENT)]
            self.SALabel["Duration"] = self.SALabel["Duration"].astype(str)
            self.SALabel["Duration"] = self.SALabel["Duration"].str.replace(r' \(.*?\)', '', regex=True)
        except Exception:
            return Result().failure(info=Constants.CUT_PAIR_FILE_ALIGN_LABEL_FAILURE +
                                         Constants.FAILURE_REASON["Align_Label_SALabel_Format_not_Correct"])

        try:
            # 获取记录开始时间
            start_time = str(self.startTime[0]).split(" ")[1]
            start_time = Data.get_time_to_seconds(start_time)
            ECG_freq = Config["ECGFreq"]

            # 计算起始时间秒数和终止时间秒数
            self.SALabel["Start"] = (self.SALabel["Time"].apply(self.get_time_to_seconds) - start_time).apply(
                lambda x: x + 24 * 3600 if x < 0 else x).astype(int)
            self.SALabel["End"] = self.SALabel["Start"] + self.SALabel["Duration"].astype(float).round(0).astype(int)

            # 标签映射
            ECG_length = self.alignInfo["cut_index"]["back_ECG"] - self.alignInfo["cut_index"]["front_ECG"]
            self.SALabel["Start"] = self.SALabel["Start"] - round((self.alignInfo["cut_index"]["front_ECG"] / ECG_freq))
            self.SALabel["End"] = self.SALabel["End"] - round((self.alignInfo["cut_index"]["front_ECG"] / ECG_freq))
            self.SALabel = self.SALabel[self.SALabel["End"] >= 0]
            self.SALabel.loc[self.SALabel["Start"] < 0, "Start"] = 0
            self.SALabel = self.SALabel[self.SALabel["Start"] < ECG_length]
            self.SALabel.loc[self.SALabel["End"] >= ECG_length, "End"] = ECG_length - 1
        except Exception as e:
            return Result().failure(info=Constants.CUT_PAIR_FILE_ALIGN_LABEL_FAILURE +
                                         Constants.FAILURE_REASON["Align_Label_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.CUT_PAIR_FILE_ALIGN_LABEL_FINISHED)

    def save(self):
        for raw in self.raw.values():
            if len(raw) == 0:
                return Result().failure(info=Constants.SAVE_FAILURE +
                                             Constants.FAILURE_REASON["Data_not_Exist"])

        try:
            for key, raw in self.raw.items():
                if Config["ChannelInput"][key].startswith("PSG:"):
                    # print(f"Saving PSG channel: {key} to {Config['Path']['SavePSGFolder']} /  {Config['ChannelSave'][key] + str(self.freq[key]) + Config['EndWith'][key]}")
                    DataFrame(raw.reshape(-1)).to_csv(Path(Config["Path"]["SavePSGFolder"]) / Path((Config["ChannelSave"][key][len("PSG:"):] + str(self.freq[key]) + Config["EndWith"][key])),
                                                      index=False, header=False)
                elif Config["ChannelInput"][key].startswith("OrgBCG:"):
                    # print(f"Saving OrgBCG channel: {key} to {Config['Path']['SaveOrgBCGFolder']} /  {Config['ChannelSave'][key] + str(self.freq[key]) + Config['EndWith'][key]}")
                    DataFrame(raw.reshape(-1)).to_csv(Path(Config["Path"]["SaveOrgBCGFolder"]) / Path((Config["ChannelSave"][key][len("OrgBCG:"):] + str(self.freq[key]) + Config["EndWith"][key])),
                                                      index=False, header=False)
            # 重排index，从1开始，并给index命名
            self.SALabel.sort_values(by=["Start"], inplace=True)
            self.SALabel.reset_index(drop=True, inplace=True)
            self.SALabel.index = self.SALabel.index + 1
            self.SALabel.index.name = "Index"
            self.SALabel.to_csv(Path(Config["Path"]["SavePSGFolder"]) / Path((Config["LabelSave"]["SA Label"] + Config["EndWith"]["SA Label"])),
                                encoding="gbk")
        except PermissionError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_Permission_Denied"])
        except FileNotFoundError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_File_Not_Found"])
        except Exception as e:
            return Result().failure(info=Constants.SAVE_FAILURE +
                                         Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.SAVE_FINISHED)

    @staticmethod
    def get_time_to_seconds(time_str):
        h, m, s = map(int, time_str.split(":"))
        return h * 3600 + m * 60 + s