#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)
  #define SEM_WAIT(sem, ms) (WaitForSingleObject(sem, ms) == WAIT_OBJECT_0)
  #define SEM_TRYWAIT(sem) (WaitForSingleObject(sem, 0) == WAIT_OBJECT_0)
  #define SEM_POST(sem) ReleaseSemaphore(sem, 1, NULL)
  #define YIELD() sleep_ms(5)
#endif

#ifdef USE_PTHREADS
  #include <pthread.h>
  #include <semaphore.h>
  #include <sched.h>

  #define TIME_NS_IN_MSEC  1000000ULL
  static void makeSemTimeoutTime(struct timespec *ts, int ms) {
      clock_gettime(CLOCK_REALTIME, ts);
      ts->tv_sec += ms / 1000;
      ts->tv_nsec += (ms % 1000) * TIME_NS_IN_MSEC;
      if (ts->tv_nsec >= 1000000000L) {
          ts->tv_sec++;
          ts->tv_nsec = ts->tv_nsec - 1000000000L;
      }
  }

  static int _SEM_WAIT(sem_t *sem, int ms)
  {
    struct timespec ts;
    makeSemTimeoutTime(&ts, ms);
    int r = sem_timedwait(sem, &ts);
    return (r == 0);
  }

  static int msleep(long msec)
  {
      struct timespec ts;
      int res;

      if (msec < 0) {
          return -1;
      }

      ts.tv_sec = msec / 1000;
      ts.tv_nsec = (msec % 1000) * 1000000;

      res = nanosleep(&ts, &ts);
      return res;
  }

  static void yield()
  {
    msleep(5);
  }

  #define MUTEX_LOCK(m) pthread_mutex_lock(&m)
  #define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m)
  #define SEM_WAIT(sem, ms) _SEM_WAIT(&sem, ms)
  #define SEM_TRYWAIT(sem) (sem_trywait(&sem) == 0)
  #define SEM_POST(sem) sem_post(&sem)
  #define YIELD() yield()
#endif

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

#include <malloc.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ao/ao.h>

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


typedef struct _queue_ {
    Command_t command;
    void *buf;
    int buflen;
    double at_second;
    double music_duration;
    int music_id;
    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;

    int             paused;

    ao_device      *ao_device;
    int             dev_bits_per_sample;
    int             dev_endianess;
    int             dev_channels;
    int             dev_rate;

#ifdef USE_WINDOWS_THREADS
    HANDLE          mutex;
    HANDLE          pause_mutex;
    HANDLE          clear_mutex;
    HANDLE          thread;
    DWORD           thread_id;
    HANDLE          queue_sem;
#endif
#ifdef USE_PTHREADS
    pthread_mutex_t mutex;
    pthread_mutex_t pause_mutex;
    pthread_mutex_t clear_mutex;
    pthread_t       thread;
    sem_t           queue_sem;
#endif
    double          at_second;
    double          music_duration;
    int             at_music_id;
    int             buf_size;
    int             volume_in_10000;    // volume in 100000 steps, i.e. 100000 equals 100%
} AO_Handle;


