//playback.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2009-2014
 *
 *  This file is part of RoarAudio PlayList Daemon,
 *  a playlist management daemon for RoarAudio.
 *  See README for details.
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  RoarAudio is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this software; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

#include "rpld.h"

static struct rpld_queue * _get_queue(pli_t queue, struct rpld_playlist ** playlist) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);

 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_NOENT);
  return NULL;
 }

 if ( pl->queue == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return NULL;
 }

 *playlist = pl;

 return pl->queue;
}

int  playback_preinit(void) {
 return 0;
}

int  playback_init(void) {
 ROAR_DBG("playback_init() = ?");

 if ( playback_preinit() == -1 )
  return -1;

 ROAR_DBG("playback_init() = 0");

 return 0;
}

void playback_uninit(void) {
 size_t i;

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL )
   continue;

  if ( g_playlists[i]->queue == NULL )
   continue;

  playback_stop(g_playlists[i]->id, 0, 1);
 }
}

void playback_set_role(pli_t queue, const int role) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 if ( plq == NULL )
  return;

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_ROLE, -1, queue, -1, role, -1, NULL, -1);

 plq->role = role;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return;
 }

 // set role on running stream:
 if ( roar_stream_set_role(&(plq->backend->con),
                           &(plq->stream),
                           plq->role) == -1 ) {
  ROAR_WARN("playback_set_role(queue=%i, role=%i): Can not set role for new stream to %s",
             (int)queue, role, roar_role2str(plq->role));
 }

 rpld_pl_unref(pl);
}

struct roar_audio_info * playback_get_codechelper_info(void) {
 static struct roar_audio_info ret = {
  .rate     = ROAR_RATE_DEFAULT,
  .bits     = ROAR_BITS_DEFAULT,
  .channels = ROAR_CHANNELS_DEFAULT,
  .codec    = ROAR_CODEC_DEFAULT
  };

 return &ret;
}

static void _handle_queue_io(pli_t queue, struct rpld_queue * plq) {
 char buf[2*1024];
 ssize_t len;
 int is_playing;

 if ( (len = roar_vio_read(plq->io.fp, buf, sizeof(buf))) < 1 ) {
  if ( len == -1 && roar_error == ROAR_ERROR_AGAIN )
   return;

  is_playing = playback_is_playing(queue);

  playback_stop(queue, 0, 0);
  playback_next(queue);

  if ( is_playing )
   playback_play(queue);

  return;
 }

 if ( roar_vio_write(&(plq->io.stream), buf, len) != len ) {
  is_playing = playback_is_playing(queue);

  playback_stop(queue, 0, 0);
  playback_next(queue);

  if ( is_playing )
   playback_play(queue);

  return;
 }
}

