#include "frame_queue.h"

#include <libavutil/mem.h>
#include <stdlib.h>
#include <string.h>

void frame_data_free(FrameData *frame) {
    if (!frame) {
        return;
    }

    av_freep(&frame->buffer);
    memset(frame, 0, sizeof(*frame));
}

int frame_queue_init(FrameQueue *queue) {
    if (!queue) {
        return -1;
    }

    memset(queue, 0, sizeof(*queue));
    queue->mutex = SDL_CreateMutex();
    queue->cond = SDL_CreateCond();

    return (queue->mutex && queue->cond) ? 0 : -1;
}

void frame_queue_clear(FrameQueue *queue) {
    if (!queue || !queue->mutex) {
        return;
    }

    SDL_LockMutex(queue->mutex);
    while (queue->count > 0) {
        frame_data_free(&queue->frames[queue->head]);
        queue->head = (queue->head + 1) % FRAME_QUEUE_CAPACITY;
        queue->count -= 1;
    }
    queue->head = 0;
    SDL_UnlockMutex(queue->mutex);
}

void frame_queue_destroy(FrameQueue *queue) {
    if (!queue) {
        return;
    }

    frame_queue_clear(queue);
    if (queue->cond) {
        SDL_DestroyCond(queue->cond);
    }
    if (queue->mutex) {
        SDL_DestroyMutex(queue->mutex);
    }
    memset(queue, 0, sizeof(*queue));
}

int frame_queue_push(FrameQueue *queue, FrameData *frame) {
    int tail;

    if (!queue || !frame || !queue->mutex) {
        return -1;
    }

    SDL_LockMutex(queue->mutex);

    if (queue->count == FRAME_QUEUE_CAPACITY) {
        frame_data_free(&queue->frames[queue->head]);
        queue->head = (queue->head + 1) % FRAME_QUEUE_CAPACITY;
        queue->count -= 1;
    }

    tail = (queue->head + queue->count) % FRAME_QUEUE_CAPACITY;
    queue->frames[tail] = *frame;
    memset(frame, 0, sizeof(*frame));
    queue->count += 1;
    SDL_CondSignal(queue->cond);
    SDL_UnlockMutex(queue->mutex);
    return 0;
}

int frame_queue_pop_latest(FrameQueue *queue, FrameData *out) {
    int latest_index;

    if (!queue || !out || !queue->mutex) {
        return 0;
    }

    SDL_LockMutex(queue->mutex);
    if (queue->count == 0) {
        SDL_UnlockMutex(queue->mutex);
        return 0;
    }

    latest_index = (queue->head + queue->count - 1) % FRAME_QUEUE_CAPACITY;
    *out = queue->frames[latest_index];
    memset(&queue->frames[latest_index], 0, sizeof(queue->frames[latest_index]));

    while (queue->count > 0) {
        if (queue->head == latest_index) {
            queue->head = 0;
            queue->count = 0;
            break;
        }

        frame_data_free(&queue->frames[queue->head]);
        queue->head = (queue->head + 1) % FRAME_QUEUE_CAPACITY;
        queue->count -= 1;
    }

    SDL_UnlockMutex(queue->mutex);
    return 1;
}

int frame_queue_peek_first(FrameQueue *queue, FrameData *out) {
    if (!queue || !out || !queue->mutex) {
        return 0;
    }

    SDL_LockMutex(queue->mutex);
    if (queue->count == 0) {
        SDL_UnlockMutex(queue->mutex);
        return 0;
    }

    *out = queue->frames[queue->head];
    SDL_UnlockMutex(queue->mutex);
    return 1;
}

int frame_queue_pop_first(FrameQueue *queue, FrameData *out) {
    if (!queue || !out || !queue->mutex) {
        return 0;
    }

    SDL_LockMutex(queue->mutex);
    if (queue->count == 0) {
        SDL_UnlockMutex(queue->mutex);
        return 0;
    }

    *out = queue->frames[queue->head];
    memset(&queue->frames[queue->head], 0, sizeof(queue->frames[queue->head]));
    queue->head = (queue->head + 1) % FRAME_QUEUE_CAPACITY;
    queue->count -= 1;
    if (queue->count == 0) {
        queue->head = 0;
    }
    SDL_UnlockMutex(queue->mutex);
    return 1;
}
