#include "ZDistA_wrapper.h"
#include "ZDistA_komp.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

// Dodatkowe includy potrzebne do kompilacji
#include "analog_in.h"

// Funkcja pomocnicza do odczytu stanu binary_io
static inline int get_binary_io_val(struct binary_io *b) {
    if (!b || !b->ptr) return 0;
    return (*b->ptr & b->bit_mask) != 0;
}

// Definicja "uchwytu". Ta struktura przechowuje wszystko, co jest potrzebne do działania algorytmu.
// Jest niewidoczna dla Pythona.
struct ZDistA_handle {
    // Główna struktura logiczna z oryginalnego kodu
    struct ZDistA_komp_logic logic;

    // Pamięć na dane wejściowe (fazory) - wskaźniki w 'logic' będą wskazywać tutaj
    float u_re[3], u_im[3];
    float i_re[3], i_im[3];
    float u12_re, u12_im, u23_re, u23_im, u31_re, u31_im;
    float u_mag[3], i_mag[3], u12_mag, u23_mag, u31_mag;
    
    // Składowe symetryczne
    float su1_re, su1_im, su1_mag;
    float su0_re, su0_im, su0_mag;
    float su2_re, su2_im, su2_mag;
    float si1_re, si1_im, si1_mag;
    float si0_re, si0_im, si0_mag;
    float si2_re, si2_im, si2_mag;

    // Dummy analog_in_params, ponieważ nie mamy pełnego środowiska
    struct analog_in_params param_i;
    struct analog_in_params param_u;

    // Struktura z pustymi wskaźnikami, bo nie jest używana w tym trybie
    struct ZDistA_komp_args args;
};

// Funkcja pomocnicza do ustawiania wskaźników w strukturze 'logic'
static void setup_pointers(ZDistA_handle* h) {
    struct ZDistA_komp_logic* log = &h->logic;

    // Ustawienie wskaźników na dane wejściowe
    log->U1_orta = &h->u_re[0]; log->U1_ortb = &h->u_im[0]; log->U1 = &h->u_mag[0];
    log->U2_orta = &h->u_re[1]; log->U2_ortb = &h->u_im[1]; log->U2 = &h->u_mag[1];
    log->U3_orta = &h->u_re[2]; log->U3_ortb = &h->u_im[2]; log->U3 = &h->u_mag[2];
    
    log->I1_orta = &h->i_re[0]; log->I1_ortb = &h->i_im[0]; log->I1 = &h->i_mag[0];
    log->I2_orta = &h->i_re[1]; log->I2_ortb = &h->i_im[1]; log->I2 = &h->i_mag[1];
    log->I3_orta = &h->i_re[2]; log->I3_ortb = &h->i_im[2]; log->I3 = &h->i_mag[2];

    // Napięcia międzyfazowe
    log->U12_orta = &h->u12_re; log->U12_ortb = &h->u12_im; log->U12 = &h->u12_mag;
    log->U23_orta = &h->u23_re; log->U23_ortb = &h->u23_im; log->U23 = &h->u23_mag;
    log->U31_orta = &h->u31_re; log->U31_ortb = &h->u31_im; log->U31 = &h->u31_mag;

    // Składowe symetryczne
    log->sU1_orta = &h->su1_re; log->sU1_ortb = &h->su1_im; log->sU1 = &h->su1_mag;
    log->sU0_orta = &h->su0_re; log->sU0_ortb = &h->su0_im; log->sU0 = &h->su0_mag;
    log->sU2_orta = &h->su2_re; log->sU2_ortb = &h->su2_im; log->sU2 = &h->su2_mag;
    
    log->sI1_orta = &h->si1_re; log->sI1_ortb = &h->si1_im; log->sI1 = &h->si1_mag;
    log->sI0_orta = &h->si0_re; log->sI0_ortb = &h->si0_im; log->sI0 = &h->si0_mag;
    log->sI2_orta = &h->si2_re; log->sI2_ortb = &h->si2_im; log->sI2 = &h->si2_mag;

    // "Dummy" wskaźniki, aby uniknąć błędów
    log->param_I = &h->param_i;
    log->param_U = &h->param_u;
    
    // Inne wskaźniki, które muszą być zainicjowane
    log->dw.log_ptr = log;
    log->nast_.wyjscie.Zdist_dw = &log->dw;
    log->nast_.wyjscie.on = &log->nast_.on_;
}

