     //playlist.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"
#include <ctype.h>

struct rpld_playlist * g_playlists[MAX_PLAYLISTS];

struct rpld_playlist_entry * rpld_ple_new     (void) {
 struct rpld_playlist_entry * plent = roar_mm_malloc(sizeof(struct rpld_playlist_entry));
 static uint64_t counter = 0;
 unsigned char   rand_data = roar_random_uint16() & 0xFF;

 if ( plent == NULL )
  return NULL;

 memset(plent, 0, sizeof(struct rpld_playlist_entry));

 plent->global_tracknum        = (((ei_t      )getpid() & 0xFF) << 56) |
                                 (((uint64_t)RPLD_PL_MAGIC) << 48)     |
                                 (counter << 8)                        |
                                 rand_data;
 plent->global_short_tracknum  = (((ei_short_t)getpid() & 0xFF) << 24) ^ (counter & 0x00FF0000) >> 16;
 plent->global_short_tracknum |= (counter & 0xFFFF) << 8;
 plent->global_short_tracknum |= rand_data ^ RPLD_PL_MAGIC;

 plent->flags                  = RPLD_PLE_FLAG_UNSKIPABLE;
 plent->start                  = -1;
 plent->end                    = -1;
 plent->wallclock_start        = RPLD_PLE_WALLCLOCK_INVALID;
 plent->rpg.mode               = ROAR_RPGMODE_DEFAULT;
 plent->rpg.mul                =  1;
 plent->rpg.div                =  1;
 plent->codec                  = -1;
 plent->io.filenames           = NULL;
 plent->io.num                 = 0;
 plent->io.slots               = 0;
 plent->meta.genre             = -1;
 plent->meta.extra.kv          = NULL;
 plent->meta.extra.kvlen       = -1;
 plent->meta.extra.kv_store    = NULL;
 plent->meta.extra.kv_storelen = -1;

 plent->likeness               =  1;

 roar_uuid_gen_random(plent->uuid);

 counter++;

 return plent;
}

void                         rpld_ple_free    (struct rpld_playlist_entry * plent) {
 ssize_t i;

 if ( plent == NULL )
  return;

 if ( plent->meta.extra.kv_store != NULL )
  roar_mm_free(plent->meta.extra.kv_store);

 if ( plent->meta.extra.kv != NULL )
  roar_mm_free(plent->meta.extra.kv);

 if ( plent->io.filenames != NULL ) {
  for (i = 0; i < plent->io.num; i++ )
   roar_mm_free(plent->io.filenames[i].filename);
  roar_mm_free(plent->io.filenames);
 }

 memset(plent, 0, sizeof(*plent));
 strncpy(plent->meta.title, "<<<freed>>>", sizeof(plent->meta.title));

 roar_mm_free(plent);
}

static inline struct rpld_filename * __copy_filenames(const struct rpld_filename * src, ssize_t len) {
 struct rpld_filename * ret;
 ssize_t i;
 int err;

 if ( len < 1 )
  return NULL;

 ret = roar_mm_malloc(sizeof(struct rpld_filename)*len);
 if ( ret == NULL )
  return NULL;

 for (i = 0; i < len; i++) {
  ret[i] = src[i];
  ret[i].filename = roar_mm_strdup(src[i].filename);
  if ( ret[i].filename == NULL ) {
   err = roar_error;
   for (i--; i >= 0; i--)
    roar_mm_free(ret[i].filename);
   roar_mm_free(ret);
   roar_err_set(err);
   return NULL;
  }
 }

 return ret;
}

struct rpld_playlist_entry * rpld_ple_copy    (struct rpld_playlist_entry * plent) {
 struct rpld_playlist_entry * ret = NULL;
 ei_t trknl;
 ei_short_t trkns;
 int err;

 if ( plent == NULL )
  return NULL;

 if ( (ret = rpld_ple_new()) == NULL )
  return NULL;

 trknl = ret->global_tracknum;
 trkns = ret->global_short_tracknum;

 memcpy(ret, plent, sizeof(struct rpld_playlist_entry));
 memset(&(ret->list), 0, sizeof(ret->list));
 memset(&(ret->meta.extra), 0, sizeof(ret->meta.extra));

 ret->global_tracknum       = trknl;
 ret->global_short_tracknum = trkns;

 ret->meta.extra.kv_store = roar_keyval_copy(&(ret->meta.extra.kv), plent->meta.extra.kv, plent->meta.extra.kvlen);
 ret->meta.extra.kv_storelen = -1;
 if ( ret->meta.extra.kv_store != NULL )
  ret->meta.extra.kvlen = plent->meta.extra.kvlen;

