from __future__ import annotations
import os
from typing import Tuple, Union, Optional
from dataclasses import dataclass, field

import numpy as np
import pandas as pd

from base.base import (BaseKukaDataParser, BaseKukaTextParser, 
                       BaseTraceStageDetector, BaseTextStageDetector,
                       BaseRawTraceProcessor, KukaDataHead, 
                       KukaTXT, Settings)


class KukaDataParser(BaseKukaDataParser):
    
    def parse(self, head_path: str) -> pd.DataFrame:
        head = self._parse_dat_file(head_path)
        body_path = os.path.join(os.path.dirname(head_path), head.filename)
        dataframe = self._parse_r64_file(body_path, head)
        return dataframe

    def _parse_dat_file(self, path: str) -> KukaDataHead:
        with open(path, 'r', encoding='cp1252') as file:
            head = KukaDataHead(0, "", {})
            inside_channel = False
            self._ch_name = None
            for line in file:
                line = line.strip()
                if line in ('#BEGINCHANNELHEADER', "#BEGINGLOBALHEADER"):
                    inside_channel = True
                elif line in ('#ENDCHANNELHEADER' "#ENDGLOBALHEADER"):
                    inside_channel = False
                else:
                    if inside_channel:
                        self._parse_head_file_line(line, head)
        return head
    
    def _parse_head_file_line(self, line: str, head: KukaDataHead) -> None:
        tag, data = line.split(',')
        match tag:
            case '102':
                head.rob_ID = data
            case '200':
                self._ch_name = str(data)
                head.channels[self._ch_name] = {}
            case '202':
                head.channels[self._ch_name]['unit'] = str(data)
            case '211':
                head.filename = str(data)
            case '220':
                head.channels[self._ch_name]['len'] = int(data)
            case '221':
                head.channels[self._ch_name]['num'] = int(data)
            case '241':
                head.channels[self._ch_name]['multiplyer'] = float(data)

    def _parse_r64_file(self, path: str, head: KukaDataHead) -> pd.DataFrame:
        time_axis = self._build_time_axis(head)
        mul, axes_names = self._extract_channels_data(head)
        data_array = self._read_r64_file(path)

        if data_array.size % len(axes_names) != 0:
            raise ValueError("Количество записей в {path} не кратно количеству найденных каналов ({ch_count})")
        try:
            axes = data_array.reshape(-1, len(axes_names))
        except ValueError as e:
            raise ValueError(f"Ошибка при изменении формы data_array: {e}")

        dataframe = pd.concat([time_axis, pd.DataFrame(axes*mul, columns=axes_names)], axis=1)
        return dataframe
    
    @staticmethod
    def _extract_channels_data(head: KukaDataHead) -> Tuple[np.ndarray, list]:
        sorted_channels = sorted(
            (item for item in head.channels.items() if item[0] != "Zeit"),
            key=lambda item: item[1]['num']
            )
        mul = np.array([info['multiplyer'] for _, info in sorted_channels])
        names = [key for key, _ in sorted_channels]
        return mul, names

    @staticmethod
    def _build_time_axis(head: KukaDataHead) -> pd.Series:
        len_timestamps = head.channels['Zeit']['len'] -1
        t_step = head.channels['Zeit']['multiplyer']
        time_axis = pd.Series(np.arange(0, len_timestamps*t_step, t_step))
        time_axis.name = 'time'
        return time_axis

    @staticmethod
    def _read_r64_file(path: str) -> np.ndarray[float]:
        with open(path, 'rb') as file:
            data = file.read()
            floats = np.frombuffer(data, dtype='<d')
        return floats


class KukaTextParser(BaseKukaTextParser):
    def __init__(self):
        super().__init__()
        self._in_msg = False
        self._datapacks = []

    def parse(self, path:str ) -> list[KukaTXT]:
        self._datapacks = []
        with open(path, 'r') as file:
            datapack = KukaTXT()
            for line in file:
                line = line.strip()
                datapack = self._check_msg_flow(line, datapack)
        return self._datapacks
                
    def _check_msg_flow(self, line:str, datapack:KukaTXT) -> KukaTXT:
        if line == "#BEGINMOTIONINFO":
            self._in_msg = True
            datapack = KukaTXT()
        elif line == "#ENDMOTIONINFO":
            self._in_msg = False
            self._datapacks.append(datapack)
            datapack = KukaTXT()
        elif self._in_msg:
            datapack = self._process_line(line, datapack)
        return datapack

    def _process_line(self, line:str, datapack:KukaTXT) -> KukaTXT:
        tag, data = line.split(":")
        match tag:
            case "TIME":
                datapack.time = float(data)
            case "ENDTIME":
                datapack.endtime = float(data)
            case "MODULE":
                pass
            case "FUNCTION/PROCEDURE":
                datapack.func = data
            case "TYPE":
                datapack.type_ = data
            case "SIGNAL":
                datapack.signal = data
            case "LINE":
                pass
            case "POINT NAME":
                pass
            case "POINT COORDINATES":
                pass
            case "BLENDING":
                pass
            case "BLENDING PARAMETER":
                pass
            case "VELOCITIES":
                pass
            case "ACCELERATIONS":
                pass
            case "BASE":
                pass
            case "TOOL":
                pass
            case "IPO MODE":
                pass
            case "MOTION MODE":
                pass
            case "LOAD":
                pass
            case "LOAD A3":
                pass
        return datapack


