#include "player.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct Player {
    mpv_handle *mpv;
    unsigned long wid;
    PlayerEventCallback event_callback;
    gpointer event_callback_data;
    gboolean initialized;
};

static void check_error(int status)
{
    if (status < 0) {
        fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
    }
}

Player* player_new(void)
{
    if (load_mpv_library() < 0) {
        return NULL;
    }

    Player *player = g_new0(Player, 1);
    
    player->mpv = mpv_create();
    if (!player->mpv) {
        fprintf(stderr, "Failed to create mpv context\n");
        g_free(player);
        return NULL;
    }
    
    player->wid = 0;
    player->event_callback = NULL;
    player->event_callback_data = NULL;
    player->initialized = FALSE;
    
    return player;
}

void player_destroy(Player *player)
{
    if (!player) return;
    
    if (player->mpv) {
        mpv_terminate_destroy(player->mpv);
    }
    
    g_free(player);
}

void player_set_window(Player *player, unsigned long wid)
{
    if (!player) return;
    player->wid = wid;
}

static void wakeup_callback(void *ctx)
{
    /* This is called from mpv's thread. We need to wake up the GTK main loop. */
    g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)player_process_events, ctx, NULL);
}

int player_init(Player *player)
{
    if (!player || !player->mpv) return -1;
    
    /* Set window ID if provided */
    if (player->wid != 0) {
        char wid_str[64];
        snprintf(wid_str, sizeof(wid_str), "%lu", player->wid);
        check_error(mpv_set_option_string(player->mpv, "wid", wid_str));
    }
    
    /* Basic configuration */
    check_error(mpv_set_option_string(player->mpv, "input-default-bindings", "no"));
    check_error(mpv_set_option_string(player->mpv, "input-vo-keyboard", "no"));
    check_error(mpv_set_option_string(player->mpv, "osc", "no")); /* We have our own controls */
    check_error(mpv_set_option_string(player->mpv, "input-cursor", "no")); /* Let GTK handle the cursor */
    check_error(mpv_set_option_string(player->mpv, "keep-open", "yes"));
    check_error(mpv_set_option_string(player->mpv, "idle", "yes"));
    
    /* Initialize mpv */
    int ret = mpv_initialize(player->mpv);
    if (ret < 0) {
        fprintf(stderr, "Failed to initialize mpv: %s\n", mpv_error_string(ret));
        return ret;
    }
    
    /* Set wakeup callback for event processing */
    mpv_set_wakeup_callback(player->mpv, wakeup_callback, player);
    
    player->initialized = TRUE;
    return 0;
}

/* Playback commands */

