#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>
  #include <semaphore.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>
#include <stdint.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;
    sem_t           queue_sem;

    int             paused;

    ao_device      *ao_device;
#ifdef USE_WINDOWS_THREADS
    HANDLE          mutex;
    HANDLE          pause_mutex;
    HANDLE          thread;
    DWORD           thread_id;
#endif
#ifdef USE_PTHREADS
    pthread_mutex_t mutex;
    pthread_mutex_t pause_mutex;
    pthread_t       thread;
#endif
    double          at_second;
    double          music_duration;
    ao_play_func_t  ao_play_f;
    int             buf_size;
} 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)
{
    sem_wait(&h->queue_sem);
    MUTEX_LOCK(h->mutex);
    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;
    MUTEX_UNLOCK(h->mutex);
    return q;
}

static void add(AO_Handle *h, Queue_t *elem)
{
    MUTEX_LOCK(h->mutex);
    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;
    MUTEX_UNLOCK(h->mutex);
    sem_post(&h->queue_sem);
}

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_t) 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->pause_mutex);
        MUTEX_UNLOCK(handle->pause_mutex);

        Queue_t *q = get(handle);

        handle->at_second = q->at_second;
        handle->music_duration = q->music_duration;

        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, (char *) q->buf, q->buflen);
        }

        del_elem(q);
    }

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

int ao_async_version()
{
  return VERSION;
}

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_func_t) ao_play_f;
    handle->buf_size = 0;

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

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

#ifdef USE_WINDOWS_THREADS
    handle->mutex = CreateMutex(NULL,              // default security attributes
                                FALSE,             // initially not owned
                                NULL);
    handle->pause_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);
    // TODO Windows Semaphores!
#endif

    MUTEX_UNLOCK(handle->pause_mutex);

    return (void *) handle;
}

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

    clear(h);
    Queue_t *q = new_elem(STOP, 0.0, 0.0, 0, NULL);
    add(h, q);

#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);
}

/*
(define (abs x) (if (>= x 0) x (* x -1)))

(define (make-sample-bytes sample bytes-per-sample endianess)
  (letrec ((mk (lambda (i d)
                 (if (< i bytes-per-sample)
                     (cons (bitwise-and d 255)
                           (mk (+ i 1) (arithmetic-shift d -8)))
                     '()))))
    (let ((bytes (mk 0 sample)))
      (if (eq? endianess 'big-endian)
          (reverse bytes)
          bytes))))

         ;(get-sample (lambda (k channel)
         ;              (let ((chan-buf (list-ref buffer channel)))
         ;                (vector-ref chan-buf k))))
         )
    ;(letrec ((i 0)
    ;         (fill (lambda (k channel)
    ;                 (if (< k buf-len)
    ;                     (if (< channel channels)
    ;                         (let* ((sample (get-sample k channel))
    ;                                (bytes (make-sample-bytes sample bytes-per-sample endianess))
    ;                                )
    ;                           (for-each (lambda (byte)
    ;                                       (ptr-set! audio _byte i byte)
    ;                                       (set! i (+ i 1)))
    ;                                     bytes)
    ;                           ;; process sample
    ;                           (fill k (+ channel 1)))
    ;                         (fill (+ k 1) 0))
    ;                     'filled))
    ;               ))
    ;  (fill 0 0)

*/

#define AO_FMT_LITTLE 1
#define AO_FMT_BIG    2
#define AO_FMT_NATIVE 4

static inline void make_sample_bytes(int32_t sample, int bytes_per_sample, int endianess, unsigned char b[4])
{
  int i;
  for (i = 0; i < bytes_per_sample; i++) {
     b[i] = sample&0xff;
     sample = sample >> 8;
  }
  if (endianess == AO_FMT_BIG) {
     unsigned char b1[4] = { 0, 0, 0, 0 };
     for(i = 0; i < bytes_per_sample; i++) {
        b1[bytes_per_sample - i - 1] = b[i];
     }
     for(i = 0; i < bytes_per_sample; i++) {
        b[i] = b1[i];
     }
  }
}

void *convertFlac(void *mem, int buf_len, BufferInfo_t *info, int *audio_size)
{
  // buf_size equals number of samples of 32bit for all channels. So buf_size for flac = 4 * buf_len * channels

  int bytes = info->sample_bits / 8;
  int endianness = info->endiannes;
  int store_size = info->channels * bytes * buf_len;
  unsigned char *new_mem = (unsigned char *) malloc(store_size);
  *audio_size = store_size;
  int32_t **buffer = (int32_t **) mem;
  int i, k, channel;

  i = 0;
  for(k = 0; k < buf_len; k++) {
    for(channel = 0; channel < info->channels; channel++) {
      int32_t *chan = buffer[channel];
      int32_t sample = chan[k];
      unsigned char b[4];
      make_sample_bytes(sample, bytes, endianness, b);
      for(int j = 0; j < bytes; j++) {
        new_mem[i++] = b[j];
      }
    }
  }

  return (void *) new_mem;
}

void ao_play_async(void *ao_handle, double at_second, double music_duration, int buf_size, void *mem, BufferInfo_t info)
{
    AO_Handle *h = (AO_Handle *) ao_handle;

    Queue_t *q = NULL;

    switch(info.type) {
      case flac: {
        int store_size = 0;
        void *store_mem = convertFlac(mem, buf_size, &info, &store_size);
        q = new_elem(PLAY, at_second, music_duration, store_size, store_mem);
        free(store_mem);
      }
      break;
      case ao: {
        q = new_elem(PLAY, at_second, music_duration, buf_size, mem);
      }
      break;
      case mpg123: {
        static int warned = 0;
        if (!warned) {
          warned = 1;
          fprintf(stderr, "format mpg123 not supported yet\n");
        }
        return;
      }
      break;
      case ao_ogg: {
        static int warned = 0;
        if (!warned) {
          warned = 1;
          fprintf(stderr, "format ao_ogg not supported yet\n");
        }
        return;
      }
      break;
    }

    add(h, q);
}

void ao_clear_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    clear(h);
}

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);

    if (h->paused) {
      if (!paused) {
        h->paused = paused;
        MUTEX_UNLOCK(h->pause_mutex);
      }
    } else {
      if (paused) {
        h->paused = paused;
        MUTEX_LOCK(h->pause_mutex);
      }
    }

    MUTEX_UNLOCK(h->mutex);
}


