#include "reader_analyzer.h"
#include <lib/nfc/protocols/nfc_util.h>
#include <lib/nfc/protocols/mifare_classic.h>
#include <m-array.h>

#include "nfc_debug_pcap.h"
#include "nfc_debug_log.h"

#define TAG "ReaderAnalyzer"

#define READER_ANALYZER_MAX_BUFF_SIZE (1024)

typedef struct {
    bool reader_to_tag;
    bool crc_dropped;
    uint16_t len;
} ReaderAnalyzerHeader;

typedef enum {
    ReaderAnalyzerNfcDataMfClassic,
} ReaderAnalyzerNfcData;

struct ReaderAnalyzer {
    bool alive;
    FuriStreamBuffer* stream;
    FuriThread* thread;

    ReaderAnalyzerParseDataCallback callback;
    void* context;

    ReaderAnalyzerMode mode;
    NfcDebugLog* debug_log;
    NfcDebugPcap* pcap;
};

void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) {
    if(size < sizeof(ReaderAnalyzerHeader)) return;

    size_t bytes_i = 0;
    while(bytes_i < size) {
        ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i];
        uint16_t len = header->len;
        if(bytes_i + len > size) break;
        bytes_i += sizeof(ReaderAnalyzerHeader);
        if(instance->pcap) {
            nfc_debug_pcap_process_data(
                instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped);
        }
        if(instance->debug_log) {
            nfc_debug_log_process_data(
                instance->debug_log,
                &buffer[bytes_i],
                len,
                header->reader_to_tag,
                header->crc_dropped);
        }
        bytes_i += len;
    }
}

int32_t reader_analyzer_thread(void* context) {
    ReaderAnalyzer* reader_analyzer = context;
    uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {};

    while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) {
        size_t ret = furi_stream_buffer_receive(
            reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50);
        if(ret) {
            reader_analyzer_parse(reader_analyzer, buffer, ret);
        }
    }

    return 0;
}

ReaderAnalyzer* reader_analyzer_alloc() {
    ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer));

    instance->alive = false;
    instance->stream =
        furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader));

    instance->thread =
        furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance);
    furi_thread_set_priority(instance->thread, FuriThreadPriorityLow);

    return instance;
}

void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) {
    furi_assert(instance);

    furi_stream_buffer_reset(instance->stream);
    if(mode & ReaderAnalyzerModeDebugLog) {
        instance->debug_log = nfc_debug_log_alloc();
    }
    if(mode & ReaderAnalyzerModeDebugPcap) {
        instance->pcap = nfc_debug_pcap_alloc();
    }

    instance->alive = true;
    furi_thread_start(instance->thread);
}

void reader_analyzer_stop(ReaderAnalyzer* instance) {
    furi_assert(instance);

    instance->alive = false;
    furi_thread_join(instance->thread);

    if(instance->debug_log) {
        nfc_debug_log_free(instance->debug_log);
        instance->debug_log = NULL;
    }
    if(instance->pcap) {
        nfc_debug_pcap_free(instance->pcap);
        instance->pcap = NULL;
    }
}

void reader_analyzer_free(ReaderAnalyzer* instance) {
    furi_assert(instance);

    reader_analyzer_stop(instance);
    furi_thread_free(instance->thread);
    furi_stream_buffer_free(instance->stream);
    free(instance);
}

void reader_analyzer_set_callback(
    ReaderAnalyzer* instance,
    ReaderAnalyzerParseDataCallback callback,
    void* context) {
    furi_assert(instance);
    furi_assert(callback);

    instance->callback = callback;
    instance->context = context;
}

static void reader_analyzer_write(
    ReaderAnalyzer* instance,
    uint8_t* data,
    uint16_t len,
    bool reader_to_tag,
    bool crc_dropped) {
    ReaderAnalyzerHeader header = {
        .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len};
    size_t data_sent = 0;
    data_sent = furi_stream_buffer_send(
        instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever);
    if(data_sent != sizeof(ReaderAnalyzerHeader)) {
        FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader));
    }
    data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever);
    if(data_sent != len) {
        FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len);
    }
}

static void
    reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
    UNUSED(crc_dropped);
    ReaderAnalyzer* reader_analyzer = context;
    uint16_t bytes = bits < 8 ? 1 : bits / 8;
    reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped);
}

static void
    reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
    UNUSED(crc_dropped);
    ReaderAnalyzer* reader_analyzer = context;
    uint16_t bytes = bits < 8 ? 1 : bits / 8;
    reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped);
}

void reader_analyzer_prepare_tx_rx(
    ReaderAnalyzer* instance,
    FuriHalNfcTxRxContext* tx_rx,
    bool is_picc) {
    furi_assert(instance);
    furi_assert(tx_rx);

    if(is_picc) {
        tx_rx->sniff_tx = reader_analyzer_write_rx;
        tx_rx->sniff_rx = reader_analyzer_write_tx;
    } else {
        tx_rx->sniff_rx = reader_analyzer_write_rx;
        tx_rx->sniff_tx = reader_analyzer_write_tx;
    }

    tx_rx->sniff_context = instance;
}