class TraceStageDetector(BaseTraceStageDetector):
    
    def __init__(self, parent:TraceProcessor = None):
        super().__init__(parent)

    def detect_stages(self, df: pd.DataFrame) -> list:
        df = df.sort_index()
        stage_markers = []
        timestamps = df['time'].to_list()  # времена (в секундах) как список

        for i, time in enumerate(timestamps):
            mark = self._mark_timestamp(df, i, time)
            if mark: stage_markers.append(mark)

        # Объединяем подряд идущие одинаковые метки в единые этапы.
        stages = self._merge_marks_to_stages(stage_markers, timestamps)
        return stages

    def _mark_timestamp(self, df:pd.DataFrame, index:int, time:float) -> Union[str, None]:
        # Не интересные значения помечаем как "unknown"
        if (time < self._parent._settings.filter["ROI_start"][0] or 
            time > self._parent._settings.filter["ROI_finish"][0]):
            return "unknown"

        rob_vel = df.loc[index, "CartVel_Act"]
        act_vel = df.loc[index, "DriveMotorVel_Act7"]
        act_curr = df.loc[index, "DriveMotorCurr_Act7"]

        if abs(rob_vel) > self._parent._settings.filter["robot_zero_velocity_trashold"][0]:
            return "Oncomming"

        if (abs(act_vel) < self._parent._settings.filter["actuator_zero_velocity_trashold"][0] and 
            abs(act_curr) > self._parent._settings.filter["actuator_current_trashold"][0]):
            return "Squeeze&Welding&Relief"

        if self._parent._settings.filter["actuator_finishing_velocity_trashold"][0] > abs(abs(act_vel) - self._parent._settings.filter["actuator_finishing_velocity"][0]):
            return "Closing"
        
        return None

    def _merge_marks_to_stages(self, stage_markers:list, timestamps:list) -> list:
        stages = []
        current_stage = stage_markers[0]
        start_time = timestamps[0]

        for i in range(1, len(stage_markers)):
            if stage_markers[i] != current_stage:
                end_time = timestamps[i - 1]
                stages.append({
                    "stage": current_stage,
                    "start_time": start_time,
                    "end_time": end_time
                })
                current_stage = stage_markers[i]
                start_time = timestamps[i]

        stages.append({
            "stage": current_stage,
            "start_time": start_time,
            "end_time": timestamps[-1]
        })
        return stages


class TextStageDetector(BaseTextStageDetector):

    def __init__(self, parent :TraceProcessor = None):
        super().__init__(parent)
    
    def detect_welding(self, data:list[KukaTXT]) -> list:
        stages = []
        for i in range(len(data)):
            if data[i].func == " SPOT" and data[i].signal == " END":
                stages.append({
                    "stage": "welding",
                    "start_time": data[i].time,
                    "end_time": data[i+1].time
                })
        return stages