void playback_check(int block) {
 struct roar_vio_select vioset[3*(MAX_PLAYLISTS/2)]; // we need 3 handles per queue
                                                     // and guess we have a max of MAX_PLAYLISTS/2 queues.
 struct rpld_queue * plq;
 struct rpld_playlist * pl;
 struct roar_vio_selecttv tv;
 size_t i;
 size_t count = 0;
 int is_playing = 0;
 size_t left_handles = 0;

#ifdef ROAR_TARGET_WIN32
 if ( block == RPLD_YIELD_NONBLOCK ) {
  // we do not support non-blocking checks as win32 is totally broken. See below.
  return;
 }
#endif

 if ( block == RPLD_YIELD_NONBLOCK ) {
  block = 0;
 } else {
  block = 1;
 }

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  pl = g_playlists[i];

  if ( pl == NULL )
   continue;

  plq = pl->queue;

  if ( plq == NULL )
   continue;
  if ( !plq->playing )
   continue;

  ROAR_VIO_SELECT_SETVIO(&(vioset[count]), &(plq->io.stream), ROAR_VIO_SELECT_WRITE);
  vioset[count].ud.vp = pl;
  count++;
  ROAR_VIO_SELECT_SETVIO(&(vioset[count]), plq->io.fp, ROAR_VIO_SELECT_READ);
  vioset[count].ud.vp = pl;
  count++;

  left_handles += 2;

  if ( plq->io.fp == &(plq->io.file) ) {
   ROAR_VIO_SELECT_SETVIO(&(vioset[count]), &(plq->io.file), ROAR_VIO_SELECT_NONE|ROAR_VIO_SELECT_NO_RETEST);
  } else {
   ROAR_VIO_SELECT_SETVIO(&(vioset[count]), &(plq->io.file), ROAR_VIO_SELECT_READ);
   left_handles++;
  }
  vioset[count].ud.vp = pl;
  count++;

  is_playing = 1;
 }

 if ( is_playing ) {
  tv.sec  = 0;
  tv.nsec = 32*1000;

  yield_watchdog_trigger();
  if ( roar_vio_select(vioset, count, &tv, NULL) == -1 )
   return;
  yield_watchdog_trigger();

  for (i = 0; i < count; i++) {
   if ( vioset[i].eventsa & vioset[i].eventsq ) {
    vioset[i].eventsq |= ROAR_VIO_SELECT_NO_RETEST;
    left_handles--;
   }
  }

  tv.sec  = 0;

  if ( block ) {
   tv.nsec = 10*1000*1000;
  } else {
   tv.nsec = 32*1000;
  }

  ROAR_DBG("playback_check(void) = ?");

  ROAR_DBG("playback_check(void) = ?");
  yield_watchdog_trigger();
  // if we have less than one handle we can not do anything anyway so we just return.
  ROAR_DBG("playback_check(void) = ?");
  if ( left_handles )
   if ( roar_vio_select(vioset, count, &tv, NULL) == -1 )
     return;
  ROAR_DBG("playback_check(void) = ?");
  yield_watchdog_trigger();

  for (i = 0; i < count; i += 3) {
   if ( !(vioset[i+0].eventsa & ROAR_VIO_SELECT_WRITE) )
    continue;
   if ( !(vioset[i+1].eventsa & ROAR_VIO_SELECT_READ) && !(vioset[i+2].eventsa & ROAR_VIO_SELECT_READ) )
    continue;
   pl = vioset[i].ud.vp;
   _handle_queue_io(pl->id, pl->queue);
  }

  ROAR_DBG("playback_check(void) = ?");
 } else {
  ROAR_DBG("playback_check(void) = ?");
  roar_usleep(10000);
 }

 ROAR_DBG("playback_check(void) = (void)");
}

#if defined(HAVE_FUNC_UALARM) && defined(HAVE_FUNC_SIGINTERRUPT)
static void _sighandler_dummy(int sig) {
 (void)sig;
}
#endif