 ret->io.filenames = __copy_filenames(plent->io.filenames, plent->io.num);
 if ( ret->io.filenames == NULL ) {
  err = roar_error;
  rpld_ple_free(ret);
  roar_err_set(err);
 }

 return ret;
}

struct rpld_playlist_entry * rpld_ple_cut_out_next (struct rpld_playlist_entry * plent) {
 struct rpld_playlist_entry * next;

 if ( plent == NULL )
  return NULL;

 // test if there is a next element in chain:
 if ( (next = plent->list.next) == NULL )
  return NULL;

 plent->list.next = next->list.next;

 memset(&(next->list), 0, sizeof(next->list));

 return next;
}

const char                 * rpld_ple_time_hr (const struct rpld_playlist_entry * plent) {
 static char buf[32];
 struct tm tm;
 time_t    length = plent->length;

 tm.tm_hour = length / 3600;
 length = length % 3600;

 tm.tm_min = length / 60;
 length = length % 60;

 tm.tm_sec = length; 

 snprintf(buf, 31, "%.2u:%.2u:%.2u", tm.tm_hour, tm.tm_min, tm.tm_sec);

 buf[31] = 0;

 return buf;
}

int                          rpld_ple_set_time_hr (struct rpld_playlist_entry * plent, char * time) {
 char * cur;
 char * next;
 time_t length = 0;

 if ( plent == NULL || time == NULL )
  return -1;

 cur  = time;

 while ( cur != NULL ) {
  next = strstr(cur, ":");
  if ( next != NULL ) {
   *next = 0;
    next++;
  }

  length *= 60;

  length += atol(cur);

  cur = next;
 }

 plent->length = length;

 return 0;
}

enum rpld_filename_type      rpld_ple_get_filenametype(const char * type) {
 const struct {
  const enum rpld_filename_type type;
  const char * name;
 } names[] = {
  {RPLD_TYPE_ERROR, "error"},
  {RPLD_TYPE_UNKNOWN, "unknown"},
  {RPLD_TYPE_AUTOLOCATE, "autolocate"},
  {RPLD_TYPE_STREAMURL, "streamurl"},
  {RPLD_TYPE_FILENAME, "filename"},
  {RPLD_TYPE_FILEURL, "fileurl"}
 };
 size_t i;

 if ( type == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return RPLD_TYPE_ERROR;
 }

 for (i = 0; i < (sizeof(names)/sizeof(*names)); i++) {
  if ( !!strcasecmp(names[i].name, type) )
   continue;
  roar_err_set(ROAR_ERROR_NONE);
  return names[i].type;
 }

 roar_err_set(ROAR_ERROR_NOENT);
 return RPLD_TYPE_ERROR;
}

