from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional
import os
import numpy as np
import pandas as pd

from intervaltree import Interval, IntervalTree


class BasePerformanceFactory(ABC):

    @abstractmethod
    def factory_method(self):
        ...
    
    def job(self):
        ...

    def _get_file_data(self, path) -> pd.DataFrame:
        head, file = self._dat_parser(path)
        self.dat_name = file[:-4]
        path_r64 = os.path.dirname(path) + '\\' + file
        time_axis, dataframe = self._r64_parser(path_r64, head)
        dataframe = pd.concat([dataframe, time_axis], axis=1)
        return dataframe
    
    def _dat_parser(self, path: str) -> list[dict, str]:
        with open(path, 'r') as file:
            head = {'channels': 0}
            inside_channel = False
            channels = 0
            for line in file:
                line = line.strip()          
                if line == '#BEGINCHANNELHEADER' or line == "#BEGINGLOBALHEADER":
                    inside_channel = True
                    
                elif line == '#ENDCHANNELHEADER' or line == "#ENDGLOBALHEADER":
                    inside_channel = False
                    pass
                # Формирование словаря
                elif inside_channel:
                    _, data = line.split(',')
                    match _:
                        case '102':
                            head['rob_id'] = data
                        case '200':
                            ch_name = data
                            if ch_name != 'Zeit': channels +=1
                            head[ch_name] = {}
                        case '202':
                            head[ch_name]['unit'] = data
                        case '211':
                            file = data
                        case '220':
                            head[ch_name]['len'] = int(data)
                        case '221':
                            head[ch_name]['num'] = int(data)
                        case '241':
                            head[ch_name]['multiplyer'] = float(data)
            head['channels'] = int(channels)
        return head, file

    def _r64_parser(self, path: str, head: dict) -> Optional[list[pd.Series, pd.DataFrame]]:
        ch = head['channels']
        keys = list(head.keys())[-ch:]
        len_timestamps = head['Zeit']['len']
        t_step = head['Zeit']['multiplyer']
        time_axis = pd.Series(np.arange(0, len_timestamps*t_step, t_step))
        time_axis.name = 'time'
        dataframe = pd.DataFrame({})
        with open(path, 'rb') as file:
            data = file.read()
            floats = np.frombuffer(data, dtype='<d')  # Little-endian double
        for key in keys:
            step = head[key]['num']-1
            result = pd.Series(np.array(floats[step::ch])* head[key]['multiplyer'])
            result.name = key
            dataframe = pd.concat([dataframe, result], axis=1)
        return time_axis, dataframe


class BaseProduct(ABC):
    def __init__(self):
        self._signals = []

    def _find_indexes(self,
                       signal: str,  
                       dataframe: pd.DataFrame) -> list[list[float], list[float]]:
        stage_diff = np.diff(dataframe[signal])
        start_idx = np.where(stage_diff == 1)
        finish_idx = np.where(stage_diff == -1)
        return start_idx[0], finish_idx[0]
    
    def _find_events(self, 
                        dataframe: pd.DataFrame, 
                        signals: list[str]) -> Optional[dict[dict[pd.Series]]]: 
        intervals = {}
        end_time = 0
        for signal in signals:
            start_idx, finish_idx = np.array(self._find_indexes(signal, dataframe))
            start_series = dataframe.loc[start_idx, "time"].reset_index(drop=True)
            end_series = dataframe.loc[finish_idx, "time"].reset_index(drop=True)
            end_series.fillna(end_time)
            intervals[signal] = {"rise": start_series,
                                "fall": end_series}
        return intervals
    
    def _form_intervals(self, 
                        start: pd.Series, 
                        end: pd.Series) -> IntervalTree:
        if len(start) != len(end):
            for i in range(1, len(end)):
                if end[i-1] > start[i]:
                   start = start.drop(i).reset_index(drop=True)
        tree = IntervalTree(Interval(start[i], end[i], i) for i in range(len (start)))
        return tree
    
    @abstractmethod
    def operation(self):
        ...


class RobotPerformance(BasePerformanceFactory):
    
    def factory_method(self) -> BaseProduct:
        return RobotDF()
    
    def job(self, path) -> list[pd.DataFrame]:
        product = self.factory_method()
        dataframe = self._get_file_data(path)
        rob_df = product.operation(dataframe)
        return rob_df

class TWC_Performance(BasePerformanceFactory):

    def factory_method(self) -> BaseProduct:
        return TWC_DF()
    
    def job(self, TWC_DF: pd.DataFrame) -> list[pd.DataFrame]:
        product = self.factory_method()
        result = product.operation(TWC_DF) 
        return result


class RobotDF(BaseProduct):
    def __init__(self):
        self._signals = [
            "$OUT3012", 
            "$IN3003", 
            "$OUT3003"
        ]

    def operation(self, dataframe: pd.DataFrame) -> list[IntervalTree]:
        events = self._find_events(dataframe, self._signals)
        point_tree = self._form_intervals(start=events["$OUT3012"]["rise"], end=events["$OUT3012"]["fall"])
        movement_tree = self._form_intervals(start=events["$OUT3003"]["fall"], end=events["$OUT3012"]["rise"])
        dialog_tree = []
        return point_tree, movement_tree, dialog_tree

    
class TWC_DF(BaseProduct):
    def __init__(self):
        self._signals = [
            "Closing",
            "Squeeze",
            "Welding",
            "Relief"
        ]

    def operation(self, dataframe: pd.DataFrame) -> list[IntervalTree]:
        events = self._find_events(dataframe, self._signals)
        closing_tree = self._form_intervals(start=events["Closing"]["rise"], end=events["Closing"]["fall"])
        squeeze_tree = self._form_intervals(start=events["Squeeze"]["rise"], end=events["Squeeze"]["fall"])
        relief_tree = self._form_intervals(start=events["Relief"]["rise"], end=events["Relief"]["fall"])
        return closing_tree, squeeze_tree, relief_tree


        
        
            

            
   


