#include "bluespy.h"
#include <string>

void callback(bluespy_event_id id);

#ifndef EXPORT
#if defined _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
#endif

bool my_global;

extern "C" {
// The function `init` is called when blueSPY starts up. We register our callbacks then
EXPORT void init() { bluespy_register_event_callback(bluespy_event_custom, callback); }
// The function `reset` is called when blueSPY starts a new capture. Use it to reset any global
// state you have. If you have no global state, you don't need to provide this funtion
EXPORT void reset() { my_global = true; }

bluespy_custom_query queries[1] = {{.query = "something_special",
                                    .pretty_name = "Something Special",
                                    .description = "A special value with mysterious origins"}};

EXPORT size_t registerCustomQueries(bluespy_custom_query** q) {
    *q = queries;
    return 1;
}
}

// We are defining our own event type so we can attach some extra data to the event
// You likely won't need to do this and can just use bluespy_custom_event directly
// You may want the extra data to record the state of a state machine when the packet arrived
// or to perform a computation only once (rather than each time `query` is called)
struct MyCustomEvent : bluespy_custom_event {
    std::string str;
    bool encrypted;
    bool corrupt;
};

// Queries are how blueSPY will get information from your custom event to show in the UI
// You don't have to implement any queries
bluespy_query_value query(const bluespy_custom_event* self_, const char* query_c,
                          bool prefer_string) {
    std::string query{query_c};
    const MyCustomEvent* self = static_cast<const MyCustomEvent*>(self_);
    if (query == "summary") {
        // The summary query is used to determine what to show in the summary column
        return (bluespy_query_value){.type = bluespy_query_type_string, .s = self->str.c_str()};
    } else if (query == "details") {
        // The "details" query is a special query for custom events
        // It needs to return a specific json structure which is used to determine what to show in
        // the details tab
        auto b = bluespy_query_bytes(self->children[0], "payload");
        return (bluespy_query_value){
            .type = bluespy_query_type_string,
            .s = "[{\"name\": \"Foo\", \"value\": 10, \"bitpos\": 0, \"bitwidth\": 8},"
                 "{\"name\": \"Bar\", \"bitpos\": 8, \"value\": "
                 "[{\"name\": \"Baz\", \"value\": \"Qux\", \"bitpos\": 0, \"bitwidth\": 8},"
                 "{\"name\": \"Quux\", \"value\": true, \"bitpos\": 8, \"bitwidth\": 16}]}]"};
    } else if (query == "time") {
        // We can return other types, giving them a string representation is optional.
        // If you want default string formatting, return bluespy_query_type_int even when
        // prefer_string is true.
        if (prefer_string)
            return (bluespy_query_value){
                .type = bluespy_query_type_string,
                .s = "3 O'Clock on a tuesday",
            };
        else
            return (bluespy_query_value){.type = bluespy_query_type_int, .i = 1234};
    } else if (query == "payload") {
        // Just use the payload from the child. This example is the same as the default behaviour;
        // not returning anything for "payload" will use the child's payload. If we had multiple
        // children we could join up the payloads or something more interesting here
        return (bluespy_query_value){.type = bluespy_query_type_bytes,
                                     .bytes = bluespy_query_bytes(self->children[0], "payload")};
    } else if (query == "custom_source") {
        // This is useful when you have multiple custom decoders and want to identify where a custom
        // event has come from. This is necessary when you have custom decoder that act on custom
        // events (such as "example_custom_decoder.cpp") so that they don't trigger on themselves
        return (bluespy_query_value){.type = bluespy_query_type_string,
                                     .s = "example_custom_decoder.cpp"};
    } else if (query == "encrypted") {
        return (bluespy_query_value){.type = bluespy_query_type_bool, .b = self->encrypted};
    } else if (query == "corrupt") {
        return (bluespy_query_value){.type = bluespy_query_type_bool, .b = self->corrupt};
    } else if (query == "something_special") {
        return (bluespy_query_value){.type = bluespy_query_type_int, .i = 10};
    } else {
        // Return nothing for all other queries
        return (bluespy_query_value){.type = bluespy_query_type_none};
    }
}

bool encryption_dectection(bluespy_bytes payload) {
    if (payload.len == 0)
        return false;
    return (*payload.data) & 1;
}

bool CRC_failed(bluespy_bytes payload) {
    if (payload.len == 0)
        return false;
    return ((*payload.data) & 1) != ((*payload.data) >> 7);
}

void callback(bluespy_event_id id) {
    auto source = std::string(bluespy_query(id, "custom_source"));
    if (source == "example_custom_decoder.cpp")
        return; // We don't want to trigger on our own events

    MyCustomEvent* event = bluespy::allocate<MyCustomEvent>();

    event->children = bluespy::allocate<bluespy_event_id>();
    event->children[0] = id;
    event->n_children = 1;
    event->query = query;

    // If we used automatic here, it would go on the Transaction layer. This is because the C
    // example uses automatic and operates on baseband packets so is put one layer above on Logical.
    // This C++ example has the packets from the C example as children so would go one layer above
    // that on Transaction.
    event->layer = bluespy_custom_event_layer_application;

    std::string str = "Hello world from C++!";
    if (my_global) {
        str += " I am the first packet!!!";
        my_global = false;
    }

    event->str = str;

    auto q = bluespy_query_get(id, "payload");
    if (q.type == bluespy_query_type_bytes) {
        event->corrupt = CRC_failed(q.bytes);
        event->encrypted = event->corrupt ? false : encryption_dectection(q.bytes);
    } else {
        event->corrupt = false;
        event->encrypted = false;
    }

    bluespy_add_event(event);
}

EXPORT void final() {
    // The capture has ended
    // You would probably want to tidy up any ongoing higher level events here and emit events for
    // them
}
