#include "mpris.h"
#include <systemd/sd-bus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MPRIS_PATH  "/org/mpris/MediaPlayer2"
#define IFACE_ROOT  "org.mpris.MediaPlayer2"
#define IFACE_PLAYER "org.mpris.MediaPlayer2.Player"

struct MprisPlayer {
    sd_bus      *bus;
    sd_bus_slot *slot_root;
    sd_bus_slot *slot_player;

    char    *identity;
    char    *playback_status;
    double   volume;
    int64_t  position_us;
    int      can_go_next;
    int      can_go_previous;
    int      can_play;
    int      can_pause;
    int      can_seek;

    char    *track_id;
    char    *title;
    char    *artist;
    char    *album;
    int64_t  length_us;

    MprisCallback            on_play;         void *ud_play;
    MprisCallback            on_pause;        void *ud_pause;
    MprisCallback            on_play_pause;   void *ud_play_pause;
    MprisCallback            on_stop;         void *ud_stop;
    MprisCallback            on_next;         void *ud_next;
    MprisCallback            on_previous;     void *ud_previous;
    MprisSeekCallback        on_seek;         void *ud_seek;
    MprisSetPositionCallback on_set_position; void *ud_set_position;
    MprisVolumeCallback      on_volume;       void *ud_volume;
};

/* ── Root interface ─────────────────────────────────────────────────────── */

static int handle_raise(sd_bus_message *m, void *ud, sd_bus_error *e) {
    return sd_bus_reply_method_return(m, "");
}
static int handle_quit(sd_bus_message *m, void *ud, sd_bus_error *e) {
    return sd_bus_reply_method_return(m, "");
}

static int get_identity(sd_bus *bus, const char *path, const char *iface,
                        const char *prop, sd_bus_message *reply,
                        void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "s", ((MprisPlayer *)ud)->identity);
}
static int get_false(sd_bus *bus, const char *path, const char *iface,
                     const char *prop, sd_bus_message *reply,
                     void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", 0);
}
static int get_empty_strv(sd_bus *bus, const char *path, const char *iface,
                          const char *prop, sd_bus_message *reply,
                          void *ud, sd_bus_error *e) {
    int r = sd_bus_message_open_container(reply, 'a', "s");
    if (r < 0) return r;
    return sd_bus_message_close_container(reply);
}

static const sd_bus_vtable root_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("Raise", "", "", handle_raise, SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Quit",  "", "", handle_quit,  SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_PROPERTY("CanQuit",             "b",  get_false,      0, 0),
    SD_BUS_PROPERTY("CanRaise",            "b",  get_false,      0, 0),
    SD_BUS_PROPERTY("HasTrackList",        "b",  get_false,      0, 0),
    SD_BUS_PROPERTY("Identity",            "s",  get_identity,   0, 0),
    SD_BUS_PROPERTY("SupportedUriSchemes", "as", get_empty_strv, 0, 0),
    SD_BUS_PROPERTY("SupportedMimeTypes",  "as", get_empty_strv, 0, 0),
    SD_BUS_VTABLE_END
};

/* ── Player interface ───────────────────────────────────────────────────── */

static int handle_play(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_play) p->on_play(p->ud_play);
    return sd_bus_reply_method_return(m, "");
}
static int handle_pause(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_pause) p->on_pause(p->ud_pause);
    return sd_bus_reply_method_return(m, "");
}
static int handle_play_pause(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_play_pause) p->on_play_pause(p->ud_play_pause);
    return sd_bus_reply_method_return(m, "");
}
static int handle_stop(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_stop) p->on_stop(p->ud_stop);
    return sd_bus_reply_method_return(m, "");
}
static int handle_next(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_next) p->on_next(p->ud_next);
    return sd_bus_reply_method_return(m, "");
}
static int handle_previous(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    if (p->on_previous) p->on_previous(p->ud_previous);
    return sd_bus_reply_method_return(m, "");
}
static int handle_seek(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    int64_t offset;
    int r = sd_bus_message_read(m, "x", &offset);
    if (r < 0) return r;
    if (p->on_seek) p->on_seek(offset, p->ud_seek);
    return sd_bus_reply_method_return(m, "");
}
static int handle_set_position(sd_bus_message *m, void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    const char *track_id;
    int64_t position;
    int r = sd_bus_message_read(m, "ox", &track_id, &position);
    if (r < 0) return r;
    if (p->on_set_position) p->on_set_position(track_id, position, p->ud_set_position);
    return sd_bus_reply_method_return(m, "");
}
static int handle_open_uri(sd_bus_message *m, void *ud, sd_bus_error *e) {
    return sd_bus_reply_method_return(m, "");
}