int                          rpld_ple_push_filename(struct rpld_playlist_entry * plent, const char * filename, enum rpld_filename_type type, int clear) {
 const char * tantalos;
 void * tmp;
 ssize_t i;

 ROAR_DBG("rpld_ple_push_filename(plent=%p, filename='%s', type=%i, clear=%i) = ?", plent, filename, (int)type, clear);

 if ( plent == NULL || filename == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 switch (type) {
  case RPLD_TYPE_ERROR:
    roar_err_set(ROAR_ERROR_INVAL);
    return -1;
   break;
  case RPLD_TYPE_UNKNOWN:
    if ( !strncmp(filename, "tantalos://", 11) ) {
     type = RPLD_TYPE_AUTOLOCATE;
    } else if ( !strncmp(filename, "file://", 7) ) {
     type = RPLD_TYPE_FILENAME;
    } else if ( !strncmp(filename, "icy://", 6) ) {
     type = RPLD_TYPE_STREAMURL;
    }
   break;
#ifndef DEBUG
  default:
    /* make gcc not warn about missing cases */
   break;
#endif
 }

 tantalos = rpld_ple_get_filename(plent, plent->io.num, NULL);
 if ( tantalos != NULL ) {
  if ( !strcmp(tantalos, filename) ) {
   roar_err_set(ROAR_ERROR_ALREADY);
   return -1;
  }
 }

 for (i = 0; i < plent->io.num; i++) {
  if ( !strcmp(plent->io.filenames[i].filename, filename) && plent->io.filenames[i].type == type ) {
   roar_err_set(ROAR_ERROR_ALREADY);
   return -1;
  }
 }

 ROAR_DBG("rpld_ple_push_filename(plent=%p, filename='%s', type=%i, clear=%i) = ?", plent, filename, (int)type, clear);

 if ( plent->io.slots == plent->io.num ) {
  tmp = roar_mm_realloc(plent->io.filenames, (plent->io.slots + 8)*sizeof(*plent->io.filenames));
  if ( tmp == NULL )
   return -1;

  plent->io.slots += 8;
  plent->io.filenames = tmp;
 }

 ROAR_DBG("rpld_ple_push_filename(plent=%p, filename='%s', type=%i, clear=%i) = ?", plent, filename, (int)type, clear);

 if ( clear ) {
  for (i = 0; i < plent->io.num; i++)
   roar_mm_free(plent->io.filenames[plent->io.num].filename);
  plent->io.num = 0;
 }

 plent->io.filenames[plent->io.num].filename = roar_mm_strdup(filename);
 plent->io.filenames[plent->io.num].type = type;

 plent->io.num++;

 ROAR_DBG("rpld_ple_push_filename(plent=%p, filename='%s', type=%i, clear=%i) = 0", plent, filename, (int)type, clear);
 return 0;
}

const char *                 rpld_ple_get_filename(struct rpld_playlist_entry * plent, ssize_t index, enum rpld_filename_type * type) {
 ssize_t max_index;
 char uuid[37];

 ROAR_DBG("rpld_ple_get_filename(plent=%p, index=%lli, type=%p) = ?", plent, (long long int)index, type);

 if ( plent == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return NULL;
 }

 max_index = rpld_ple_num_filename(plent);
 if ( max_index == -1 )
  return NULL;

 max_index--;

 if ( index == -1 )
  index = 0;

 if ( index < 0 ) {
  roar_err_set(ROAR_ERROR_INVAL);
  return NULL;
 } else if ( index == max_index ) {
  roar_uuid2str(uuid, plent->uuid, sizeof(uuid));
  snprintf(plent->io.tantalos, sizeof(plent->io.tantalos), "tantalos://%s", uuid);
  plent->io.tantalos[sizeof(plent->io.tantalos)-1] = 0;

  if ( type != NULL )
   *type = RPLD_TYPE_AUTOLOCATE;

  return plent->io.tantalos;
 } else if ( index >= max_index ) {
  roar_err_set(ROAR_ERROR_NOENT);
  return NULL;
 }

 if ( type != NULL )
  *type = plent->io.filenames[index].type;

 ROAR_DBG("rpld_ple_get_filename(plent=%p, index=%lli, type=%p) = ?", plent, (long long int)index, type);
 (void)(volatile typeof(plent->io.filenames[index]))plent->io.filenames[index];
 return plent->io.filenames[index].filename;
}

ssize_t                      rpld_ple_num_filename(const struct rpld_playlist_entry * plent) {
 if ( plent == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 return plent->io.num + 1;
}

static inline int __iscasein(const char * haystack, const char * needle) {
 size_t i, j;
 int lower_first = tolower(*needle);

 // consider the following magic.
 for (i = 0; haystack[i]; i++) {
  if ( tolower(haystack[i]) == lower_first ) {
   for (j = 0; haystack[i+j] && needle[j]; j++)
    if ( tolower(haystack[i+j]) != tolower(needle[j]) )
     break;
   if ( !needle[j] )
    return 1;
  }
 }

 return 0;
}

int                          rpld_ple_filter  (struct rpld_playlist_entry * plent, const void * needle, const char * key, int options) {
 const char * barn[8];
 const char ** haystacks;
 ssize_t haystackcount = 0;
 ssize_t filenamecount = -1;
 ssize_t i, j;
 ssize_t needlelen;
 int ret = 0;
 union {
  const ei_t * ei;
  const ei_short_t * ei_short;
  const roar_uuid_t * uuid;
  const discid_t * discid;
  const int  * si;
  const char * str;
  const void * vp;
 } subject;

 subject.vp = needle;

 // a key of NULL is valid and means 'search in all keys'.
 if ( plent == NULL || needle == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 // if no sources are given this doesn't match.
 if ( !(options & (RPLD_PLE_FILTER_NORMKEY|RPLD_PLE_FILTER_EXTKEY)) )
  return 0;

 if ( options & RPLD_PLE_FILTER_BNEEDLE ) {
  // we can not do a binary search on non-binary data.
  if ( options & RPLD_PLE_FILTER_EXTKEY ) {
   roar_err_set(ROAR_ERROR_INVAL);
   return -1;
  }

  // key needs to be given as we use it as type in binary mode.
  if ( key == NULL ) {
   roar_err_set(ROAR_ERROR_FAULT);
   return -1;
  }

  if ( !strcasecmp(key, "long") ) {
   return *subject.ei == plent->global_tracknum ? 1 : 0;
  } else if ( !strcasecmp(key, "short") ) {
   return *subject.ei_short == plent->global_short_tracknum ? 1 : 0;
  } else if ( !strcasecmp(key, "uuid") ) {
   return roar_uuid_eq(*subject.uuid, plent->uuid);
  } else if ( !strcasecmp(key, "discid") ) {
   return *subject.discid == plent->meta.discid ? 1 : 0;
  } else if ( !strcasecmp(key, "genre") ) {
   return *subject.si == plent->meta.genre ? 1 : 0;
  } else if ( !strcasecmp(key, "tracknum") || !strcasecmp(key, "tracknumber") ) {
   return *subject.si == plent->meta.tracknum ? 1 : 0;
  } else {
   roar_err_set(ROAR_ERROR_NOTSUP);
   return -1;
  }
 }

 // first count how many keys we need to search:
 if ( options & RPLD_PLE_FILTER_NORMKEY ) {
#define _ck(keyname) if ( key == NULL || !strcasecmp(key, (keyname)) ) { haystackcount++; }
  _ck("album")
  _ck("title")
  _ck("artist")
  _ck("performer")
  _ck("version")
#undef _ck

  if ( key == NULL || !strcasecmp(key, "filename") ) {
   filenamecount = rpld_ple_num_filename(plent);
   if ( filenamecount > 0 )
    haystackcount += filenamecount;
  }
 }

 if ( (options & RPLD_PLE_FILTER_EXTKEY) && plent->meta.extra.kv != NULL )
  for (i = 0; i < plent->meta.extra.kvlen; i++)
   if ( plent->meta.extra.kv[i].key != NULL && (key == NULL || !strcasecmp(plent->meta.extra.kv[i].key, key)) )
    haystackcount++;

 // now allocate space for all the hay:
 if ( haystackcount <= (ssize_t)(sizeof(barn)/sizeof(*barn)) ) {
  haystacks = barn;
 } else {
  haystacks = roar_mm_malloc(haystackcount*sizeof(*haystacks));
  if ( haystacks == NULL )
   return -1;
 }

 // Build the list of haystacks:
 j = 0;

 if ( options & RPLD_PLE_FILTER_NORMKEY ) {
#define _ck(keyname,member) if ( key == NULL || !strcasecmp(key, (keyname)) ) { haystacks[j++] = plent->member; }
  _ck("album", meta.album)
  _ck("title", meta.title)
  _ck("artist", meta.artist)
  _ck("performer", meta.performer)
  _ck("version", meta.version)
#undef _ck
  if ( key == NULL || !strcasecmp(key, "filename") ) {
   for (i = 0; i < filenamecount; i++) {
    haystacks[j++] = rpld_ple_get_filename(plent, i, NULL);
   }
  }
 }

 if ( (options & RPLD_PLE_FILTER_EXTKEY) && plent->meta.extra.kv != NULL )
  for (i = 0; i < plent->meta.extra.kvlen; i++)
   if ( plent->meta.extra.kv[i].key != NULL && (key == NULL || !strcasecmp(plent->meta.extra.kv[i].key, key)) )
    haystacks[j++] = plent->meta.extra.kv[i].value;

 // now do the search:
 // TODO: this should be optimized.
 needlelen = roar_mm_strlen(subject.str);
 for (i = 0; !ret && i < haystackcount; i++) {
  if ( haystacks[i] == NULL )
   continue;

  if ( (options & RPLD_PLE_FILTER_ANCHORB) & (options & RPLD_PLE_FILTER_ANCHORE) ) {
   if ( options & RPLD_PLE_FILTER_CASE ) {
    if ( !strcmp(haystacks[i], subject.str) )
     ret = 1;
   } else {
    if ( !strcasecmp(haystacks[i], subject.str) )
     ret = 1;
   }
  } else if ( options & RPLD_PLE_FILTER_ANCHORB ) {
   if ( options & RPLD_PLE_FILTER_CASE ) {
    if ( !strncmp(haystacks[i], subject.str, needlelen) )
     ret = 1;
   } else {
    if ( !strncasecmp(haystacks[i], subject.str, needlelen) )
     ret = 1;
   }
  } else if ( options & RPLD_PLE_FILTER_ANCHORE ) {
   j = roar_mm_strlen(haystacks[i]);
   if ( needlelen > j )
    continue;

   if ( options & RPLD_PLE_FILTER_CASE ) {
    if ( !strcmp(haystacks[i] + j - needlelen, subject.str) )
     ret = 1;
   } else {
    if ( !strcasecmp(haystacks[i] + j - needlelen, subject.str) )
     ret = 1;
   }
  } else {
   if ( options & RPLD_PLE_FILTER_CASE ) {
    if ( strstr(haystacks[i], subject.str) != NULL )
     ret = 1;
   } else {
    ret = __iscasein(haystacks[i], subject.str);
   }
  }
 }

 // Free space space we alloced because barn was too small.
 if ( haystacks != barn )
  roar_mm_free(haystacks);

 return ret;
}

struct rpld_playlist       * rpld_pl_new      (void) {
 struct rpld_playlist * pl = roar_mm_malloc(sizeof(struct rpld_playlist));

 if ( pl == NULL )
  return NULL;

 memset(pl, 0, sizeof(struct rpld_playlist));

 pl->refc     =  1;
 pl->histsize = -1;

 return pl;
}

int                          rpld_pl_ref      (struct rpld_playlist * pl) {
 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 pl->refc++;

 return 0;
}

int                          rpld_pl_unref    (struct rpld_playlist * pl) {
 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 pl->refc--;

 if ( pl->refc == 1 && pl->name == NULL ) {
  if ( rpld_pl_unregister(pl) == 0 )
   return 0;
 }

 if ( pl->refc )
  return 0;

 rpld_pl_flush(pl);

 if ( pl->queue != NULL )
  playback_del_queue(pl);

 if ( pl->id )
  rpld_pl_unregister(pl);

 if ( pl->name != NULL )
  roar_mm_free(pl->name);

 roar_mm_free(pl);

 return 0;
}

void                         rpld_pl_push     (struct rpld_playlist * pl, struct rpld_playlist_entry * plent) {
 struct rpld_playlist_entry * next = pl->first;
 struct rpld_playlist_entry * last;

 if ( next == NULL ) {
  pl->first = plent;
 } else {
  while (next != NULL) {
   last = next;
   next = next->list.next;
  }

  last->list.next = plent;
 }

 pl->likeness_hint += plent->likeness;
 pl->size_hint++;
}

void                         rpld_pl_add      (struct rpld_playlist * pl, struct rpld_playlist_entry * plent, ssize_t pos) {
 struct rpld_playlist_entry * next = pl->first;
 struct rpld_playlist_entry * last;

 if ( pos == -1 ) {
  plent->list.next = pl->first;
  pl->first        = plent;

  pl->likeness_hint += plent->likeness;
  pl->size_hint++;

  return;
 }

 if ( pos < 0 )
  return;

 pl->likeness_hint += plent->likeness;
 pl->size_hint++;

 if ( next == NULL ) {
  pl->first = plent;
 } else {
  while (next != NULL) {
//   printf("pos=%lu, next=%p\n", (unsigned long)pos, next);
   if ( pos == 0 ) {
    plent->list.next = next->list.next;
    next->list.next  = plent;
    return;
   }

   pos--;

   last = next;
   next = next->list.next;
  }

  last->list.next = plent;
 }
}

struct rpld_playlist_entry * rpld_pl_get_first(struct rpld_playlist * pl) {
 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return NULL;
 }

 if ( pl->flags & RPLD_PL_FLAG_VIRTUAL ) {
  return pl->first->list.next;
 } else {
  return pl->first;
 }
}

struct rpld_playlist_entry * rpld_pl_shift    (struct rpld_playlist * pl) {
 struct rpld_playlist_entry * first = pl->first;

 if ( pl->first == NULL ) {
  roar_err_set(ROAR_ERROR_NOENT);
  return NULL;
 } else {
  if ( pl->flags & RPLD_PL_FLAG_VIRTUAL ) {
   first = first->list.next;
   pl->first->list.next = first->list.next;
  } else {
   pl->first = first->list.next;
  }

  pl->likeness_hint -= first->likeness;
  pl->size_hint--;

  memset(&(first->list), 0, sizeof(first->list));

  return first;
 }
}

struct rpld_playlist_entry * rpld_pl_pop      (struct rpld_playlist * pl) {
 struct rpld_playlist_entry * cur = pl->first;
 struct rpld_playlist_entry * old = NULL;
 struct rpld_playlist_entry * next;

 if ( pl->flags & RPLD_PL_FLAG_VIRTUAL ) {
  old = cur;
  cur = cur->list.next;
 }

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

 while ((next = cur->list.next) != NULL) {
  old = cur;
  cur = next;
 }

 if ( old == NULL ) {
  pl->first = NULL;
 } else {
  old->list.next = NULL;
 }

 pl->likeness_hint -= cur->likeness;
 pl->size_hint--;

 return cur;
}

void                         rpld_pl_flush    (struct rpld_playlist * pl) {
 struct rpld_playlist_entry * next = pl->first;
 struct rpld_playlist_entry * cur;

 while (next != NULL) {
  cur  = next;
  next = next->list.next;

  rpld_ple_free(cur);
 }

 pl->first = NULL;

 if ( pl->flags & RPLD_PL_FLAG_VIRTUAL )
  pl->flags -= RPLD_PL_FLAG_VIRTUAL;

 pl->size_hint     = 0;
 pl->likeness_hint = 0;
}

static float rand_float(float max);
static size_t rand_size_t(size_t max) {
/*
 size_t ret = rand() * max;

 ROAR_DBG("rand_size_t(max=%zu) = ?", max);

 return ret / RAND_MAX;
*/
 return rand_float((float)max);
}

static float rand_float(float max) {
 const float range = 65535.;
 float ret = (float)roar_random_uint16();

 ret *= max;

 ROAR_DBG("rand_size_t(max=%f) = ?", (double)max);

 return ret / range;
}

struct rpld_playlist_entry * rpld_pl_search   (struct rpld_playlist        * pl,
                                               struct rpld_playlist_search * search,
                                               struct rpld_playlist_entry  * pmatch) {
 struct rpld_playlist_entry * cur  = NULL;
 struct rpld_playlist_entry * prev = NULL;
 size_t counter                    = 0;
 float likeness                    = 0.0;
 size_t target_num                 = 0;
 float target_likeness             = 0.0;

 if ( pl == NULL || search == NULL )
  return NULL;

 ROAR_DBG("rpld_pl_search(*) = ?");

 ROAR_DBG("rpld_pl_search(*): pl ID=%i", rpld_pl_get_id(pl));

 switch (search->type) {
  case RPLD_PL_ST_NUM:
    target_num = search->subject.num;
   break;
  case RPLD_PL_ST_LIKENESS:
    target_likeness = search->subject.likeness;
   break;
  case RPLD_PL_ST_RANDOM:
    target_num = rand_size_t(pl->size_hint);
   break;
  case RPLD_PL_ST_RANDOM_LIKENESS:
    target_likeness = rand_float(pl->likeness_hint);
   break;
 }

 if ( search->options & RPLD_PL_SO_PREVIOUS ) {
  if ( rpld_pl_virtual(pl, RPLD_PL_VIRT_CREATE) != 0 )
   return NULL;
 }

 ROAR_DBG("rpld_pl_search(*) = ?");

 cur = pl->first;

 if ( pl->flags & RPLD_PL_FLAG_VIRTUAL ) {
  prev = cur;
  cur  = cur->list.next;
 }

 if ( pmatch != NULL ) {
  prev = pmatch;
  cur  = prev->list.next;
 }

 ROAR_DBG("rpld_pl_search(*) = ?");

#define _match() \
      if ( search->options & RPLD_PL_SO_PREVIOUS ) { \
       return prev; \
      } else { \
       return cur; \
      }

 while (cur != NULL) {
  likeness += cur->likeness;

  switch (search->type) {
   case RPLD_PL_ST_TRACKNUM_LONG:
     if ( cur->global_tracknum == search->subject.long_tracknum ) {
      _match();
     }
    break;
   case RPLD_PL_ST_TRACKNUM_SHORT:
     if ( cur->global_short_tracknum == search->subject.short_tracknum ) {
      _match();
     }
    break;
    case RPLD_PL_ST_UUID:
     if ( roar_uuid_eq(cur->uuid, search->subject.uuid) ) {
      _match();
     }
    break;
   case RPLD_PL_ST_NUM:
   case RPLD_PL_ST_RANDOM:
     if ( counter == target_num ) {
      _match();
     }
    break;
   case RPLD_PL_ST_LIKENESS:
   case RPLD_PL_ST_RANDOM_LIKENESS:
     if ( likeness >= target_likeness ) {
      _match();
     }
    break;
   default: return NULL;
  }

  prev = cur;
  cur  = cur->list.next;
  counter++;
 }

#undef _match

 // update _hints:
 if ( pmatch == NULL ) {
  pl->size_hint = counter;
  pl->likeness_hint = likeness;
 }

 // if we did not found any entry, but on random search we need to re-search with updated hints:
 if ( pmatch == NULL && counter > 0 ) {
  if ( search->type == RPLD_PL_ST_RANDOM || search->type == RPLD_PL_ST_RANDOM_LIKENESS ) {
   return rpld_pl_search(pl, search, pmatch);
  }
 }

 ROAR_DBG("rpld_pl_search(*) = ?");

 return NULL;
}

int                          rpld_pl_virtual  (struct rpld_playlist * pl, int virt) {
 struct rpld_playlist_entry * plent;

 if ( pl == NULL )
  return -1;

 switch (virt) {
  case RPLD_PL_VIRT_KEEP:
    return 0;
   break;
  case RPLD_PL_VIRT_DELETE:
    if ( !(pl->flags & RPLD_PL_FLAG_VIRTUAL) )
     return 0;

    plent = pl->first;
    pl->first = plent->list.next;
    rpld_ple_free(plent);

    pl->flags -= RPLD_PL_FLAG_VIRTUAL;

    return 0;
   break;
  case RPLD_PL_VIRT_CREATE:
    if ( pl->flags & RPLD_PL_FLAG_VIRTUAL )
     return 0;

    plent = rpld_ple_new();
    if ( plent == NULL )
     return -1;

    plent->list.next = pl->first;
    pl->first        = plent;

    pl->flags |= RPLD_PL_FLAG_VIRTUAL;

    return 0;
   break;
 }

 return -1;
}

int                          rpld_pl_merge    (struct rpld_playlist * pl, struct rpld_playlist * tm) {
 struct rpld_playlist_entry * ple;

 ROAR_DBG("rpld_pl_merge(pl=%p, tm=%p) = ?", pl, tm);

 if ( pl == NULL || tm == NULL )
  return -1;

 rpld_pl_unregister(tm);

 if ( rpld_pl_virtual(tm, RPLD_PL_VIRT_DELETE) == -1 ) {
  return -1;
 }

 ple = tm->first;
 tm->first = NULL;

 if ( ple != NULL ) {
  rpld_pl_push(pl, ple);
  pl->size_hint += tm->size_hint - 1;
  pl->likeness_hint += tm->likeness_hint - ple->likeness;
 }

 rpld_pl_unref(tm);

 return 0;
}

int                          rpld_pl_set_name (struct rpld_playlist * pl, const char * name) {
 if ( pl == NULL )
  return -1;

 if ( pl->name != NULL )
  roar_mm_free(pl->name);

 if ( name == NULL ) {
  pl->name = NULL;
  return 0;
 }

 pl->name = roar_mm_strdup(name);

 if ( pl->name != NULL )
  return 0;

 return -1;
}

char *                       rpld_pl_get_name (struct rpld_playlist * pl) {
 if ( pl == NULL )
  return NULL;

 return pl->name;
}

int                          rpld_pl_set_id   (struct rpld_playlist * pl, pli_t id) {
 if ( pl == NULL )
  return -1;

 pl->id = id;

 return 0;
}

pli_t                        rpld_pl_get_id   (struct rpld_playlist * pl) {
 if ( pl == NULL )
  return 0;

 return pl->id;
}

int                          rpld_pl_set_parent(struct rpld_playlist * pl, pli_t parent) {
 if ( pl == NULL )
  return -1;

 pl->parent = parent;

 return 0;
}

pli_t                        rpld_pl_get_parent(struct rpld_playlist * pl) {
 if ( pl == NULL )
  return 0;

 return pl->parent;
}

int                          rpld_pl_set_histsize(struct rpld_playlist * pl, ssize_t size) {
 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 // this may be reported as warning on some platforms.
 // if you split such a case please open a ticket at our bug tracker
 // or contact us via some other way. Thanks.
 if ( size < -1 )
  size = -1;

 pl->histsize = size;

 return 0;
}

ssize_t                      rpld_pl_get_histsize(struct rpld_playlist * pl) {
 if ( pl == NULL ) {
  roar_err_set(ROAR_ERROR_FAULT);
  return -1;
 }

 if ( pl->histsize == -1 ) {
  roar_err_set(ROAR_ERROR_TYPEMM);
  return -1;
 }

 return pl->histsize;
}

int                          rpld_pl_register (struct rpld_playlist * pl) {
 static pli_t counter = 0;
 int i;

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

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL ) {
   counter++;
   g_playlists[i] = pl;
   rpld_pl_ref(pl);
   // TODO: support tables sizes > 256 entrys
   return rpld_pl_set_id(pl, (counter << 8) | i);
  }
 }

 return -1;
}