class TraceProcessor(BaseRawTraceProcessor):

    def __init__(self):
        self._settings = Settings()
        dataparser = KukaDataParser()
        textparser = KukaTextParser()
        data_detector = TraceStageDetector(self)
        text_detector = TextStageDetector(self)

        super().__init__(dataparser, textparser, data_detector, text_detector)

    def prerender(self, data:list[str]) -> None:
        if data and len(data) == 2:
            dat_filepath = data[0]
            txt_filepath = data[1]
        elif data:
            dat_filepath = data[0]
            txt_filepath = None
        else:
            dat_filepath = None
            txt_filepath = None

        trace_df = self._unpack_trace(dat_filepath)
        text_data = self._unpack_text(txt_filepath)
        events = self._detect_stages(trace_df, text_data)
        print (events)
        
        self._mediator.notify(self, [trace_df, events])

    def update_settings(self, data:Settings) -> None:
        self._settings = data

    def _unpack_trace(self, dat_filepath:str = None) -> Optional[pd.DataFrame]:
        if dat_filepath:
            return self._dataparser.parse(dat_filepath)
        return None

    def _unpack_text(self, txt_filepath:str = None) -> Optional[list[KukaTXT]]:
        if txt_filepath:
            return self._textparser.parse(txt_filepath)
        return None
    
    def _detect_stages(self, trace_df:pd.DataFrame, text_data:list) -> dict[list]:
        if trace_df is not None and text_data is not None:
            trace_stages = self._data_detector.detect_stages(trace_df)
            welding_stages = self._text_detector.detect_welding(text_data)
            events = self._form_events(trace_stages, welding_stages)
            return events
        return None
        
    @staticmethod     
    def _form_events(trace_stages:list, welding_stages:list) -> dict[list]:
        idx = 0
        events = {
            "Closing":[[],[]],
            "Squeeze":[[],[]],
            "Welding":[[],[]],
            "Relief":[[],[]],
            "Oncomming":[[],[]]
            }
        for i in range(len(trace_stages)-1):
            if trace_stages[i]['stage'] == 'Squeeze&Welding&Relief':
                if trace_stages[i]['end_time'] > welding_stages[idx]['start_time']:
                
                    events["Squeeze"][0].append(trace_stages[i]['start_time']) 
                    events["Squeeze"][1].append(welding_stages[idx]['start_time'])

                    events["Welding"][0].append(welding_stages[idx]['start_time']) 
                    events["Welding"][1].append(welding_stages[idx]['end_time'])

                    events["Relief"][0].append(welding_stages[idx]['end_time']) 
                    events["Relief"][1].append(trace_stages[i+1]['start_time'])
                    idx += 1
                else:
                    events["Squeeze"][0].append(trace_stages[i]['start_time']) 
                    events["Squeeze"][1].append(trace_stages[i]['end_time'])
            else:
                events[trace_stages[i]['stage']][0].append(trace_stages[i]['start_time']) 
                events[trace_stages[i]['stage']][1].append(trace_stages[i]['end_time'])
        return events


"Перемещение"
#FUNCTION/PROCEDURE: SW_RSP030TL01_SN - какое-то перемещение
#
# ИЛИ
#
#FUNCTION/PROCEDURE: SGL_MoveToPos - перемещение между точками
#SIGNAL: BLENDING 

"Смыкание и набор усилия"
#FUNCTION/PROCEDURE: SGM_MOVE_TO_FORCE - перемещение электрода  движения робота
# ... ... 
#FUNCTION/PROCEDURE: SGM_MOVE_TO_FORCE - перемещение 0.5 мм роботом с движением электрода
#... ... 
#FUNCTION/PROCEDURE: SGM_MOVE_TO_FORCE - остановка в позиции (может быть набор усилия?)

"Сварка"
#FUNCTION/PROCEDURE: SPOT - Начало сварочного процесса
#SIGNAL: START
#
#FUNCTION/PROCEDURE: SPOT - Конец сварочного процесса
#SIGNAL: END

"Снятие усилия и разъезд"
#FUNCTION/PROCEDURE: SGL_MoveToPos - Выход из контакта с точкой движением робота и электрода
#SIGNAL: START
#

"Перемещение"
#FUNCTION/PROCEDURE: SW_RSP030TL01_SN - какое-то перемещение
#
# ИЛИ
#
#FUNCTION/PROCEDURE: SGL_MoveToPos - перемещение между точками
#SIGNAL: BLENDING 

if __name__ == '__main__':

    roboreader = KukaDataParser()
    txt_reader = KukaTextParser()
    detector_traces = TraceStageDetector(region_of_focus=[7.7, 42])
    detector_weldings = TextStageDetector()

    path1 = os.path.abspath("trace_samples/teslaSP_VelTCP_KRCIpo.dat")
    path2 = os.path.abspath("trace_samples/teslaSP_VelTCP_PROG.TXT")

    data1 = roboreader.parse(path1)
    data2 = txt_reader.parse(path2)

    stages = detector_traces.detect_stages(data1)
    weldings = detector_weldings.detect_welding(data2)

    counter = 0
    for st in stages:
        print(st)
        if st["stage"] == "force":
            counter+=1
            print(counter)
    print("=========================================")
    for we in weldings:
        print (we)



    """
    save = os.path.dirname(path) + "/sample.csv"
    data.to_csv(save)
    path = os.path.abspath("trace_samples/teslaSP_VelTCP_PROG.TXT")
    txt_parser = TXT_Parser()
    txt_parser.parse(path)
    print(len(txt_parser._datapacks))
    """
    

            

            
   