int  playback_add_queue(struct rpld_playlist * pl, const char * backend, int mixer, struct rpld_playlist * history) {
 struct rpld_queue * queue;
 int err;

 if ( pl == NULL || history == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 if ( pl->queue != NULL ) {
  roar_err_set(ROAR_ERROR_BUSY);
  return -1;
 }

 queue = roar_mm_malloc(sizeof(*queue));
 if ( queue == NULL )
  return -1;

 memset(queue, 0, sizeof(*queue));

 queue->refc        = 1;
 queue->playing     = 0;
 queue->role        = ROAR_ROLE_MUSIC;
 queue->volume_mono = 65535;
 queue->ple_mduc    = 0;
 queue->mixer       = mixer;
 queue->history     = history;
 queue->backend     = backend_get_connection(backend);

 if ( queue->backend == NULL ) {
  err = roar_error;
  roar_mm_free(queue);
  roar_error = err;
  return -1;
 }

 rpld_pl_ref(history);

 pl->queue = queue;

 return 0;
}

int  playback_del_queue(struct rpld_playlist * pl) {
 struct rpld_queue * plq;

 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 if ( (plq = pl->queue) == NULL ) {
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 if ( plq->playing )
  if ( playback_stop(rpld_pl_get_id(pl), 0, 1) == -1 )
   return -1;

 pl->queue = NULL;

 rpld_pl_unref(plq->history);
 backend_unref(plq->backend);
 roar_mm_free(plq);

 return 0;
}

int  playback_has_queue(pli_t queue) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 int ret;

 if ( pl == NULL )
  return -1;

 ret = pl->queue == NULL ? 0 : 1;

 rpld_pl_unref(pl);

 return ret;
}

int  playback_set_pointer(pli_t queue, int pointer, struct rpld_playlist_pointer * plp) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 struct rpld_playlist_pointer ** cpointer;

 if ( pl == NULL )
  return -1;

 if ( pl->queue == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 switch (pointer) {
  case POINTER_DEFAULT:
    cpointer = &(pl->queue->pointers.defaultptr);
   break;
  case POINTER_CURRENT:
    cpointer = &(pl->queue->pointers.currentptr);
   break;
  case POINTER_STARTUP:
    cpointer = &(pl->queue->pointers.startupptr);
   break;
  case POINTER_FALLBACK:
    cpointer = &(pl->queue->pointers.fallbackptr);
   break;
  default:
    rpld_pl_unref(pl);
    roar_err_set(ROAR_ERROR_INVAL);
    return -1;
   break;
 }

 if ( *cpointer != NULL ) {
  rpld_plp_unref(*cpointer);
  *cpointer = NULL;
 }

 *cpointer = plp;
 if ( plp != NULL )
  rpld_plp_ref(plp);

 rpld_pl_unref(pl);
 return 0;
}

struct rpld_playlist_pointer * playback_get_pointer(pli_t queue, int pointer) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 struct rpld_playlist_pointer * ret = NULL;

 if ( pl == NULL )
  return NULL;

 if ( pl->queue == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return NULL;
 }

 switch (pointer) {
  case POINTER_DEFAULT:
    ret = pl->queue->pointers.defaultptr;
   break;
  case POINTER_CURRENT:
    ret = pl->queue->pointers.currentptr;
   break;
  case POINTER_STARTUP:
    ret = pl->queue->pointers.startupptr;
   break;
  case POINTER_FALLBACK:
    ret = pl->queue->pointers.fallbackptr;
   break;
  default:
    roar_err_set(ROAR_ERROR_INVAL);
   break;
 }

 rpld_pl_unref(pl);

 if ( ret == NULL ) {
  roar_err_set(ROAR_ERROR_NOENT);
 } else {
  rpld_plp_ref(ret);
 }

 return ret;
}

void playback_play_all(void) {
 size_t i;

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_PLAY, -1, -1, -1, -1, -1, NULL, -1);

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL )
   continue;

  if ( g_playlists[i]->queue == NULL )
   continue;

  playback_play(rpld_pl_get_id(g_playlists[i]));
 }
}

void playback_stop_all(size_t offset, int kick) {
 size_t i;

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_STOP, -1, -1, -1, offset, kick, NULL, -1);

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL )
   continue;

  if ( g_playlists[i]->queue == NULL )
   continue;

  playback_stop(rpld_pl_get_id(g_playlists[i]), offset, kick);
 }
}

int  playback_is_playing_any(void) {
 size_t i;

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL )
   continue;

  if ( g_playlists[i]->queue == NULL )
   continue;

  if ( playback_is_playing(rpld_pl_get_id(g_playlists[i])) )
   return 1;
 }

 return 0;
}

