/**
 * receiver.c — OFDM-приёмник с RS(255,223) FEC и частотной коррекцией
 */

#include "common.h"

#define BUF_SAMPLES      32768
#define MAX_FRAME_SAMPLES (1 << 16)
#define RX_GAIN          50.0
#define CFO_FILTER_ALPHA 0.95f
#define CFO_MAX_VALID    0.5f

extern volatile sig_atomic_t stop;

static uint64_t calls = 0, hdr_ok = 0, crc_raw = 0;
static uint64_t rs_saved = 0, crc_final = 0, total_bytes = 0;
static float rssi = -100, evm = 0;
static float cfo_est = 0.0f;
static nco_crcf nco = NULL;
static fec decoder = NULL;
static int fec_on = 0;
static liquid_float_complex frame[MAX_FRAME_SAMPLES];

static int callback(unsigned char *hdr, int hdr_valid,
                    unsigned char *pay, unsigned int pay_len,
                    int pay_valid, framesyncstats_s stats,
                    void *user) {
    (void)user;
    calls++;
    rssi = stats.rssi;
    evm = stats.evm;

    // Обновляем CFO только если NCO инициализирован
    if (nco && fabsf(stats.cfo) < CFO_MAX_VALID) {
        cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo;
        nco_crcf_set_frequency(nco, cfo_est);
    }

    if (!hdr_valid) return 0;
    hdr_ok++;
    if (pay_valid) crc_raw++;

    uint16_t original_len = (hdr[6] << 8) | hdr[7];
    uint8_t last_block_bytes = hdr[10];

    if (fec_on && decoder && pay_len >= RS_ENC) {
        size_t nblocks = pay_len / RS_ENC;
        if (nblocks == 0 || nblocks > 100) return 0;

        uint8_t recovered[4096];
        int rs_ok = 1;
        for (size_t b = 0; b < nblocks; b++) {
            if (fec_decode(decoder, RS_DATA, pay + b * RS_ENC,
                          recovered + b * RS_DATA) != 0) {
                rs_ok = 0; break;
            }
        }
        if (rs_ok) {
            if (!pay_valid) rs_saved++;
            size_t out_len = (nblocks - 1) * RS_DATA +
                           (last_block_bytes > 0 ? last_block_bytes : RS_DATA);
            if (out_len > nblocks * RS_DATA) out_len = nblocks * RS_DATA;
            if (out_len > original_len) out_len = original_len;
            if (out_len > 0) {
                fwrite(recovered, 1, out_len, stdout);
                fflush(stdout);
                total_bytes += out_len;
                crc_final++;
            }
        }
    } else if (pay_valid && original_len > 0 && original_len <= pay_len) {
        fwrite(pay, 1, original_len, stdout);
        fflush(stdout);
        total_bytes += original_len;
        crc_final++;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
    const char *uri = RX_URI_DEFAULT;
    const char *fec_opt = "rs8";

    int opt;
    while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) {
        switch (opt) {
            case 'f': freq = atoll(optarg); break;
            case 'r': rate = atoll(optarg); break;
            case 'b': bw = atoll(optarg); break;
            case 'u': uri = optarg; break;
            case 'c': fec_opt = optarg; break;
            case 'h':
                print_usage(argv[0], "ПРИЁМНИК");
                fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ RX:\n");
                fprintf(stderr, "  Усиление фиксированное: %.0f дБ\n", RX_GAIN);
                fprintf(stderr, "  Частотная коррекция: адаптивная (α=%.2f)\n", CFO_FILTER_ALPHA);
                fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
                fprintf(stderr, "  %s -f %lld -r %lld -b %lld -c rs8 > output.bin\n",
                        argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW);
                return 0;
            default: return 1;
        }
    }

    fec_on = !strcmp(fec_opt, "rs8");
    setup_signal_handlers();

    print_config("ПРИЁМНИК", freq, rate, bw, fec_on);
    fprintf(stderr, "Усиление:    %.0f дБ (ручное)\n", RX_GAIN);
    fprintf(stderr, "CFO фильтр:  α=%.2f\n", CFO_FILTER_ALPHA);
    fprintf(stderr, "URI:         %s\n", uri);
    fprintf(stderr, "═══════════════════════════════════════════════════\n");

    struct iio_context *ctx = NULL;
    struct iio_device *phy = NULL, *rx = NULL;
    if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1;

    pluto_configure(phy, freq, rate, bw, RX_GAIN, 0);

    iio_channel_enable(iio_device_find_channel(rx, "voltage0", false));
    iio_channel_enable(iio_device_find_channel(rx, "voltage1", false));

    struct iio_buffer *buf = iio_device_create_buffer(rx, BUF_SAMPLES, false);
    if (!buf) {
        fprintf(stderr, "Ошибка создания буфера приёма\n");
        iio_context_destroy(ctx);
        return 1;
    }

    // Инициализация FEC
    if (fec_on) {
        decoder = fec_create(LIQUID_FEC_RS_M8, NULL);
        if (!decoder) {
            fprintf(stderr, "Ошибка создания FEC-декодера\n");
        }
    }

    // Инициализация NCO для частотной коррекции
    nco = nco_crcf_create(LIQUID_NCO);
    if (!nco) {
        fprintf(stderr, "Ошибка создания NCO\n");
    } else {
        nco_crcf_set_frequency(nco, 0.0f);
    }

    ofdmflexframesync fs = ofdmflexframesync_create(
        OFDM_M, OFDM_CP, OFDM_TAPER, NULL, callback, NULL);
    if (!fs) {
        fprintf(stderr, "Ошибка создания OFDM-синхронизатора\n");
        if (decoder) fec_destroy(decoder);
        if (nco) nco_crcf_destroy(nco);
        iio_buffer_destroy(buf);
        iio_context_destroy(ctx);
        return 1;
    }

    fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
    fprintf(stderr, "Кадры   | Загол. | CRC  | RS испр | Вывод  | RSSI  | EVM   | CFO\n");
    fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n");

    time_t last = time(NULL);

    while (!stop) {
        ssize_t n = iio_buffer_refill(buf);
        if (n < 0) { usleep(500); continue; }

        int16_t *bb = (int16_t *)iio_buffer_start(buf);
        int ns = (iio_buffer_end(buf) - iio_buffer_start(buf)) / sizeof(int16_t) / 2;
        if (ns > MAX_FRAME_SAMPLES) ns = MAX_FRAME_SAMPLES;

        for (int i = 0; i < ns; i++) {
            frame[i] = ((float)bb[2*i] / 2048.0f) +
                      ((float)bb[2*i+1] / 2048.0f) * _Complex_I;
            if (nco) {
                nco_crcf_mix_down(nco, frame[i], &frame[i]);
                nco_crcf_step(nco);
            }
        }
        if (nco) nco_crcf_set_phase(nco, 0.0f);

        ofdmflexframesync_execute(fs, frame, ns);

        if (time(NULL) > last) {
            fprintf(stderr, "\r%6llu | %5llu | %4llu | %6llu | %6llu | %+5.0f | %+5.1f | %+8.4f",
                    (unsigned long long)calls, (unsigned long long)hdr_ok,
                    (unsigned long long)crc_raw, (unsigned long long)rs_saved,
                    (unsigned long long)crc_final, rssi, evm, cfo_est);
            fflush(stderr);
            last = time(NULL);
        }
    }

    fprintf(stderr, "\n\nПРИЁМ ЗАВЕРШЁН\n");
    fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | Вывод: %llu | Байт: %llu\n",
            (unsigned long long)calls, (unsigned long long)hdr_ok,
            (unsigned long long)crc_raw, (unsigned long long)rs_saved,
            (unsigned long long)crc_final, (unsigned long long)total_bytes);

    if (decoder) fec_destroy(decoder);
    if (nco) nco_crcf_destroy(nco);
    ofdmflexframesync_destroy(fs);
    iio_buffer_destroy(buf);
    iio_context_destroy(ctx);
    return 0;
}