void player_load_file(Player *player, const char *path, gboolean append)
{
    if (!player || !player->mpv || !path) return;
    
    const char *cmd[] = {"loadfile", path, append ? "append-play" : "replace", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_play(Player *player)
{
    if (!player || !player->mpv) return;
    
    int flag = 0;
    mpv_set_property_async(player->mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
}

void player_pause(Player *player)
{
    if (!player || !player->mpv) return;
    
    int flag = 1;
    mpv_set_property_async(player->mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
}

void player_toggle_pause(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"cycle", "pause", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_stop(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"stop", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_seek(Player *player, double seconds)
{
    if (!player || !player->mpv) return;
    
    char sec_str[32];
    snprintf(sec_str, sizeof(sec_str), "%f", seconds);
    const char *cmd[] = {"seek", sec_str, "relative", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_seek_absolute(Player *player, double position)
{
    if (!player || !player->mpv) return;
    
    char pos_str[32];
    snprintf(pos_str, sizeof(pos_str), "%f", position);
    const char *cmd[] = {"seek", pos_str, "absolute", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_frame_step(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"frame-step", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_frame_back_step(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"frame-back-step", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_set_option_string(Player *player, const char *name, const char *value)
{
    if (!player || !player->mpv) return;
    check_error(mpv_set_option_string(player->mpv, name, value));
}

void player_set_option_flag(Player *player, const char *name, int value)
{
    if (!player || !player->mpv) return;
    check_error(mpv_set_property_async(player->mpv, 0, name, MPV_FORMAT_FLAG, &value));
}

/* Property getters */

double player_get_duration(Player *player)
{
    if (!player || !player->mpv) return 0.0;
    
    double duration = 0.0;
    mpv_get_property(player->mpv, "duration", MPV_FORMAT_DOUBLE, &duration);
    return duration;
}

double player_get_position(Player *player)
{
    if (!player || !player->mpv) return 0.0;
    
    double position = 0.0;
    mpv_get_property(player->mpv, "time-pos", MPV_FORMAT_DOUBLE, &position);
    return position;
}

double player_get_volume(Player *player)
{
    if (!player || !player->mpv) return 100.0;
    
    double volume = 100.0;
    mpv_get_property(player->mpv, "volume", MPV_FORMAT_DOUBLE, &volume);
    return volume;
}

gboolean player_get_paused(Player *player)
{
    if (!player || !player->mpv) return TRUE;
    
    int paused = 1;
    mpv_get_property(player->mpv, "pause", MPV_FORMAT_FLAG, &paused);
    return paused ? TRUE : FALSE;
}

gboolean player_get_muted(Player *player)
{
    if (!player || !player->mpv) return FALSE;
    
    int muted = 0;
    mpv_get_property(player->mpv, "mute", MPV_FORMAT_FLAG, &muted);
    return muted ? TRUE : FALSE;
}

double player_get_speed(Player *player)
{
    if (!player || !player->mpv) return 1.0;
    
    double speed = 1.0;
    mpv_get_property(player->mpv, "speed", MPV_FORMAT_DOUBLE, &speed);
    return speed;
}

char* player_get_media_title(Player *player)
{
    if (!player || !player->mpv) return NULL;
    
    char *title = NULL;
    mpv_get_property(player->mpv, "media-title", MPV_FORMAT_STRING, &title);
    if (title) {
        char *result = g_strdup(title);
        mpv_free(title);
        return result;
    }
    return NULL;
}

char* player_get_filename(Player *player)
{
    if (!player || !player->mpv) return NULL;
    
    char *filename = NULL;
    mpv_get_property(player->mpv, "filename", MPV_FORMAT_STRING, &filename);
    if (filename) {
        char *result = g_strdup(filename);
        mpv_free(filename);
        return result;
    }
    return NULL;
}

gboolean player_get_eof_reached(Player *player)
{
    if (!player || !player->mpv) return FALSE;
    
    int flag = 0;
    int status = mpv_get_property(player->mpv, "eof-reached", MPV_FORMAT_FLAG, &flag);
    if (status < 0) {
        return FALSE;
    }
    
    return flag ? TRUE : FALSE;
}

gboolean player_is_idle(Player *player)
{
    if (!player || !player->mpv) return TRUE;
    
    int idle = 1;
    if (mpv_get_property(player->mpv, "idle-active", MPV_FORMAT_FLAG, &idle) < 0) {
        return TRUE;
    }
    
    /* Double check: if it says not idle, ensure we have a file loaded */
    if (!idle) {
        char *path = NULL;
        if (mpv_get_property(player->mpv, "path", MPV_FORMAT_STRING, &path) < 0 || !path) {
            return TRUE;
        }
        if (path) mpv_free(path);
    }
    
    return idle ? TRUE : FALSE;
}

char* player_get_metadata_tag(Player *player, const char *tag)
{
    if (!player || !player->mpv || !tag) return NULL;
    
    char prop[128];
    snprintf(prop, sizeof(prop), "metadata/by-key/%s", tag);
    
    char *value = NULL;
    mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &value);
    if (value) {
        char *result = g_strdup(value);
        mpv_free(value);
        return result;
    }
    return NULL;
}

gboolean player_get_shuffle(Player *player)
{
    if (!player || !player->mpv) return FALSE;
    int flag = 0;
    mpv_get_property(player->mpv, "shuffle", MPV_FORMAT_FLAG, &flag);
    return flag ? TRUE : FALSE;
}

gboolean player_get_loop(Player *player)
{
    if (!player || !player->mpv) return FALSE;
    char *value = NULL;
    mpv_get_property(player->mpv, "loop-playlist", MPV_FORMAT_STRING, &value);
    gboolean result = FALSE;
    if (value) {
        if (strcmp(value, "no") != 0) result = TRUE;
        mpv_free(value);
    }
    return result;
}

/* Property setters */

void player_set_volume(Player *player, double volume)
{
    if (!player || !player->mpv) return;
    
    mpv_set_property_async(player->mpv, 0, "volume", MPV_FORMAT_DOUBLE, &volume);
}

void player_set_muted(Player *player, gboolean muted)
{
    if (!player || !player->mpv) return;
    
    int flag = muted ? 1 : 0;
    mpv_set_property_async(player->mpv, 0, "mute", MPV_FORMAT_FLAG, &flag);
}

void player_set_speed(Player *player, double speed)
{
    if (!player || !player->mpv) return;
    
    mpv_set_property_async(player->mpv, 0, "speed", MPV_FORMAT_DOUBLE, &speed);
}

/* Track management */

int player_get_audio_track_count(Player *player)
{
    if (!player || !player->mpv) return 0;
    
    int64_t count = 0;
    mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &count);
    
    /* Count only audio tracks */
    int audio_count = 0;
    for (int i = 0; i < count; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        if (type && strcmp(type, "audio") == 0) {
            audio_count++;
        }
        if (type) mpv_free(type);
    }
    return audio_count;
}

int player_get_subtitle_track_count(Player *player)
{
    if (!player || !player->mpv) return 0;
    
    int64_t count = 0;
    mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &count);
    
    /* Count only subtitle tracks */
    int sub_count = 0;
    for (int i = 0; i < count; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        if (type && strcmp(type, "sub") == 0) {
            sub_count++;
        }
        if (type) mpv_free(type);
    }
    return sub_count;
}

int player_get_current_audio_track(Player *player)
{
    if (!player || !player->mpv) return 0;
    
    int64_t aid = 0;
    mpv_get_property(player->mpv, "aid", MPV_FORMAT_INT64, &aid);
    return (int)aid;
}

int player_get_current_subtitle_track(Player *player)
{
    if (!player || !player->mpv) return 0;
    
    int64_t sid = 0;
    mpv_get_property(player->mpv, "sid", MPV_FORMAT_INT64, &sid);
    return (int)sid;
}

void player_set_audio_track(Player *player, int track)
{
    if (!player || !player->mpv) return;
    
    int64_t aid = track;
    mpv_set_property_async(player->mpv, 0, "aid", MPV_FORMAT_INT64, &aid);
}

void player_set_subtitle_track(Player *player, int track)
{
    if (!player || !player->mpv) return;
    
    int64_t sid = track;
    mpv_set_property_async(player->mpv, 0, "sid", MPV_FORMAT_INT64, &sid);
}

void player_load_subtitle(Player *player, const char *path)
{
    if (!player || !player->mpv || !path) return;
    
    const char *cmd[] = {"sub-add", path, NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

/* Track list helpers */

char** player_get_audio_track_list(Player *player, int *count)
{
    if (!player || !player->mpv || !count) return NULL;
    
    int64_t total = 0;
    mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &total);
    
    /* First pass: count audio tracks */
    int audio_count = 0;
    for (int i = 0; i < total; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        if (type && strcmp(type, "audio") == 0) {
            audio_count++;
        }
        if (type) mpv_free(type);
    }
    
    *count = audio_count;
    if (audio_count == 0) return NULL;
    
    char **list = g_new0(char*, audio_count);
    int idx = 0;
    
    for (int i = 0; i < total && idx < audio_count; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        
        if (type && strcmp(type, "audio") == 0) {
            /* Get track title or lang */
            char title_prop[64], lang_prop[64], id_prop[64];
            snprintf(title_prop, sizeof(title_prop), "track-list/%d/title", i);
            snprintf(lang_prop, sizeof(lang_prop), "track-list/%d/lang", i);
            snprintf(id_prop, sizeof(id_prop), "track-list/%d/id", i);
            
            char *title = NULL, *lang = NULL;
            int64_t id = 0;
            
            mpv_get_property(player->mpv, title_prop, MPV_FORMAT_STRING, &title);
            mpv_get_property(player->mpv, lang_prop, MPV_FORMAT_STRING, &lang);
            mpv_get_property(player->mpv, id_prop, MPV_FORMAT_INT64, &id);
            
            if (title) {
                list[idx] = g_strdup_printf("Track %ld: %s", (long)id, title);
            } else if (lang) {
                list[idx] = g_strdup_printf("Track %ld: %s", (long)id, lang);
            } else {
                list[idx] = g_strdup_printf("Track %ld", (long)id);
            }
            
            if (title) mpv_free(title);
            if (lang) mpv_free(lang);
            idx++;
        }
        if (type) mpv_free(type);
    }
    
    return list;
}

void player_get_video_resolution(Player *player, int *w, int *h)
{
    if (!player || !player->mpv) {
        if (w) *w = 0;
        if (h) *h = 0;
        return;
    }
    
    int64_t width = 0, height = 0;
    mpv_get_property(player->mpv, "dwidth", MPV_FORMAT_INT64, &width);
    mpv_get_property(player->mpv, "dheight", MPV_FORMAT_INT64, &height);
    
    if (w) *w = (int)width;
    if (h) *h = (int)height;
}

char** player_get_subtitle_track_list(Player *player, int *count)
{
    if (!player || !player->mpv || !count) return NULL;
    
    int64_t total = 0;
    mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &total);
    
    /* First pass: count subtitle tracks */
    int sub_count = 0;
    for (int i = 0; i < total; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        if (type && strcmp(type, "sub") == 0) {
            sub_count++;
        }
        if (type) mpv_free(type);
    }
    
    *count = sub_count;
    if (sub_count == 0) return NULL;
    
    char **list = g_new0(char*, sub_count);
    int idx = 0;
    
    for (int i = 0; i < total && idx < sub_count; i++) {
        char prop[64];
        snprintf(prop, sizeof(prop), "track-list/%d/type", i);
        char *type = NULL;
        mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
        
        if (type && strcmp(type, "sub") == 0) {
            char title_prop[64], lang_prop[64], id_prop[64];
            snprintf(title_prop, sizeof(title_prop), "track-list/%d/title", i);
            snprintf(lang_prop, sizeof(lang_prop), "track-list/%d/lang", i);
            snprintf(id_prop, sizeof(id_prop), "track-list/%d/id", i);
            
            char *title = NULL, *lang = NULL;
            int64_t id = 0;
            
            mpv_get_property(player->mpv, title_prop, MPV_FORMAT_STRING, &title);
            mpv_get_property(player->mpv, lang_prop, MPV_FORMAT_STRING, &lang);
            mpv_get_property(player->mpv, id_prop, MPV_FORMAT_INT64, &id);
            
            if (title) {
                list[idx] = g_strdup_printf("Track %ld: %s", (long)id, title);
            } else if (lang) {
                list[idx] = g_strdup_printf("Track %ld: %s", (long)id, lang);
            } else {
                list[idx] = g_strdup_printf("Track %ld", (long)id);
            }
            
            if (title) mpv_free(title);
            if (lang) mpv_free(lang);
            idx++;
        }
        if (type) mpv_free(type);
    }
    
    return list;
}

void player_free_track_list(char **list, int count)
{
    if (!list) return;
    for (int i = 0; i < count; i++) {
        g_free(list[i]);
    }
    g_free(list);
}

/* Playlist */

int player_get_playlist_count(Player *player)
{
    if (!player || !player->mpv) return 0;
    
    int64_t count = 0;
    mpv_get_property(player->mpv, "playlist-count", MPV_FORMAT_INT64, &count);
    return (int)count;
}

int player_get_playlist_pos(Player *player)
{
    if (!player || !player->mpv) return -1;
    
    int64_t pos = -1;
    mpv_get_property(player->mpv, "playlist-pos", MPV_FORMAT_INT64, &pos);
    return (int)pos;
}

void player_playlist_next(Player *player)
{
    if (!player || !player->mpv) return;
    
    if (player_get_playlist_count(player) == 1) {
        player_seek_absolute(player, 0);
    } else {
        const char *cmd[] = {"playlist-next", NULL};
        check_error(mpv_command_async(player->mpv, 0, cmd));
    }
}

void player_playlist_prev(Player *player)
{
    if (!player || !player->mpv) return;
    
    if (player_get_playlist_count(player) == 1) {
        player_seek_absolute(player, 0);
    } else {
        const char *cmd[] = {"playlist-prev", NULL};
        check_error(mpv_command_async(player->mpv, 0, cmd));
    }
}

void player_playlist_play_index(Player *player, int index)
{
    if (!player || !player->mpv) return;
    
    char idx_str[32];
    snprintf(idx_str, sizeof(idx_str), "%d", index);
    const char *cmd[] = {"playlist-play-index", idx_str, NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_playlist_remove(Player *player, int index)
{
    if (!player || !player->mpv) return;
    
    char idx_str[32];
    snprintf(idx_str, sizeof(idx_str), "%d", index);
    const char *cmd[] = {"playlist-remove", idx_str, NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_playlist_move(Player *player, int from, int to)
{
    if (!player || !player->mpv) return;
    
    char from_str[32], to_str[32];
    snprintf(from_str, sizeof(from_str), "%d", from);
    snprintf(to_str, sizeof(to_str), "%d", to);
    const char *cmd[] = {"playlist-move", from_str, to_str, NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_playlist_clear(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"playlist-clear", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_playlist_shuffle(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"playlist-shuffle", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_set_shuffle(Player *player, gboolean shuffle)
{
    if (!player || !player->mpv) return;
    
    int val = shuffle ? 1 : 0;
    check_error(mpv_set_property_async(player->mpv, 0, "shuffle", MPV_FORMAT_FLAG, &val));
}

void player_set_loop(Player *player, gboolean loop)
{
    if (!player || !player->mpv) return;
    
    const char *val = loop ? "inf" : "no";
    check_error(mpv_set_property_async(player->mpv, 0, "loop-playlist", MPV_FORMAT_STRING, &val));
}


/* A-B Loop */

void player_set_ab_loop_a(Player *player)
{
    if (!player || !player->mpv) return;
    
    double pos = player_get_position(player);
    mpv_set_property_async(player->mpv, 0, "ab-loop-a", MPV_FORMAT_DOUBLE, &pos);
}

void player_set_ab_loop_b(Player *player)
{
    if (!player || !player->mpv) return;
    
    double pos = player_get_position(player);
    mpv_set_property_async(player->mpv, 0, "ab-loop-b", MPV_FORMAT_DOUBLE, &pos);
}

void player_clear_ab_loop(Player *player)
{
    if (!player || !player->mpv) return;
    
    /* Reset by setting to "no" */
    mpv_set_property_string(player->mpv, "ab-loop-a", "no");
    mpv_set_property_string(player->mpv, "ab-loop-b", "no");
}

double player_get_ab_loop_a(Player *player)
{
    if (!player || !player->mpv) return -1.0;
    
    double a = -1.0;
    mpv_get_property(player->mpv, "ab-loop-a", MPV_FORMAT_DOUBLE, &a);
    return a;
}

double player_get_ab_loop_b(Player *player)
{
    if (!player || !player->mpv) return -1.0;
    
    double b = -1.0;
    mpv_get_property(player->mpv, "ab-loop-b", MPV_FORMAT_DOUBLE, &b);
    return b;
}

/* Screenshot */

void player_screenshot(Player *player)
{
    if (!player || !player->mpv) return;
    
    const char *cmd[] = {"screenshot", NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

void player_screenshot_to_file(Player *player, const char *path)
{
    if (!player || !player->mpv || !path) return;
    
    const char *cmd[] = {"screenshot-to-file", path, NULL};
    check_error(mpv_command_async(player->mpv, 0, cmd));
}

/* Aspect ratio */

void player_set_aspect_ratio(Player *player, const char *aspect)
{
    if (!player || !player->mpv) return;
    
    mpv_set_property_string(player->mpv, "video-aspect-override", aspect ? aspect : "-1");
}

/* Event handling */

void player_set_event_callback(Player *player, PlayerEventCallback callback, gpointer user_data)
{
    if (!player) return;
    
    player->event_callback = callback;
    player->event_callback_data = user_data;
}

gboolean player_process_events(gpointer data)
{
    Player *player = (Player *)data;
    if (!player || !player->mpv) return G_SOURCE_REMOVE;
    
    while (1) {
        mpv_event *event = mpv_wait_event(player->mpv, 0);
        if (event->event_id == MPV_EVENT_NONE) {
            break;
        }
        
        if (player->event_callback) {
            player->event_callback(player, event, player->event_callback_data);
        }
        
        if (event->event_id == MPV_EVENT_SHUTDOWN) {
            break;
        }
    }
    
    return G_SOURCE_REMOVE;
}

mpv_handle* player_get_mpv_handle(Player *player)
{
    if (!player) return NULL;
    return player->mpv;
}

/* Property observation */

void player_observe_property(Player *player, const char *name, mpv_format format, guint64 reply_userdata)
{
    if (!player || !player->mpv || !name) return;
    
    check_error(mpv_observe_property(player->mpv, reply_userdata, name, format));
}

void player_unobserve_property(Player *player, guint64 reply_userdata)
{
    if (!player || !player->mpv) return;
    
    mpv_unobserve_property(player->mpv, reply_userdata);
}