int                          rpld_pl_register_id(struct rpld_playlist * pl, pli_t id) {
 int index = id % MAX_PLAYLISTS;

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

 if ( g_playlists[index] != NULL ) {
  roar_err_set(ROAR_ERROR_EXIST);
  return -1;
 }

 g_playlists[index] = pl;
 rpld_pl_ref(pl);
 return rpld_pl_set_id(pl, id);
}

int                          rpld_pl_unregister(struct rpld_playlist * pl) {
 int index;

 if ( pl == NULL )
  return -1;

 index = pl->id % MAX_PLAYLISTS;

 if ( g_playlists[index] != pl )
  return -1;

 rpld_pl_unref(g_playlists[index]);
 g_playlists[index] = NULL;

 return 0;
}

struct rpld_playlist       * rpld_pl_get_by_id (pli_t id) {
 struct rpld_playlist * ret = g_playlists[id % MAX_PLAYLISTS];

 ROAR_DBG("rpld_pl_get_by_id(id=%i) = ?", (int)id);

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

 rpld_pl_ref(ret);

 return ret;
}

struct rpld_playlist       * rpld_pl_get_by_name(const char * name) {
 struct rpld_playlist * c;
 int i;

 if ( name == NULL )
  return NULL;

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  c = g_playlists[i];
  if ( c != NULL ) {
   if ( c->name != NULL && !strcmp(c->name, name) ) {
    rpld_pl_ref(c);
    return c;
   }
  }
 }

 return NULL;
}

