// yabridge: a Wine plugin bridge
// Copyright (C) 2020-2026 Robbert van der Helm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#pragma once

#include <string>
#include <variant>

#include <bitsery/traits/string.h>
#include <clap/events.h>
#include <llvm/small-vector.h>

#include "../bitsery/ext/in-place-variant.h"
#include "../bitsery/ext/native-pointer.h"
#include "../bitsery/traits/small-vector.h"
#include "../common.h"

// Serialization messages for `clap/events.h`

namespace clap {
namespace events {

/**
 * The actual event data. `clap::events::Event` stores these as a variant.
 * Ideally we'd store only the non-header payload data, but the
 * `clap_input_events::get()` function requires us to return a pointer to the
 * header, so if we did that then we'd need to create a second buffer containing
 * the serialzed events.
 */
namespace payload {

/**
 * The payload for `clap_event_note`. This is used for multiple event types,
 * which are encoded through `event.header.type`.
 */
struct Note {
    clap_event_note_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_note_expression`.
 */
struct NoteExpression {
    clap_event_note_expression_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_param_value`.
 */
struct ParamValue {
    clap_event_param_value_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_param_mod`.
 */
struct ParamMod {
    clap_event_param_mod_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_param_gesture`. This is used for multiple event
 * types, which are encoded through `event.header.type`.
 */
struct ParamGesture {
    clap_event_param_gesture_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_transport`.
 */
struct Transport {
    clap_event_transport_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_midi`.
 */
struct Midi {
    clap_event_midi_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

/**
 * The payload for `clap_event_midi_sysex`.
 */
struct MidiSysex {
    clap_event_midi_sysex_t event;

    /**
     * The actual SysEx event data. The pointer in `event` is set to the string
     * data after the event has been created. As long as this event is not moved
     * that pointer will remain valid.
     *
     * We're not expecting a lot of SysEx events, and `std::string`'s small
     * string optimization should make it possible to send small sysex events
     * without allocations. An alternative that won't allocate as quickly would
     * be to store the data in a vector and to only store a tag here, but I
     * don't think it's necessary at the moment.
     */
    std::string buffer;

    template <typename S>
    void serialize(S& s) {
        s.object(event.header);
        s.value2b(event.port_index);

        s.text1b(buffer, 1 << 16);

        // NOTE: These will need to be set when retrieving the event using
        //       `clap_input_events::get()`. We could set the pointer here, but
        //       in the off chance that there are a lot more events than we can
        //       handle and the vector is reallocated to avoid dropping events,
        //       then these pointers would become dangling. Making sure these
        //       are null until the event is retrieved is probably for the best.
        event.buffer = nullptr;
        event.size = 0;
    }
};

/**
 * The payload for `clap_event_midi2`.
 */
struct Midi2 {
    clap_event_midi2_t event;

    template <typename S>
    void serialize(S& s) {
        s.object(event);
    }
};

}  // namespace payload

/**
 * Encodes a CLAP event. These can be parsed from a `clap_event_header_t*` and
 * reconstructed back to a `clap_event_header`.
 */
struct alignas(16) Event {
    /**
     * Parse a CLAP event. Returns a nullopt if yabridge does not support the
     * event.
     */
    static std::optional<Event> parse(const clap_event_header_t& generic_event);

    /**
     * Get the `clap_event_header_t*` representation for this event. The pointer
     * is valid as long as this struct isn't moved.
     */
    const clap_event_header_t* get() const;

    /**
     * The actual event data. These also contain the header because storing the
     * entire `clap_event_*_t` struct is the only way to serialize the event
     * list in a way that doesn't require us to create a second event list in
     * that format after deserializing the events. An alternative would be to
     * write the event in the proper format to a buffer before returning it from
     * `clap_input_events::get()`, but that would cause unexpected lifetime
     * issues.
     */
    mutable std::variant<
        payload::Note,
        payload::NoteExpression,
        payload::ParamValue,
        payload::ParamMod,
        payload::ParamGesture,
        // Most events are about the same length, but having the transport in
        // here sadly doubles this struct's size
        // TODO: Pack the events at some point, this will require special
        //       handling for SysEx events
        payload::Transport,
        payload::Midi,
        payload::MidiSysex,
        payload::Midi2>
        payload;

    template <typename S>
    void serialize(S& s) {
        s.ext(payload, bitsery::ext::InPlaceVariant{});
    }
};

/**
 * A list storing one or more CLAP events. Can be used for both input and output
 * events.
 */
class EventList {
   public:
    /**
     * We only provide a default constructor here, because we need to fill the
     * existing object with new events every processing cycle to avoid
     * reallocating a new object every time.
     */
    EventList() noexcept;

    /**
     * Read data from a `clap_input_events_t` into this existing object. This
     * minimizes reallocations by keeping `events_` as is.
     */
    void repopulate(const clap_input_events_t& in_events);

    /**
     * Remove all events. Used at the start of the process function for the
     * output event list.
     */
    void clear() noexcept;