DLL_EXPORT ZDistA_handle* ZDistA_init(double z1_r, double z1_x, double line_angle_deg, int kierunek) {
    ZDistA_handle* h = (ZDistA_handle*)malloc(sizeof(ZDistA_handle));
    if (!h) return NULL;

    memset(h, 0, sizeof(ZDistA_handle));
    setup_pointers(h);

    struct Nastawy_przeliczone_ZDistA_komp* nast = &h->logic.nast_;
    nast->on_ = 1; // Włączamy logikę
    
    // Ustawiamy parametry przekładni (hardkodowane, jak w tester.py)
    h->param_u.znam_wtor = 1.0; // Wartości znormalizowane
    h->param_i.znam_wtor = 1.0;
    nast->wyjscie.przekladnia = 1.0;

    // Ustawienia charakterystyk - wzorowane na logice z distance_algorithm_zimba.py
    nast->tanfi1 = tan(line_angle_deg * (3.14159265 / 180.0));
    nast->tanfi2 = tan(85.0 * (3.14159265 / 180.0)); // Typowa wartość

    // Strefa 1
    nast->typ[1] = 0; // Poligonalna
    nast->kierunek[1] = kierunek;
    nast->n_pol[1][0].Rr = z1_r;      // LE
    nast->n_pol[1][0].Xr = z1_x;      // LE
    nast->n_pol[1][1].Rr = z1_r;      // LL
    nast->n_pol[1][1].Xr = z1_x;      // LL

    // Strefy 2-5 z rozsądnymi wartościami domyślnymi
    for (int i = 2; i < 6; ++i) {
        nast->typ[i] = 0;
        nast->kierunek[i] = kierunek;
        nast->n_pol[i][0].Rr = z1_r * (1.0 + (i-1)*0.5);
        nast->n_pol[i][0].Xr = z1_x * (1.0 + (i-1)*0.5);
        nast->n_pol[i][1].Rr = z1_r * (1.0 + (i-1)*0.5);
        nast->n_pol[i][1].Xr = z1_x * (1.0 + (i-1)*0.5);
    }
    
    // Inne ważne parametry
    nast->Igr = 0.1 * 0.1; // Próg prądowy
    nast->kp = 1.05; // Współczynnik powrotu
    nast->kpk = 1.05 * 1.05;
	nast->Igrp = nast->Igr * nast->kpk;

    // Przeliczenie wartości powrotnych i tangensów
    for(int i = 0; i<6; ++i) {
        for(int j=0; j<2; ++j) {
            nast->n_pol[i][j].Rp = nast->n_pol[i][j].Rr / nast->kp;
            nast->n_pol[i][j].Xp = nast->n_pol[i][j].Xr / nast->kp;
            nast->n_pol[i][j].Rrtanfi1 = nast->n_pol[i][j].Rr * nast->tanfi1;
            nast->n_pol[i][j].Rptanfi1 = nast->n_pol[i][j].Rp * nast->tanfi1;
        }
    }
    nast->Xr1f = nast->n_pol[1][0].Xr * (1 + nast->tanfi2 / nast->tanfi1);
	nast->Xr1fp = nast->Xr1f / nast->kp;

    return h;
}