size_t                       rpld_pl_num      (struct rpld_playlist * pl) {
 struct rpld_playlist_entry * cur, * next;
 size_t count = 0;

 if ( pl == NULL )
  return 0;

 cur = rpld_pl_get_first(pl);

 while ( cur != NULL ) {
  count++;
  next = cur->list.next;
  if ( next == cur ) {
   ROAR_ERR("rpld_pl_num(pl=%p): Application error: PLE %p points to itself as next.", pl, cur);
   roar_panic(ROAR_FATAL_ERROR_MEMORY_CORRUPTION, NULL);
   return count;
  }
  cur = next;
 }

 return count;
}

struct rpld_playlist_pointer * rpld_plp_init    (struct rpld_playlist         * pl,
                                                 struct rpld_playlist_search  * pls) {
 struct rpld_playlist_pointer * plp = roar_mm_malloc(sizeof(struct rpld_playlist_pointer));

 if ( plp == NULL )
  return NULL;

 memset(plp, 0, sizeof(struct rpld_playlist_pointer));

 plp->refc = 1;

 if ( pl != NULL ) {
  plp->hint.pl = pl;
  rpld_pl_ref(pl);
 }

 if ( pls != NULL ) {
  memcpy(&(plp->pls), pls, sizeof(struct rpld_playlist_search));
 }

 return plp;
}

