#include "ao_playasync.h"

#ifdef WIN32
#include <windows.h>
#define USE_WINDOWS_THREADS
#define sleep_ms(ms) Sleep(ms)
#else
#define USE_PTHREADS
#define sleep_ms(ms) usleep(ms * 1000)
#endif

#ifdef USE_WINDOWS_THREADS
  #define MUTEX_LOCK(m) WaitForSingleObject(m, INFINITE)
  #define MUTEX_UNLOCK(m) ReleaseMutex(m)
#endif

#ifdef USE_PTHREADS
  #include <pthread.h>
  #define MUTEX_LOCK(m) pthread_mutex_lock(&m)
  #define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m)
#endif

#ifndef WIN32
#include <unistd.h>
#endif

#include <assert.h>
#include <malloc.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef enum {
    PLAY = 1,
    STOP = 2
} Command_t;

typedef void * ao_device;

typedef struct _queue_ {
    Command_t command;
    void *buf;
    int buflen;
    double at_second;
    double music_duration;
    struct _queue_ *next;
    struct _queue_ *prev;
} Queue_t;

typedef int(*ao_play_func_t)(void *, char *, uint32_t);

typedef struct {
    Queue_t        *play_queue;
    Queue_t        *last_frame;
    ao_device      *ao_device;
#ifdef USE_WINDOWS_THREADS
    HANDLE          mutex;
    HANDLE          thread;
    DWORD           thread_id;
#endif
#ifdef USE_PTHREADS
    pthread_mutex_t mutex;
    pthread_t       thread;
#endif
    double          at_second;
    double          music_duration;
    ao_play_func_t  ao_play_f;
    int             buf_size;
    int             paused;
} AO_Handle;

//static int(*ao_play)(void *device, char *samples, uint32_t n) = NULL;


static Queue_t *front(AO_Handle *h)
{
    assert(h->play_queue != NULL);
    return h->play_queue;
}

static Queue_t *get(AO_Handle *h)
{
    assert(h->play_queue != NULL);
    Queue_t *q = h->play_queue;
    h->play_queue = h->play_queue->next;
    if (h->play_queue == NULL) {
        h->last_frame = NULL;
    } else {
        h->play_queue->prev = NULL;
    }
    h->buf_size -= q->buflen;
    return q;
}

static void add(AO_Handle *h, Queue_t *elem)
{
    if (h->last_frame == NULL) {
        h->play_queue = elem;
        elem->next = NULL;
        elem->prev = NULL;
        h->last_frame = h->play_queue;
    } else {
        h->last_frame->next = elem;
        elem->prev = h->last_frame;
        elem->next = NULL;
        h->last_frame = elem;
    }
    h->buf_size += elem->buflen;
}

static Queue_t *new_elem(int command, double at_second, double music_duration, int buf_len, void *buf)
{
    Queue_t *q = (Queue_t *) malloc(sizeof(Queue_t));
    void *new_buf;

    if (buf_len != 0) {
        new_buf = (void *) malloc(buf_len);
        memcpy(new_buf, buf, buf_len);
    } else {
        new_buf = NULL;
    }
    q->at_second = at_second;
    q->music_duration = music_duration;
    q->buf = new_buf;
    q->buflen = buf_len;
    q->command = command;
    q->next = NULL;
    q->prev = NULL;
    return q;
}

static void del_elem(Queue_t *q)
{
    if (q->buflen != 0) {
        free(q->buf);
    }
    free(q);
}

static void clear(AO_Handle *h)
{
    while (h->play_queue != NULL) {
        Queue_t *q = get(h);
        del_elem(q);
    }
}

#ifdef USE_PTHREADS
static void *run(void *arg)
#endif
#ifdef USE_WINDOWS_THREADS
static DWORD run(LPVOID arg)
#endif
{
    AO_Handle *handle = (AO_Handle *) arg;

    int go_on = (1 == 1);

    while(go_on) {
        MUTEX_LOCK(handle->mutex);
        int has_frames = (!handle->paused) && (handle->play_queue != NULL);

        if (has_frames) {
            Queue_t *q = get(handle);
            handle->at_second = q->at_second;
            handle->music_duration = q->music_duration;
            MUTEX_UNLOCK(handle->mutex);

            if (q->command == STOP) {
                go_on = (1 == 0);
            } else {
                //fprintf(stderr, "playing buf at %lf\n", handle->at_second);
                handle->ao_play_f(handle->ao_device, q->buf, q->buflen);
            }

            del_elem(q);
        } else {
            MUTEX_UNLOCK(handle->mutex);
            sleep_ms(5);   // sleep for 5ms
        }
    }

#ifdef USE_PTHREADS
    return NULL;
#endif
#ifdef USE_WINDOWS_THREADS
    return 0;
#endif
}

void *ao_create_async(void *ao_device_yeah, void *ao_play_f)
{
    //if (ao_play == NULL) { get_ao_play(); }

    AO_Handle *handle = (AO_Handle *) malloc(sizeof(AO_Handle));

    handle->ao_device = (ao_device *) ao_device_yeah;
    handle->play_queue = NULL;
    handle->last_frame = NULL;
    handle->at_second = -1;

    handle->ao_play_f = ao_play_f;
    handle->buf_size = 0;
    handle->paused = (1 == 0);

#ifdef USE_PTHREADS
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    handle->mutex = m;
    pthread_create(&handle->thread, NULL, run, handle);
#endif

#ifdef USE_WINDOWS_THREADS
    handle->mutex = CreateMutex(NULL,              // default security attributes
                                FALSE,             // initially not owned
                                NULL);
    handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id);
#endif

    return (void *) handle;
}

void ao_stop_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;

    MUTEX_LOCK(h->mutex);
    clear(h);
    Queue_t *q = new_elem(STOP, 0.0, 0.0, 0, NULL);
    add(h, q);
    MUTEX_UNLOCK(h->mutex);

#ifdef USE_PTHREADS
    void *retval;
    pthread_join(h->thread, &retval);
#endif
#ifdef USE_WINDOWS_THREADS
    WaitForSingleObject(h->thread, INFINITE);
    CloseHandle(h->thread);
    CloseHandle(h->mutex);
#endif
    free(h);
}

void ao_play_async(void *ao_handle, double at_second, double music_duration, int buf_size, void *mem)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    Queue_t *q = new_elem(PLAY, at_second, music_duration, buf_size, mem);
    MUTEX_LOCK(h->mutex);
    add(h, q);
    MUTEX_UNLOCK(h->mutex);
}

void ao_clear_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    clear(h);
    MUTEX_UNLOCK(h->mutex);
}

double ao_is_at_second_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    double s = h->at_second;
    MUTEX_UNLOCK(h->mutex);
    return s;
}

double ao_music_duration_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    double duration = h->music_duration;
    MUTEX_UNLOCK(h->mutex);
    return duration;
}

int ao_bufsize_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    int s = h->buf_size;
    MUTEX_UNLOCK(h->mutex);
    return s;
}

void ao_pause_async(void *ao_handle, int paused)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    h->paused = paused;
    MUTEX_UNLOCK(h->mutex);
}