int  playback_play(pli_t queue) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 struct roar_audio_info * codechelper_info = playback_get_codechelper_info();
 struct rpld_playlist_entry * plent;
 int codec;
 ssize_t magic_len = 0;
 char magic[64];
 const char * content_type;
 struct rpld_queue * plq;
 const char * filename = NULL;
 ssize_t filenamecount;
 ssize_t i;
 int err;
 int ret;

 ROAR_DBG("playback_play(void) = ?");

 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 if ( (plq = pl->queue) == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 if ( plq->playing ) {
  rpld_pl_unref(pl);
  return 0;
 }

 if ( !(plq->backend->flags & BACKEND_FLAG_CONNECTED) ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_NOTCONN);
  return -1;
 }

 ROAR_DBG("playback_play(void) = ?");

 if ( (plent = rpld_pl_get_first(pl)) == NULL ) {
  // retry with autoqueue
  if ( autoqueue(queue) == -1 ) {
   rpld_pl_unref(pl);
   return -1;
  }
  if ( (plent = rpld_pl_get_first(pl)) == NULL ) {
   rpld_pl_unref(pl);
   return -1;
  }
 }

 ROAR_DBG("playback_play(void) = ?");

 filenamecount = rpld_ple_num_filename(plent);

 ret = -1;
 err = ROAR_ERROR_NOENT;

 ROAR_DBG("playback_play(void) = ?");

 for (i = 0; ret == -1 && i < filenamecount; i++) {
  filename = rpld_ple_get_filename(plent, i, NULL);
  if ( filename == NULL ) {
   err = roar_error;
   continue;
  }

  yield_auto(RPLD_YIELD_PAUSE, 0, 0, 0);
#if defined(HAVE_FUNC_UALARM) && defined(HAVE_FUNC_SIGINTERRUPT)
  //signal(SIGALRM,  SIG_IGN);
  signal(SIGALRM,  _sighandler_dummy);
  siginterrupt(SIGALRM, 0);
  ualarm(1000*1000, 0);
#endif
  ret = roar_vio_open_dstr_simple(&(plq->io.file), filename, ROAR_VIOF_READ|ROAR_VIOF_NONBLOCK);
  err = roar_error;

  yield_auto(RPLD_YIELD_UNPAUSE, 0, 0, 0);
 }

 if ( ret == -1 ) {
  rpld_pl_unref(pl);
  roar_err_set(err);
  ROAR_WARN("playback_play(void): Can not open file: %s: %s", filename, roar_error2str(roar_error));
  return -1;
 }

 ROAR_DBG("playback_play(void) = ?");

 plq->io.fp = &(plq->io.file);

 /* TODO: FIXME: do a better check for streams than checking for :// ! */
 // try to guess the type by magic.
 if ( (codec = plent->codec) == -1 && strstr(filename, "://") == NULL ) {
  if ( (magic_len = roar_vio_read(&(plq->io.file), magic, 64)) < 1 ) {
   err = roar_error;
   roar_vio_close(&(plq->io.file));
   rpld_pl_unref(pl);
   roar_err_set(err);
   return -1;
  }

  codec = roar_file_codecdetect(magic, magic_len);

  if ( codec == -1 ) {
   if ( roar_vio_lseek(&(plq->io.file), 0, SEEK_SET) == (off_t)-1 ) {
    err = roar_error;
    roar_vio_close(&(plq->io.file));
    rpld_pl_unref(pl);
    roar_err_set(err);
    return -1;
   }
  }
 }

 ROAR_DBG("playback_play(void): codec=%i", codec);

 // try to read the magic from filesystem or stream headers.
 if ( codec == -1 ) {
  if ( roar_vio_ctl(&(plq->io.file), ROAR_VIO_CTL_GET_MIMETYPE, &content_type) != -1 ) {
   codec = roar_mime2codec(content_type);
  }
 }

 ROAR_DBG("playback_play(void): codec=%i", codec);

 // test if we need the codec helper for this kind of stream.
 if ( backend_blacklist_check_codec(plq->backend, codec) ) {
  codec = -1;
 }

 // open stream...
 if ( codec != -1 ) {
  if ( roar_vio_simple_new_stream_obj(&(plq->io.stream),
                                      &(plq->backend->con),
                                      &(plq->stream),
                                      ROAR_RATE_DEFAULT, ROAR_CHANNELS_DEFAULT, ROAR_BITS_DEFAULT,
                                      codec, ROAR_DIR_PLAY, plq->mixer
                                      ) == -1 ) {
   ROAR_DBG("playback_play(void): codec=%i->-1", codec);
   codec = -1; // we try the helper in case roard does not know about this codec...
  }
 }

 ROAR_DBG("playback_play(void): codec=%i", codec);

 if ( codec == -1 ) {
  if ( roar_vio_simple_new_stream_obj(&(plq->io.stream),
                                      &(plq->backend->con),
                                      &(plq->stream),
                                      codechelper_info->rate, codechelper_info->channels,
                                      codechelper_info->bits, codechelper_info->codec,
                                      ROAR_DIR_PLAY, plq->mixer
                                      ) == -1 ) {
   err = roar_error;
   roar_vio_close(&(plq->io.file));
   rpld_pl_unref(pl);
   roar_err_set(err);
   ROAR_DBG("playback_play(void) = -1");
   return -1;
  }
 }

 ROAR_DBG("playback_play(void) = ?");

 if ( codec == -1 ) {
  if ( roar_vio_open_cmd(&(plq->io.filter), &(plq->io.file),
                         "rpld-codec-helper", NULL,
                         0) == -1 ) {
   err = roar_error;
   roar_vio_close(&(plq->io.file));
   roar_vio_close(&(plq->io.stream));
   rpld_pl_unref(pl);
   roar_err_set(err);
   ROAR_DBG("playback_play(void) = -1");
   return -1;
  }
  plq->io.fp = &(plq->io.filter);
 } else {
  if ( magic_len > 0 ) {
   if ( roar_vio_write(&(plq->io.stream), magic, magic_len) != magic_len ) {
    err = roar_error;
    roar_vio_close(&(plq->io.file));
    roar_vio_close(&(plq->io.stream));
    rpld_pl_unref(pl);
    roar_err_set(err);
    ROAR_DBG("playback_play(void) = -1");
    return -1;
   }
  }
 }

 ROAR_DBG("playback_play(void) = ?");

 memcpy(&(plq->ple), plent, sizeof(plq->ple));
 plq->ple_mduc = 1;


 plq->playing = 1;

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_PLAY, -1, queue, -1, -1, -1, NULL, -1);

 // set rpg data:
 // only set in case non-default parameters are requested.
 if ( plent->rpg.mode != ROAR_RPGMODE_DEFAULT ) {
  if ( roar_stream_set_rpg(&(plq->backend->con), &(plq->stream), &(plent->rpg)) == -1 ) {
   ROAR_WARN("playback_play(queue=%u): Can not set RPG settings: %s", (unsigned)queue, roar_errorstring);
  }
 }

 // set role for new stream:
 playback_set_role(queue, plq->role);

 // set volume for next track:
 playback_set_volume(queue);

 rpld_pl_unref(pl);

 ROAR_DBG("playback_play(void) = 0");
 return 0;
}

