"""
Wrapper dla ZDistA_komp.dll - algorytm zabezpieczenia odległościowego
Używa natywnej biblioteki C zamiast implementacji Python
"""
import ctypes
import numpy as np
import math
import os

# Ścieżka do DLL
DLL_PATH = os.path.join(os.path.dirname(__file__), 'ZDistA_komp.dll')

# Ładowanie DLL
try:
    _dll = ctypes.CDLL(DLL_PATH)
    _dll_loaded = True
except OSError as e:
    print(f"OSTRZEŻENIE: Nie można załadować DLL: {e}")
    print("Używam implementacji Python")
    _dll_loaded = False

# Definicje funkcji DLL jeśli załadowano
if _dll_loaded:
    # ZDistA_init(z1_r, z1_x, line_angle_deg, kierunek) -> handle
    _dll.ZDistA_init.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int]
    _dll.ZDistA_init.restype = ctypes.c_void_p

    # ZDistA_step(handle, u_re[3], u_im[3], i_re[3], i_im[3]) -> int
    _dll.ZDistA_step.argtypes = [
        ctypes.c_void_p,
        ctypes.POINTER(ctypes.c_float),  # u_re[3]
        ctypes.POINTER(ctypes.c_float),  # u_im[3]
        ctypes.POINTER(ctypes.c_float),  # i_re[3]
        ctypes.POINTER(ctypes.c_float)   # i_im[3]
    ]
    _dll.ZDistA_step.restype = ctypes.c_int

    # ZDistA_cleanup(handle)
    _dll.ZDistA_cleanup.argtypes = [ctypes.c_void_p]
    _dll.ZDistA_cleanup.restype = None