    /**
     * Write the stored events to the host's `clap_output_events_t`.
     */
    void write_back_outputs(const clap_output_events_t& out_events) const;

    /**
     * Get a `clap_input_events_t` interface for this event list that the plugin
     * can read the events from. This is only valid as long as this object is
     * not moved.
     */
    const clap_input_events_t* input_events();
    /**
     * Get a `clap_output_events_t` interface for this event list that the
     * plugin can push events to. This is only valid as long as this object is
     * not moved.
     */
    const clap_output_events_t* output_events();

    static uint32_t CLAP_ABI in_size(const struct clap_input_events* list);
    static const clap_event_header_t* CLAP_ABI
    in_get(const struct clap_input_events* list, uint32_t index);

    static bool CLAP_ABI out_try_push(const struct clap_output_events* list,
                                      const clap_event_header_t* event);

    /**
     * Return the number of events we store. Used in debug logs.
     */
    inline size_t size() const noexcept { return events_.size(); }

    template <typename S>
    void serialize(S& s) {
        s.container(events_, 1 << 16);
    }

   private:
    llvm::SmallVector<Event, 64> events_;

    // These are populated in the `input_events()` and `output_events()` methods
    clap_input_events_t input_events_vtable_{};
    clap_output_events_t output_events_vtable_{};
};

}  // namespace events
}  // namespace clap

template <typename S>
void serialize(S& s, clap_event_header_t& event_header) {
    // Feels a bit weird serializing this, but assuming the host/plugin set it
    // correctly it will be fine. And this is kind of a host implementation
    // detail for storing the events in a packed list anyways.
    s.value4b(event_header.size);
    s.value4b(event_header.time);
    s.value2b(event_header.space_id);
    s.value2b(event_header.type);
    s.value4b(event_header.flags);
}

template <typename S>
void serialize(S& s, clap_event_note_t& event) {
    s.object(event.header);
    s.value4b(event.note_id);
    s.value2b(event.port_index);
    s.value2b(event.channel);
    s.value2b(event.key);
    s.value8b(event.velocity);
}

template <typename S>
void serialize(S& s, clap_event_note_expression_t& event) {
    s.object(event.header);
    s.value4b(event.expression_id);
    s.value4b(event.note_id);
    s.value2b(event.port_index);
    s.value2b(event.channel);
    s.value2b(event.key);
    s.value8b(event.value);
}

template <typename S>
void serialize(S& s, clap_event_param_value_t& event) {
    s.object(event.header);
    s.value4b(event.param_id);
    // The cookie is a pointer. Using `native_size_t`/the host system's pointer
    // size here will allow bridged 32-bit plugins to work correctly.
    // XXX: This will silently blow up when using 32-bit yabridge on a 64-bit
    //      system with 64-bit plugins, but that's such a specific use case that
    //      we won't even bother. Building 32-bit yabridge with CLAP support on
    //      64-bit symbols has been disabled to prevent this from being an
    //      issue.
    s.ext(event.cookie, bitsery::ext::NativePointer{});
    s.value4b(event.note_id);
    s.value2b(event.port_index);
    s.value2b(event.channel);
    s.value2b(event.key);
    s.value8b(event.value);
}

template <typename S>
void serialize(S& s, clap_event_param_mod_t& event) {
    s.object(event.header);
    s.value4b(event.param_id);
    // Same as the above
    s.ext(event.cookie, bitsery::ext::NativePointer{});
    s.value4b(event.note_id);
    s.value2b(event.port_index);
    s.value2b(event.channel);
    s.value2b(event.key);
    s.value8b(event.amount);
}

template <typename S>
void serialize(S& s, clap_event_param_gesture_t& event) {
    s.object(event.header);
    s.value4b(event.param_id);
}

template <typename S>
void serialize(S& s, clap_event_transport_t& event) {
    s.object(event.header);
    s.value4b(event.flags);
    s.value8b(event.song_pos_beats);
    s.value8b(event.song_pos_seconds);
    s.value8b(event.tempo);
    s.value8b(event.tempo_inc);
    s.value8b(event.loop_start_beats);
    s.value8b(event.loop_end_beats);
    s.value8b(event.loop_start_seconds);
    s.value8b(event.loop_end_seconds);
    s.value8b(event.bar_start);
    s.value4b(event.bar_number);
    s.value2b(event.tsig_num);
    s.value2b(event.tsig_denom);
}

template <typename S>
void serialize(S& s, clap_event_midi_t& event) {
    s.object(event.header);
    s.value2b(event.port_index);
    s.container1b(event.data);
}

// `clap_event_midi_sysex_t` can't be serialized without special handling, so
// we'll serialize it directly inside of `clap::events::payload::MidiSysex` to
// reduce the risk of this causing problems in the future

template <typename S>
void serialize(S& s, clap_event_midi2_t& event) {
    s.object(event.header);
    s.value2b(event.port_index);
    s.container4b(event.data);
}