int  playback_next(pli_t queue) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 struct rpld_queue * plq;
 struct rpld_playlist_entry * old;
 int was_playing;
 ssize_t histsize;
 // we implement this by stoping the current song,
 // hoping to the next and restart playing if we was playing.

 if ( pl == NULL )
  return -1;

 if ( (plq = pl->queue) == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_NEXT, -1, queue, -1, -1, -1, NULL, -1);

 was_playing = plq->playing;
 if ( playback_stop(queue, 0, 1) == -1 ) {
  rpld_pl_unref(pl);
  return -1;
 }

 // get next element, if there is none try autoqueue.
 if ( (old = rpld_pl_shift(pl)) == NULL )
  if ( autoqueue(queue) != -1 )
   old = rpld_pl_shift(pl);

 if ( old != NULL ) {
  rpld_pl_add(plq->history, old, -1);

  histsize = rpld_pl_get_histsize(plq->history);

  if ( histsize != -1 ) {
   while ( (ssize_t)rpld_pl_num(plq->history) > histsize ) {
    old = rpld_pl_pop(plq->history);
    if ( old != NULL )
     rpld_ple_free(old);
   }
  }
 }

 if ( was_playing )
  return playback_play(queue);

 rpld_pl_unref(pl);

 return 0;
}

int  playback_prev(pli_t queue) {
 struct rpld_playlist * pl = rpld_pl_get_by_id(queue);
 struct rpld_queue * plq;
 struct rpld_playlist_entry * old;
 int was_playing;

 if ( pl == NULL )
  return -1;

 if ( (plq = pl->queue) == NULL ) {
  rpld_pl_unref(pl);
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_PREV, -1, queue, -1, -1, -1, NULL, -1);

 was_playing = plq->playing;

 old = rpld_pl_shift(plq->history);

 if ( old == NULL ) {
  rpld_pl_unref(pl);
  return -1;
 }

 if ( playback_stop(queue, 0, 1) == -1 ) {
  rpld_pl_add(plq->history, old, -1);
  rpld_pl_unref(pl);
  return -1;
 }

 rpld_pl_add(pl, old, -1);

 rpld_pl_unref(pl);
  
 if ( was_playing )
  return playback_play(queue);

 return 0;
}