static Queue_t *get(AO_Handle *h, int ms_wait)
{
    Queue_t *q = NULL;
    int r;
    if (ms_wait <= 0) {
      r = SEM_TRYWAIT(h->queue_sem);
    } else {
      r = SEM_WAIT(h->queue_sem, ms_wait);
    }
    if (r) {
      MUTEX_LOCK(h->mutex);
      if (h->play_queue != NULL) {             // Clear could have cleared the play_queue.
        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;
    SEM_POST(h->queue_sem);
    MUTEX_UNLOCK(h->mutex);
}

static Queue_t *new_elem(int command, int music_id, 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->music_id = music_id;
    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)
{
    //fprintf(stderr, "Wait for clear mutex\n");
    MUTEX_LOCK(h->clear_mutex);
    //fprintf(stderr, "Starting clear\n");
    int count = 0;
    Queue_t *q = get(h, 0);
    while (q != NULL) {
      del_elem(q);
      count += 1;
      q = get(h, 0);
    }
    fprintf(stderr, "%d elements cleared\n", count);
    MUTEX_UNLOCK(h->clear_mutex);
}

static int inline littleEndian()
{
  int n = 1;
  return (*(char *)&n) == 1;
}

static void inline adjustVolume(AO_Handle *handle, char *_buf, int buf_size, int volume_in_10000)
{
  int bytes_per_sample = (handle->dev_bits_per_sample >> 3);
  register int endianess = handle->dev_endianess;
  register unsigned char *buf = (unsigned char *) _buf;

  register int i;
  register long long sample;
  register int k;

  int little_endian = (endianess == AO_FMT_LITTLE);
  if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian();

  for(i = 0; i < buf_size; i += bytes_per_sample) {
    if (little_endian) {
      sample = buf[bytes_per_sample + i - 1] > 127 ? -1 : 0;
      for(k = bytes_per_sample + i - 1; k >= i; k--) {
        sample <<= 8;
        sample |= buf[k];
      }
    } else {
      sample = (buf[i] > 127) ? -1 : 0;
      for(k = i; k < (i + bytes_per_sample); k++) {
        sample <<= 8;
        sample += buf[k];
      }
    }
    sample *= volume_in_10000;
    sample /= 10000;
    if (little_endian) {
      for(k = i; k < (i + bytes_per_sample); k++) {
        buf[k] = sample&0xff;
        sample >>= 8;
      }
    } else {
      for(k = i + bytes_per_sample - 1; k >= i; k--) {
        buf[k] = sample&0xff;
        sample >>= 8;
      }
    }
  }
}

#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);
        MUTEX_LOCK(handle->clear_mutex);
        Queue_t *q = get(handle, 250);
        MUTEX_UNLOCK(handle->clear_mutex);
        if (q != NULL) {
          handle->at_second = q->at_second;
          handle->music_duration = q->music_duration;
          handle->at_music_id = q->music_id;

          if (handle->volume_in_10000 != 10000) {
              // adjust volume
              adjustVolume(handle, q->buf, q->buflen, handle->volume_in_10000);
          }

          if (q->command == STOP) {
              go_on = (1 == 0);
          } else {
              ao_play(handle->ao_device, (char *) q->buf, q->buflen);
          }

          del_elem(q);
        } else {
          YIELD();
        }
    }

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

int ao_async_version()
{
  return VERSION;
}

#ifdef _WIN32
#else
#include <dlfcn.h>
#endif

#ifdef _WIN32
static void at_exit_shutdown_ao(void)
#else
static void at_exit_shutdown_ao()
#endif
{
  ao_shutdown();
}

static void init_ao()
{
  static int init = 0;
  if (!init) {
    ao_initialize();
    atexit(at_exit_shutdown_ao);
  }
}

void *ao_create_async(int bits, int rate, int channels, int byte_format)
{
    init_ao();

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

    ao_sample_format fmt;
    fmt.bits = bits;
    fmt.rate = rate;
    fmt.byte_format = byte_format;
    fmt.channels = channels;
    fmt.matrix = NULL;

    ao_device *dev = ao_open_live(ao_default_driver_id(), &fmt, NULL);
    if (dev == NULL) {
      fprintf(stderr, "Cannot open ao-device, error code = %d\n", errno);
      return NULL;
    }

    handle->ao_device = dev;
    handle->dev_bits_per_sample = bits;
    handle->dev_channels = channels;
    handle->dev_rate = rate;
    handle->dev_endianess = byte_format;
    handle->volume_in_10000 = 10000;

    handle->play_queue = NULL;
    handle->last_frame = NULL;
    handle->at_second = -1;
    handle->at_music_id = -1;

    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 c = PTHREAD_MUTEX_INITIALIZER;
    handle->clear_mutex = c;
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    handle->mutex = m;
    sem_init(&handle->queue_sem, 0, 0);
    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->pause_mutex = CreateMutex(NULL,              // default security attributes
                                FALSE,             // initially not owned
                                NULL);
    handle->clear_mutex = CreateMutex(NULL,
                                      FALSE,
                                      NULL);
    handle->queue_sem = CreateSemaphore(NULL, 0, 1000000, NULL);
    handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id);
#endif

    MUTEX_UNLOCK(handle->pause_mutex);
    MUTEX_UNLOCK(handle->clear_mutex);

    return (void *) handle;
}

void ao_stop_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    fprintf(stderr, "stopping ao_async, calling clear\n");

    clear(h);

    fprintf(stderr, "queue cleared\n");

    if (h->paused) {
      MUTEX_UNLOCK(h->pause_mutex);
    }
    Queue_t *q = new_elem(STOP, 0, 0.0, 0.0, 0, NULL);
    add(h, q);

    fprintf(stderr, "stop command queued\n");

#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
    ao_close(h->ao_device);

    fprintf(stderr, "device closed\n");

    free(h);

    fprintf(stderr, "async handle freed\n");
}

/*
(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 big_endian, unsigned char b[4])
{
  int i;

  for (i = 0; i < bytes_per_sample; i++) {
     b[i] = sample&0xff;
     sample >>=  8;
  }
  if (big_endian) {
     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 endianess = info->endiannes;

  int little_endian = (endianess == AO_FMT_LITTLE);
  if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian();
  int big_endian = !little_endian;

  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, big_endian, b);
      for(int j = 0; j < bytes; j++) {
        new_mem[i++] = b[j];
      }
    }
  }

  return (void *) new_mem;
}

void ao_play_async(void *ao_handle, int music_id, 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, music_id, at_second, music_duration, store_size, store_mem);
        free(store_mem);
      }
      break;
      case ao: {
        q = new_elem(PLAY, music_id, 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;
}

int ao_is_at_music_id_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    int s = h->at_music_id;
    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_set_volume_async(void *ao_handle, double percentage)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    int volume_10000 = (int) (percentage * 100.0);
    if (volume_10000 >= 9990 && volume_10000 <= 10010) {
      volume_10000 = 10000;
    }
    h->volume_in_10000 = volume_10000;
    MUTEX_UNLOCK(h->mutex);
}


double ao_volume_async(void *ao_handle)
{
    AO_Handle *h = (AO_Handle *) ao_handle;
    MUTEX_LOCK(h->mutex);
    double volume = h->volume_in_10000 / 100.0;
    MUTEX_UNLOCK(h->mutex);
    return volume;
}

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