DLL_EXPORT int ZDistA_step(
    ZDistA_handle* handle,
    const float u_re[3], const float u_im[3],
    const float i_re[3], const float i_im[3]
) {
    if (!handle) return 0;
    
    // 1. Skopiuj dane wejściowe do wewnętrznych buforów uchwytu
    for (int i = 0; i < 3; ++i) {
        handle->u_re[i] = u_re[i]; handle->u_im[i] = u_im[i];
        handle->i_re[i] = i_re[i]; handle->i_im[i] = i_im[i];
        handle->u_mag[i] = sqrtf(u_re[i]*u_re[i] + u_im[i]*u_im[i]);
        handle->i_mag[i] = sqrtf(i_re[i]*i_re[i] + i_im[i]*i_im[i]);
    }

    // 2. Oblicz wartości, które w oryginale pochodziły z innych modułów
    // Napięcia międzyfazowe
    handle->u12_re = u_re[0] - u_re[1]; handle->u12_im = u_im[0] - u_im[1];
    handle->u23_re = u_re[1] - u_re[2]; handle->u23_im = u_im[1] - u_im[2];
    handle->u31_re = u_re[2] - u_re[0]; handle->u31_im = u_im[2] - u_im[0];
    handle->u12_mag = sqrtf(handle->u12_re*handle->u12_re + handle->u12_im*handle->u12_im);
    handle->u23_mag = sqrtf(handle->u23_re*handle->u23_re + handle->u23_im*handle->u23_im);
    handle->u31_mag = sqrtf(handle->u31_re*handle->u31_re + handle->u31_im*handle->u31_im);

    // Składowe symetryczne (stałe 'a' i 'a2' dla uproszczenia)
    const float a_re = -0.5, a_im = 0.8660254; // sqrt(3)/2
    
    // I0 = (I1+I2+I3)/3
    handle->si0_re = (i_re[0] + i_re[1] + i_re[2]) / 3.0f;
    handle->si0_im = (i_im[0] + i_im[1] + i_im[2]) / 3.0f;
    
    // I1_zg = (I1 + a*I2 + a2*I3)/3
    handle->si1_re = (i_re[0] + (i_re[1]*a_re - i_im[1]*a_im) + (i_re[2]*a_re - i_im[2]*-a_im)) / 3.0f;
    handle->si1_im = (i_im[0] + (i_re[1]*a_im + i_im[1]*a_re) + (i_re[2]*-a_im + i_im[2]*a_re)) / 3.0f;
    
    // I2_pr = (I1 + a2*I2 + a*I3)/3
    handle->si2_re = (i_re[0] + (i_re[1]*a_re - i_im[1]*-a_im) + (i_re[2]*a_re - i_im[2]*a_im)) / 3.0f;
    handle->si2_im = (i_im[0] + (i_re[1]*-a_im + i_im[1]*a_re) + (i_re[2]*a_im + i_im[2]*a_re)) / 3.0f;

    // To samo dla napięć
    handle->su0_re = (u_re[0] + u_re[1] + u_re[2]) / 3.0f;
    handle->su0_im = (u_im[0] + u_im[1] + u_im[2]) / 3.0f;
    handle->su1_re = (u_re[0] + (u_re[1]*a_re - u_im[1]*a_im) + (u_re[2]*a_re - u_im[2]*-a_im)) / 3.0f;
    handle->su1_im = (u_im[0] + (u_re[1]*a_im + u_im[1]*a_re) + (u_re[2]*-a_im + u_im[2]*a_re)) / 3.0f;
    handle->su2_re = (u_re[0] + (u_re[1]*a_re - u_im[1]*-a_im) + (u_re[2]*a_re - u_im[2]*a_im)) / 3.0f;
    handle->su2_im = (u_im[0] + (u_re[1]*-a_im + u_im[1]*a_re) + (u_re[2]*a_im + u_im[2]*a_re)) / 3.0f;

    // Magnitudy składowych symetrycznych
    handle->si0_mag = sqrtf(handle->si0_re*handle->si0_re + handle->si0_im*handle->si0_im);
    handle->si1_mag = sqrtf(handle->si1_re*handle->si1_re + handle->si1_im*handle->si1_im);
    handle->si2_mag = sqrtf(handle->si2_re*handle->si2_re + handle->si2_im*handle->si2_im);
    handle->su0_mag = sqrtf(handle->su0_re*handle->su0_re + handle->su0_im*handle->su0_im);
    handle->su1_mag = sqrtf(handle->su1_re*handle->su1_re + handle->su1_im*handle->su1_im);
    handle->su2_mag = sqrtf(handle->su2_re*handle->su2_re + handle->su2_im*handle->su2_im);

    // 3. Wywołaj główną funkcję logiki
    ZDistA_komp(NULL, &handle->logic);

    // 4. Sprawdź, czy którekolwiek z wyjść jest aktywne
    struct ZDistA_komp_logic* log = &handle->logic;
    if (get_binary_io_val(&log->P1_L1E) || get_binary_io_val(&log->P1_L2E) || get_binary_io_val(&log->P1_L3E) ||
        get_binary_io_val(&log->P1_L1L2) || get_binary_io_val(&log->P1_L2L3) || get_binary_io_val(&log->P1_L3L1)) {
        return 1;
    }

    return 0;
}

DLL_EXPORT void ZDistA_cleanup(ZDistA_handle* handle) {
    if (handle) {
        free(handle);
    }
}