int  playback_stop(pli_t queue, size_t offset, int kick) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);

 if ( plq == NULL )
  return -1;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return 0;
 }

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_STOP, -1, queue, -1, offset, kick, NULL, -1);

 // first we kick, than we close the IO handles to avoid race conditions.
 roar_kick(&(plq->backend->con),
           ROAR_OT_STREAM,
           roar_stream_get_id(&(plq->stream)));

 roar_vio_close(&(plq->io.stream));
 roar_vio_close(plq->io.fp);

 plq->playing = 0;

 memset(&(plq->ple), 0, sizeof(plq->ple));
 plq->ple_mduc = 0;

 rpld_pl_unref(pl);

 ROAR_DBG("playback_stop(offset=%i, kick=%i) = 0", offset, kick);

 return 0;
}

int  playback_pause(pli_t queue, int how) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 struct roar_stream_info info;
 int reset = -1;

 if ( plq == NULL )
  return -1;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return -1;
 }

 switch (how) {
  case PLAYBACK_PAUSE_TRUE:
    reset = ROAR_SET_FLAG;
   break;
  case PLAYBACK_PAUSE_FALSE:
    reset = ROAR_RESET_FLAG;
   break;
  case PLAYBACK_PAUSE_TOGGLE:
#ifdef ROAR_FT_FUNC_SET_FLAGS2
#ifdef ROAR_FT_SONAME_LIBROAR2
#define _set_flags roar_stream_set_flags
#else
#define _set_flags roar_stream_set_flags2
#endif
    if ( _set_flags(&(plq->backend->con),
                    &(plq->stream),
                    ROAR_FLAG_PAUSE, ROAR_TOGGLE_FLAG) == 0 ) {
     rpld_pl_unref(pl);
     return 0;
    }
#endif

    if ( roar_stream_get_info(&(plq->backend->con),
                              &(plq->stream),
                              &info) == -1 ) {
     rpld_pl_unref(pl);
     return -1;
    }
    if ( info.flags & ROAR_FLAG_PAUSE ) {
     reset = ROAR_RESET_FLAG;
    } else {
     reset = ROAR_SET_FLAG;
    }
   break;
 }

#ifdef ROAR_FT_FUNC_SET_FLAGS2
 return _set_flags(&(plq->backend->con),
                   &(plq->stream),
                   ROAR_FLAG_PAUSE, reset);