int                          rpld_plp_ref  (struct rpld_playlist_pointer * plp) {
 if ( plp == NULL )
  return -1;

 plp->refc++;

 return 0;
}

int                          rpld_plp_unref  (struct rpld_playlist_pointer * plp) {
 if ( plp == NULL )
  return -1;

 plp->refc--;

 if ( plp->refc )
  return 0;

 if ( plp->hint.pl != NULL )
  rpld_pl_unref(plp->hint.pl);

 roar_mm_free(plp);

 return 0;
}

struct rpld_playlist_entry * rpld_plp_search  (struct rpld_playlist_pointer * plp) {
 struct rpld_playlist_entry * ple;
 struct rpld_playlist * pl;
 int i;

 ROAR_DBG("rpld_plp_search(*) = ?");

 if ( plp == NULL )
  return NULL;

 if ( plp->hint.pl != NULL ) {
  ROAR_DBG("rpld_plp_search(*): We have a playlist hint");
  return rpld_pl_search(plp->hint.pl, &(plp->pls), NULL);
 }

 if ( plp->pls.type == RPLD_PL_ST_RANDOM || plp->pls.type == RPLD_PL_ST_RANDOM_LIKENESS ) {
  ROAR_DBG("rpld_plp_search(*): We have a random search.");
  pl = rpld_pl_get_by_id(plp->pls.subject.pl);
  ple = rpld_pl_search(pl, &(plp->pls), NULL);
  rpld_pl_unref(pl);
  return ple;
 }

 ROAR_DBG("rpld_plp_search(*): No Playlist hint, searching...");
 // add some way to search multiple playlists
 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( rpld_pl_get_histsize(g_playlists[i]) != -1 )
   continue;

  if ( g_playlists[i] != NULL ) {
   if ( (ple = rpld_pl_search(g_playlists[i], &(plp->pls), NULL)) != NULL )
    return ple;
  }
 }

 ROAR_DBG("rpld_plp_search(*): No matches found.");

 ROAR_DBG("rpld_plp_search(*) = NULL");
 return NULL;
}

int                            rpld_plp_copy    (struct rpld_playlist_pointer ** dst,
                                                 struct rpld_playlist_pointer * src) {
 if ( dst == NULL || src == NULL )
  return -1;

 if ( *dst != NULL )
  if ( rpld_plp_unref(*dst) == -1 )
   return -1;

 if ( (*dst = rpld_plp_init(src->hint.pl, &(src->pls))) == NULL )
  return -1;

 return 0;
}

//ll