class DistanceRelayZDLL:
    """
    Algorytm zabezpieczenia odległościowego - wersja DLL
    Wykorzystuje natywną bibliotekę ZDistA_komp.dll
    """
    def __init__(self,
                 Z_line_R=2.0, Z_line_X=8.0,
                 line_angle=75.0,
                 z1_reach=0.8, z2_reach=1.2, z3_reach=1.5,
                 z4_reach=2.0, z5_reach=2.5,
                 t_z1=0, t_z2=300, t_z3=600, t_z4=1000, t_z5=1500,
                 przekladnia=1.0,
                 fi1=75.0, fi2=85.0, fi3=90.0, fi4=90.0,
                 I_min=0.5, U_min=1.0,
                 kierunek=2,
                 Kk1=0.0, Kk1_kat=0.0,
                 KkC=0.0, KkC_kat=0.0,
                 typ_zwarc=0,
                 wyl=False,
                 settings=None):

        if not _dll_loaded:
            raise RuntimeError("DLL nie została załadowana!")

        # Parametry linii
        self.Z_line_R = Z_line_R
        self.Z_line_X = Z_line_X
        self.Z_line_mag = np.sqrt(Z_line_R**2 + Z_line_X**2)
        self.line_angle = line_angle

        # Przekładnia
        self.przekladnia = przekladnia

        # Kąty charakterystyki
        self.fi1 = fi1
        self.fi2 = fi2
        self.fi3 = fi3
        self.fi4 = fi4

        # Współczynniki stref
        self.z1_reach = z1_reach
        self.z2_reach = z2_reach
        self.z3_reach = z3_reach
        self.z4_reach = z4_reach
        self.z5_reach = z5_reach

        # Oblicz R i X dla stref
        self.Z1_R = Z_line_R * z1_reach
        self.Z1_X = Z_line_X * z1_reach
        self.Z2_R = Z_line_R * z2_reach
        self.Z2_X = Z_line_X * z2_reach
        self.Z3_R = Z_line_R * z3_reach
        self.Z3_X = Z_line_X * z3_reach
        self.Z4_R = Z_line_R * z4_reach
        self.Z4_X = Z_line_X * z4_reach
        self.Z5_R = Z_line_R * z5_reach
        self.Z5_X = Z_line_X * z5_reach

        # Opóźnienia stref [ms]
        self.t_z1 = t_z1
        self.t_z2 = t_z2
        self.t_z3 = t_z3
        self.t_z4 = t_z4
        self.t_z5 = t_z5

        # Minimalne prądy i napięcia
        self.I_min = I_min
        self.U_min = U_min
        self.Igr = I_min * I_min

        # Kierunek
        self.kierunek = kierunek

        # Nadpisz nastawy z pliku konfiguracyjnego
        if settings:
            print("\n--- Nadpisywanie nastaw ZDistA DLL z pliku konfiguracyjnego ---")
            self.kierunek = int(settings.get('Kierunek', self.kierunek))

            if 'Z1_R' in settings and self.Z_line_R > 0:
                self.z1_reach = float(settings['Z1_R']) / self.Z_line_R
                self.Z1_R = float(settings['Z1_R'])
                self.Z1_X = self.Z_line_X * self.z1_reach
            if 'Z2_R' in settings and self.Z_line_R > 0:
                self.z2_reach = float(settings['Z2_R']) / self.Z_line_R
                self.Z2_R = float(settings['Z2_R'])
                self.Z2_X = self.Z_line_X * self.z2_reach
            if 'Z3_R' in settings and self.Z_line_R > 0:
                self.z3_reach = float(settings['Z3_R']) / self.Z_line_R
                self.Z3_R = float(settings['Z3_R'])
                self.Z3_X = self.Z_line_X * self.z3_reach

            self.t_z1 = int(settings.get('tZ1', self.t_z1))
            self.t_z2 = int(settings.get('tZ2', self.t_z2))
            self.t_z3 = int(settings.get('tZ3', self.t_z3))
            print("--- Koniec nadpisywania nastaw ZDistA DLL ---\n")

        # Inicjalizacja DLL
        # Używamy Z1_R i Z1_X jako nastawy bazowej
        self.handle = _dll.ZDistA_init(
            ctypes.c_double(self.Z1_R),
            ctypes.c_double(self.Z1_X),
            ctypes.c_double(line_angle),
            ctypes.c_int(kierunek)
        )

        if not self.handle:
            raise RuntimeError("Nie udało się zainicjalizować ZDistA DLL")

        # Debug info
        print(f"Nastawy zabezpieczenia ZDistA (DLL):")
        print(f"  Linia: R={Z_line_R:.2f} Ohm, X={Z_line_X:.2f} Ohm, |Z|={self.Z_line_mag:.2f} Ohm")
        print(f"  Kąt linii: {line_angle:.1f} deg")
        print(f"  Strefa 1: {self.z1_reach*100:.0f}% (R={self.Z1_R:.2f}, X={self.Z1_X:.2f})")
        print(f"  Strefa 2: {self.z2_reach*100:.0f}% (R={self.Z2_R:.2f}, X={self.Z2_X:.2f})")
        print(f"  Strefa 3: {self.z3_reach*100:.0f}% (R={self.Z3_R:.2f}, X={self.Z3_X:.2f})")
        print(f"  Kierunek: {self.kierunek} (0=bez, 1=do linii, 2=do szyn)")
        print(f"  DLL zainicjalizowany pomyślnie")

        # Stan wyłącznika
        self.wyl = wyl
        self.tripped = {
            'L1': False, 'L2': False, 'L3': False,
            'L1L2': False, 'L2L3': False, 'L3L1': False
        }

    def init_relay(self):
        """Inicjalizacja zabezpieczenia"""
        print("Zabezpieczenie ZDistA (DLL) zainicjalizowane")

    def reset(self):
        """Reset stanów"""
        self.tripped = {
            'L1': False, 'L2': False, 'L3': False,
            'L1L2': False, 'L2L3': False, 'L3L1': False
        }

    def step_relay(self, phase, u_re, u_im, i_re, i_im,
                   u0_re=0, u0_im=0, i0_re=0, i0_im=0,
                   u_zg_re=0, u_zg_im=0, i_zg_re=0, i_zg_im=0):
        """
        Krok algorytmu dla jednej fazy
        phase: 'L1', 'L2' lub 'L3'
        Zwraca: 0 = brak trip, 1 = trip
        """
        if not self.handle:
            return 0

        # Mapowanie fazy na indeks (DLL oczekuje wszystkich 3 faz na raz)
        phase_map = {'L1': 0, 'L2': 1, 'L3': 2}

        # Przygotuj tablice dla DLL (3 fazy)
        u_re_arr = (ctypes.c_float * 3)()
        u_im_arr = (ctypes.c_float * 3)()
        i_re_arr = (ctypes.c_float * 3)()
        i_im_arr = (ctypes.c_float * 3)()

        # Wypełnij dane dla wszystkich 3 faz
        # Dla uproszczenia, używamy tych samych wartości dla wszystkich faz
        # (w rzeczywistości tester.py przekazuje różne wartości dla każdej fazy)
        u_re_arr[0] = u_re
        u_im_arr[0] = u_im
        i_re_arr[0] = i_re
        i_im_arr[0] = i_im

        # Dla pozostałych faz używamy 0 (lub moglibyśmy przekazać dane z innych faz)
        # To uproszczenie - w pełnej wersji trzeba przekazać wszystkie 3 fazy naraz
        u_re_arr[1] = 0.0
        u_im_arr[1] = 0.0
        i_re_arr[1] = 0.0
        i_im_arr[1] = 0.0

        u_re_arr[2] = 0.0
        u_im_arr[2] = 0.0
        i_re_arr[2] = 0.0
        i_im_arr[2] = 0.0

        # Wywołaj funkcję DLL
        result = _dll.ZDistA_step(
            self.handle,
            u_re_arr,
            u_im_arr,
            i_re_arr,
            i_im_arr
        )

        # Zaktualizuj stan trip
        if result > 0:
            self.tripped[phase] = True

        return result

    def __del__(self):
        """Destruktor - zwolnij pamięć DLL"""
        if hasattr(self, 'handle') and self.handle:
            _dll.ZDistA_cleanup(self.handle)
            self.handle = None