#else
 return roar_stream_set_flags(&(plq->backend->con),
                              &(plq->stream),
                              ROAR_FLAG_PAUSE, reset);
#endif

 rpld_pl_unref(pl);

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_PAUSE, -1, queue, -1, how, -1, NULL, -1);
}

int  playback_is_playing(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 int ret;

 if ( plq == NULL )
  return 0;

 ret = plq->playing;
 rpld_pl_unref(pl);
 return ret;
}

char * playback_stream_identifier(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 static char buf[32];

 if ( plq == NULL )
  return 0;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return NULL;
 }

 snprintf(buf, sizeof(buf), "%i", roar_stream_get_id(&(plq->stream)));
 buf[sizeof(buf)-1] = 0;

 rpld_pl_unref(pl);
 return buf;
}

int  playback_is_pause(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 struct roar_stream_info info;

 if ( plq == NULL )
  return PLAYBACK_PAUSE_FALSE;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return PLAYBACK_PAUSE_FALSE;
 }

 if ( roar_stream_get_info(&(plq->backend->con),
                           &(plq->stream),
                           &info) == -1 ) {
  rpld_pl_unref(pl);
  return PLAYBACK_PAUSE_FALSE;
 }

 rpld_pl_unref(pl);

 if ( info.flags & ROAR_FLAG_PAUSE )
  return PLAYBACK_PAUSE_TRUE;

 return PLAYBACK_PAUSE_FALSE;
}

int  playback_set_volume(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 struct roar_mixer_settings mixer;
 int id;        // stream ID
 int channels;  // nummber of channels
 int i;

 if ( plq == NULL )
  return -1;

 // it's an error to enforce a volume in case we are not playing
 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return -1;
 }

 id = roar_stream_get_id(&(plq->stream));

 if ( roar_get_vol(&(plq->backend->con), id, &mixer, &channels) == -1 ) {
  rpld_pl_unref(pl);
  return -1;
 }

 mixer.scale   = 65535;
 mixer.rpg_mul = 1;
 mixer.rpg_div = 1;

 for (i = 0; i < channels; i++)
  mixer.mixer[i] = plq->volume_mono;

#ifdef ROAR_FT_SONAME_LIBROAR2
 if ( roar_set_vol(&(plq->backend->con), id,
                   &mixer, channels, ROAR_SET_VOL_ALL) == -1 ) {
  channels = 1;
  if ( roar_set_vol(&(plq->backend->con), id,
                    &mixer, channels, ROAR_SET_VOL_UNMAPPED) == -1 ) {
   rpld_pl_unref(pl);
   return -1;
  }
 }
#else
 if ( roar_set_vol(&(plq->backend->con), id, &mixer, channels) == -1 ) {
  rpld_pl_unref(pl);
  return -1;
 }
#endif

 rpld_pl_unref(pl);
 return 0;
}

int  playback_set_volume_mu16(pli_t queue, uint16_t mono) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);

 if ( plq == NULL )
  return -1;

 plq->volume_mono = mono;

 roar_notify_core_emit_simple(RPLD_NOTIFY_PLAYBACK_VOLUME, -1, queue, -1, mono, -1, NULL, -1);

 // if we are not playing we are done
 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return 0;
 }

 rpld_pl_unref(pl);
 return playback_set_volume(queue);
}

int  playback_set_volume_mpc(pli_t queue, int pc) {
 uint16_t u16 = pc * 655;

 // correct maximum

 if ( u16 > 65480 )
  u16 = 65535;

 return playback_set_volume_mu16(queue, u16);
}