static int get_playback_status(sd_bus *bus, const char *path, const char *iface,
                                const char *prop, sd_bus_message *reply,
                                void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "s", ((MprisPlayer *)ud)->playback_status);
}

static int get_loop_status(sd_bus *bus, const char *path, const char *iface,
                            const char *prop, sd_bus_message *reply,
                            void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "s", "None");
}

static int get_shuffle(sd_bus *bus, const char *path, const char *iface,
                        const char *prop, sd_bus_message *reply,
                        void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", 0);
}

static int get_metadata(sd_bus *bus, const char *path, const char *iface,
                         const char *prop, sd_bus_message *reply,
                         void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    int r;

    r = sd_bus_message_open_container(reply, 'a', "{sv}");
    if (r < 0) return r;

    /* mpris:trackid is mandatory */
    const char *trackid = p->track_id ? p->track_id : "/org/mpris/MediaPlayer2/TrackList/NoTrack";
    r = sd_bus_message_open_container(reply, 'e', "sv"); if (r < 0) return r;
    r = sd_bus_message_append(reply, "s", "mpris:trackid"); if (r < 0) return r;
    r = sd_bus_message_open_container(reply, 'v', "o"); if (r < 0) return r;
    r = sd_bus_message_append(reply, "o", trackid); if (r < 0) return r;
    r = sd_bus_message_close_container(reply); if (r < 0) return r;
    r = sd_bus_message_close_container(reply); if (r < 0) return r;

    if (p->title) {
        r = sd_bus_message_open_container(reply, 'e', "sv"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", "xesam:title"); if (r < 0) return r;
        r = sd_bus_message_open_container(reply, 'v', "s"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", p->title); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
    }

    /* xesam:artist is an array of strings */
    if (p->artist) {
        r = sd_bus_message_open_container(reply, 'e', "sv"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", "xesam:artist"); if (r < 0) return r;
        r = sd_bus_message_open_container(reply, 'v', "as"); if (r < 0) return r;
        r = sd_bus_message_open_container(reply, 'a', "s"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", p->artist); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
    }

    if (p->album) {
        r = sd_bus_message_open_container(reply, 'e', "sv"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", "xesam:album"); if (r < 0) return r;
        r = sd_bus_message_open_container(reply, 'v', "s"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", p->album); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
    }

    if (p->length_us > 0) {
        r = sd_bus_message_open_container(reply, 'e', "sv"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "s", "mpris:length"); if (r < 0) return r;
        r = sd_bus_message_open_container(reply, 'v', "x"); if (r < 0) return r;
        r = sd_bus_message_append(reply, "x", p->length_us); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
        r = sd_bus_message_close_container(reply); if (r < 0) return r;
    }

    return sd_bus_message_close_container(reply);
}

static int get_volume(sd_bus *bus, const char *path, const char *iface,
                       const char *prop, sd_bus_message *reply,
                       void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "d", ((MprisPlayer *)ud)->volume);
}

static int set_volume(sd_bus *bus, const char *path, const char *iface,
                       const char *prop, sd_bus_message *value,
                       void *ud, sd_bus_error *e) {
    MprisPlayer *p = ud;
    double v;
    int r = sd_bus_message_read(value, "d", &v);
    if (r < 0) return r;
    p->volume = v;
    if (p->on_volume) p->on_volume(v, p->ud_volume);
    return 1; /* signal that the value changed so PropertiesChanged is emitted */
}

static int get_position(sd_bus *bus, const char *path, const char *iface,
                         const char *prop, sd_bus_message *reply,
                         void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "x", ((MprisPlayer *)ud)->position_us);
}

static int get_rate(sd_bus *bus, const char *path, const char *iface,
                     const char *prop, sd_bus_message *reply,
                     void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "d", 1.0);
}

static int get_can_go_next(sd_bus *bus, const char *path, const char *iface,
                            const char *prop, sd_bus_message *reply,
                            void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", ((MprisPlayer *)ud)->can_go_next);
}
static int get_can_go_previous(sd_bus *bus, const char *path, const char *iface,
                                const char *prop, sd_bus_message *reply,
                                void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", ((MprisPlayer *)ud)->can_go_previous);
}
static int get_can_play(sd_bus *bus, const char *path, const char *iface,
                         const char *prop, sd_bus_message *reply,
                         void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", ((MprisPlayer *)ud)->can_play);
}
static int get_can_pause(sd_bus *bus, const char *path, const char *iface,
                          const char *prop, sd_bus_message *reply,
                          void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", ((MprisPlayer *)ud)->can_pause);
}
static int get_can_seek(sd_bus *bus, const char *path, const char *iface,
                         const char *prop, sd_bus_message *reply,
                         void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", ((MprisPlayer *)ud)->can_seek);
}
static int get_can_control(sd_bus *bus, const char *path, const char *iface,
                            const char *prop, sd_bus_message *reply,
                            void *ud, sd_bus_error *e) {
    return sd_bus_message_append(reply, "b", 1);
}

static const sd_bus_vtable player_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("Play",        "",   "", handle_play,         SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Pause",       "",   "", handle_pause,        SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("PlayPause",   "",   "", handle_play_pause,   SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Stop",        "",   "", handle_stop,         SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Next",        "",   "", handle_next,         SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Previous",    "",   "", handle_previous,     SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("Seek",        "x",  "", handle_seek,         SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("SetPosition", "ox", "", handle_set_position, SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_METHOD("OpenUri",     "s",  "", handle_open_uri,     SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_SIGNAL("Seeked", "x", 0),
    SD_BUS_PROPERTY        ("PlaybackStatus", "s",     get_playback_status, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("LoopStatus",     "s",     get_loop_status,     0, 0),
    SD_BUS_PROPERTY        ("Shuffle",        "b",     get_shuffle,         0, 0),
    SD_BUS_PROPERTY        ("Metadata",       "a{sv}", get_metadata,        0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_WRITABLE_PROPERTY("Volume",        "d",     get_volume, set_volume, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("Position",       "x",     get_position,        0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
    SD_BUS_PROPERTY        ("Rate",           "d",     get_rate,            0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("MinimumRate",    "d",     get_rate,            0, 0),
    SD_BUS_PROPERTY        ("MaximumRate",    "d",     get_rate,            0, 0),
    SD_BUS_PROPERTY        ("CanGoNext",      "b",     get_can_go_next,     0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("CanGoPrevious",  "b",     get_can_go_previous, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("CanPlay",        "b",     get_can_play,        0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("CanPause",       "b",     get_can_pause,       0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("CanSeek",        "b",     get_can_seek,        0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_PROPERTY        ("CanControl",     "b",     get_can_control,     0, 0),
    SD_BUS_VTABLE_END
};

/* ── Public API ─────────────────────────────────────────────────────────── */

MprisPlayer *mpris_player_create(const char *player_name, const char *identity) {
    MprisPlayer *p = calloc(1, sizeof(MprisPlayer));
    if (!p) return NULL;

    p->identity        = strdup(identity);
    p->playback_status = strdup("Stopped");
    p->volume          = 1.0;
    p->can_play        = 1;
    p->can_pause       = 1;

    int r;

    r = sd_bus_open_user(&p->bus);
    if (r < 0) { fprintf(stderr, "mpris: D-Bus connect failed: %s\n", strerror(-r)); goto fail; }

    char bus_name[256];
    snprintf(bus_name, sizeof(bus_name), "org.mpris.MediaPlayer2.%s", player_name);

    r = sd_bus_request_name(p->bus, bus_name, 0);
    if (r < 0) { fprintf(stderr, "mpris: could not acquire %s: %s\n", bus_name, strerror(-r)); goto fail; }

    r = sd_bus_add_object_vtable(p->bus, &p->slot_root,
                                  MPRIS_PATH, IFACE_ROOT, root_vtable, p);
    if (r < 0) { fprintf(stderr, "mpris: register root interface failed: %s\n", strerror(-r)); goto fail; }

    r = sd_bus_add_object_vtable(p->bus, &p->slot_player,
                                  MPRIS_PATH, IFACE_PLAYER, player_vtable, p);
    if (r < 0) { fprintf(stderr, "mpris: register player interface failed: %s\n", strerror(-r)); goto fail; }

    return p;

fail:
    mpris_player_destroy(p);
    return NULL;
}

void mpris_player_destroy(MprisPlayer *p) {
    if (!p) return;
    sd_bus_slot_unref(p->slot_player);
    sd_bus_slot_unref(p->slot_root);
    sd_bus_unref(p->bus);
    free(p->identity);
    free(p->playback_status);
    free(p->track_id);
    free(p->title);
    free(p->artist);
    free(p->album);
    free(p);
}

#define EMIT_PLAYER(p, ...) \
    sd_bus_emit_properties_changed((p)->bus, MPRIS_PATH, IFACE_PLAYER, __VA_ARGS__, NULL)

void mpris_set_playback_status(MprisPlayer *p, const char *status) {
    free(p->playback_status);
    p->playback_status = strdup(status);
    EMIT_PLAYER(p, "PlaybackStatus");
}

void mpris_set_metadata(MprisPlayer *p, const char *track_id, const char *title,
                         const char *artist, const char *album, int64_t length_us) {
    free(p->track_id); p->track_id = track_id ? strdup(track_id) : NULL;
    free(p->title);    p->title    = title    ? strdup(title)    : NULL;
    free(p->artist);   p->artist   = artist   ? strdup(artist)   : NULL;
    free(p->album);    p->album    = album     ? strdup(album)    : NULL;
    p->length_us = length_us;
    EMIT_PLAYER(p, "Metadata");
}

void mpris_set_volume(MprisPlayer *p, double volume) {
    p->volume = volume;
    EMIT_PLAYER(p, "Volume");
}

void mpris_set_position(MprisPlayer *p, int64_t position_us) {
    p->position_us = position_us;
    /* Position uses EMITS_INVALIDATION — clients poll it or watch the Seeked signal */
}

void mpris_set_can_go_next(MprisPlayer *p, int v)     { p->can_go_next = v;     EMIT_PLAYER(p, "CanGoNext"); }
void mpris_set_can_go_previous(MprisPlayer *p, int v) { p->can_go_previous = v; EMIT_PLAYER(p, "CanGoPrevious"); }
void mpris_set_can_play(MprisPlayer *p, int v)        { p->can_play = v;        EMIT_PLAYER(p, "CanPlay"); }
void mpris_set_can_pause(MprisPlayer *p, int v)       { p->can_pause = v;       EMIT_PLAYER(p, "CanPause"); }
void mpris_set_can_seek(MprisPlayer *p, int v)        { p->can_seek = v;        EMIT_PLAYER(p, "CanSeek"); }

void mpris_emit_seeked(MprisPlayer *p, int64_t position_us) {
    p->position_us = position_us;
    sd_bus_emit_signal(p->bus, MPRIS_PATH, IFACE_PLAYER, "Seeked", "x", position_us);
}

void mpris_on_play        (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_play = cb;         p->ud_play = ud; }
void mpris_on_pause       (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_pause = cb;        p->ud_pause = ud; }
void mpris_on_play_pause  (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_play_pause = cb;   p->ud_play_pause = ud; }
void mpris_on_stop        (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_stop = cb;         p->ud_stop = ud; }
void mpris_on_next        (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_next = cb;         p->ud_next = ud; }
void mpris_on_previous    (MprisPlayer *p, MprisCallback cb,            void *ud) { p->on_previous = cb;     p->ud_previous = ud; }
void mpris_on_seek        (MprisPlayer *p, MprisSeekCallback cb,        void *ud) { p->on_seek = cb;         p->ud_seek = ud; }
void mpris_on_set_position(MprisPlayer *p, MprisSetPositionCallback cb, void *ud) { p->on_set_position = cb; p->ud_set_position = ud; }
void mpris_on_volume      (MprisPlayer *p, MprisVolumeCallback cb,      void *ud) { p->on_volume = cb;       p->ud_volume = ud; }

int mpris_process(MprisPlayer *p) {
    return sd_bus_process(p->bus, NULL);
}