uint16_t playback_get_volume_mu16(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 struct roar_mixer_settings mixer;
 uint32_t s = 0;
 int channels;  // nummber of channels
 int id;        // stream ID
 int i;

 if ( plq == NULL )
  return 65535;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return plq->volume_mono;
 }

 id = roar_stream_get_id(&(plq->stream));

 if ( roar_get_vol(&(plq->backend->con), id, &mixer, &channels) == -1 ) {
  rpld_pl_unref(pl);
  return plq->volume_mono;
 }

 for (i = 0; i < channels; i++) {
  s += mixer.mixer[i];
 }

 if ( mixer.scale != 65535 ) {
  s = (float)s / (float)(mixer.scale * channels / 65535.);
 } else {
  s /= channels;
 }

 plq->volume_mono = s;

 rpld_pl_unref(pl);

 return s;
}

int      playback_get_volume_mpc(pli_t queue) {
 return playback_get_volume_mu16(queue) / 655;
}

const struct roar_stream * playback_get_stream (pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 static struct roar_stream stream;

 // TODO: FIXME: Why don't we just returen the stream object from the plq?

 if ( plq == NULL )
  return NULL;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return NULL;
 }

 if ( roar_get_stream(&(plq->backend->con),
                      &stream,
                      roar_stream_get_id(&(plq->stream))) == -1 ) {
  rpld_pl_unref(pl);
  return NULL;
 }

 rpld_pl_unref(pl);
 return &stream;
}

struct rpld_playlist_entry * playback_get_ple (pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 struct rpld_playlist_entry * plent;
 struct roar_meta   meta;
 int types[ROAR_META_MAX_PER_STREAM];
 int i;
 int len;
 char * metadata;
 size_t metalen;

 if ( plq == NULL )
  return NULL;

 if ( !plq->playing ) {
  rpld_pl_unref(pl);
  return NULL;
 }

 plent = &(plq->ple);

 if ( (len = roar_stream_meta_list(&(plq->backend->con),
                                   &(plq->stream),
                                   types, ROAR_META_MAX_PER_STREAM)) != -1 ) {
  for (i = 0; i < len; i++) {
   metalen   = 0;
   metadata  = NULL;
   switch (types[i]) {
    case ROAR_META_TYPE_ALBUM:
      metadata = plent->meta.album;
      metalen  = sizeof(plent->meta.album);
     break;
    case ROAR_META_TYPE_TITLE:
      metadata = plent->meta.title;
      metalen  = sizeof(plent->meta.title);
     break;
    case ROAR_META_TYPE_ARTIST:
      metadata = plent->meta.artist;
      metalen  = sizeof(plent->meta.artist);
     break;
    case ROAR_META_TYPE_PERFORMER:
      metadata = plent->meta.performer;
      metalen  = sizeof(plent->meta.performer);
     break;
    case ROAR_META_TYPE_VERSION:
      metadata = plent->meta.version;
      metalen  = sizeof(plent->meta.version);
     break;
/*
   TODO: FIXME: add support for this
   case ROAR_META_TYPE_TRACKNUMBER:
     plent->meta.tracknum = atoi(delm);
     continue;
    break;
   case ROAR_META_TYPE_GENRE:
     plent->meta.genre = roar_meta_intgenre(delm);
     continue;
    break;
   case ROAR_META_TYPE_DISCID:
     sscanf(delm, "%8x", &(plent->meta.discid));
     continue;
    break;
*/
    default:
      metalen = 0;
      continue;
     break;
   }

   if ( metalen != 0 ) {
    meta.type = types[i];
    if ( roar_stream_meta_get(&(plq->backend->con),
                              &(plq->stream),
                              &meta) != -1 ) {
     plq->ple_mduc++;
     strncpy(metadata, meta.value, metalen);
     metadata[metalen-1] = 0;
     roar_meta_free(&meta);
    }
   }
  }
 }

 rpld_pl_unref(pl);
 return plent;
}

size_t playback_get_mduc(pli_t queue) {
 struct rpld_playlist * pl;
 struct rpld_queue * plq = _get_queue(queue, &pl);
 size_t ret;

 if ( plq == NULL )
  return 0;

 ret = plq->ple_mduc;
 rpld_pl_unref(pl);
 return ret;
}
//ll
