//proto_simple.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"

struct addqueue_para {
 pli_t queue;
 pli_t history;
 const char * backend;
 int mixer;
 int role;
};

static struct addqueue_para proto_simple_parse_addqueue(int client, char * txt);
static int proto_simple_addqueue(int client, char * txt);
static int proto_simple_serverinfo(int client);
static int proto_simple_searchple(int client, char * args);

// workarounds:
#ifdef ROAR_TARGET_WIN32
static int asprintf(char **strp, const char *fmt, ...) {
 char buf[1024];
 int         ret;
 va_list     ap;

 va_start(ap, fmt);
 ret = vsnprintf(buf, sizeof(buf), fmt, ap);
 va_end(ap);

 *strp = strdup(buf);

 return ret;
}
#endif

static void remove_spaces(char ** str) {
 char * c;

 if ( str == NULL )
  return;

 c = *str;

 if ( c == NULL )
  return;

 for (; *c == ' '; c++);

 *str = c;
}

static const char * __parse_string(char ** txt, const char * default_value) {
 char quote = 0;
 char * str;
 char * ret;

 remove_spaces(txt);

 str = *txt;

 if ( str[0] == '\'' || str[0] == '\"' ) {
  quote = str[0];
 } else {
  if ( !strncasecmp(*txt, "default ", 8) ) {
   *txt += 8;
   return default_value;
  } else if ( !strcasecmp(*txt, "default") ) {
   *txt += 7;
   return default_value;
  }
  roar_err_set(ROAR_ERROR_ILLSEQ);
  return NULL;
 }

 str++;
 ret = str;

 while (*str && *str != quote) str++;

 if ( *str == quote ) {
  *str = 0;
  *txt = str + 1;
  return ret;
 } else {
  roar_err_set(ROAR_ERROR_ILLSEQ);
  return NULL;
 }
}


#define HT_BOOL          "TRUE|FALSE|TOGGLE"
#define HT_PLI_RAW       "\"Name\"|ID"
#define HT_PLI           "{" HT_PLI_RAW "}"
#define HT_PLI_ANY_RAW   HT_PLI_RAW "|ANY"
#define HT_PLI_ANY       "{" HT_PLI_ANY_RAW "}"
#define HT_PLI_ANY_BUT   "{" HT_PLI_ANY_RAW " [BUT {QUEUES|HISTORIES}]...}"

#define HT_EI            "{long:0xLongID|0xLongID|short:0xShortID|uuid:UUID|pointer:name|num:Index|likeness:Likeness}"

#define HT_IOI           "{STDIN|STDOUT|\"Filename\"}"
#define HT_PLT           "{RPLD|PLS|M3U|VCLT|XSPF|PLAIN|URAS}"
#define HT_IMPORT_EXPORT "[" HT_PLI "] {TO|FROM} " HT_IOI " [AS " HT_PLT "]"
#define HT_STORE_RESTORE "{NONE|CONFIG|ALL|QUEUE|PLAYLIST " HT_PLI_ANY "}"
#define HT_PLE_BOPTS     HT_EI " [FROM " HT_PLI_ANY "]"
#define HT_PLE_POS       " [AT POSITION]"
#define HT_PLE_COPY_MOVE HT_PLE_BOPTS " TO " HT_PLI HT_PLE_POS
#define HT_PLE_SEARCH_TERM "{\"search string\"|discid:0xdiscid|uuid:UUID|genre:genre|tracknum[ber]:num}"
#define HT_PLE_SEARCH_F  "{ANY|ALBUM|TITLE|ARTIST|PERFORMER|VERSION|FILENAME|DISCID|UUID|GENRE|TRACKNUM[BER]|TAG:\"Tagname\"}"
#define HT_PLE_SEARCH_OP "{{IS|AS}|IN|AT {BEGIN|END} OF}"
#define HT_PLE_SEARCH_C  "[CASE[[ ]SENSITIVE]]"
#define HT_PLE_SEARCH    HT_PLE_SEARCH_TERM " " HT_PLE_SEARCH_C " [NOT] " HT_PLE_SEARCH_OP " [NOT] " HT_PLE_SEARCH_F " [FROM " HT_PLI_ANY_BUT "]"
#define HT_PTRI          "{CURRENT|DEFAULT|STARTUP|TEMP}"
#define HT_POINTER_SET   HT_PTRI " [TO] " HT_PLE_BOPTS
#define HT_POINTER_UNSET HT_PTRI
#define HT_POINTER_SHOW  "[" HT_PTRI "]"
#define HT_POLICYS       "{DEFAULT|OLD|NEW}"
#define HT_POLICY        "{KEEP|DELETE}"

static char * proto_simple_help_text[] = {
 "Commands:\n",
 "  HELP\n",
 "  NOOP\n",
 "  SERVERINFO\n",
 "  PLAY\n",
 "  STOP\n",
 "  NEXT\n",
 "  PREV\n",
 "  ISPLAYING\n",
 "  SHOWIDENTIFIER\n",
 "  SHOWCUR\n",
 "  LISTQ [" HT_PLI "]\n",
 "  FLUSHQ\n",
 "  SETVOLUME new-volume\n",
 "  SHOWVOLUME\n",
 "  QUIT\n",
 "  LISTPLAYLISTS\n",
 "  SETPLAYLIST " HT_PLI "\n",
 "  SHOWLIST [" HT_PLI "]\n",
 "  ADDPLAYLIST \"Name\"\n",
 "  DELPLAYLIST [" HT_PLI "]\n",
 "  FLUSHPLAYLIST [" HT_PLI "]\n",
 "  LISTPLE [" HT_PLI "]\n",
 "  SHOWPLE " HT_PLE_BOPTS "\n",
 "  COPYPLE " HT_PLE_COPY_MOVE "\n",
 "  MOVEPLE " HT_PLE_COPY_MOVE "\n",
 "  DELPLE " HT_PLE_BOPTS "\n",
 "  QUEUEPLE " HT_PLE_BOPTS HT_PLE_POS "\n",
 "  IMPORT " HT_IMPORT_EXPORT "\n",
 "  EXPORT " HT_IMPORT_EXPORT "\n",
 "  SETPOINTER " HT_POINTER_SET "\n",
 "  UNSETPOINTER " HT_POINTER_UNSET "\n",
 "  SHOWPOINTER " HT_POINTER_SHOW "\n",
 "  PAUSE " HT_BOOL "\n",
 "  UNAUTH [ACCLEV] {BY n|TO {n|\"name\"}}\n",
 "  LIKE " HT_PLE_BOPTS " [+/- LIKENESS]\n",
 "  DISLIKE " HT_PLE_BOPTS " [+/- LIKENESS]\n",
 "  SETQUEUE " HT_PLI "\n",
 "  ADDQUEUE [[TO] " HT_PLI "] [WITH BACKEND backend] [MIXER n] WITH HISTORY " HT_PLI " [WITH ROLE role]\n",
 "  DELQUEUE [" HT_PLI "]\n",
 "  SHOWQUEUE [" HT_PLI "]\n",
 "  AUTOQUEUE\n",
 "  ADDHISTORY [[TO] " HT_PLI "] [SIZE n]\n",
 "  DELHISTORY [" HT_PLI "]\n",
 "  LISTCLIENTS\n",
 "  IDENTIFY [WITH NAME name]\n", // we also support WITH {PID,NODENAME,HOSTID} but currently ignore those values as they are not stored.
 "  SEARCHPLE " HT_PLE_SEARCH "\n",
 "Commands to fix:\n",
 "  SETPARENTLIST [OF " HT_PLI "] [TO] " HT_PLI "\n",
 "  SHOWPLAYING\n",
 "  STORE " HT_STORE_RESTORE "\n",
 "  RESTORE " HT_STORE_RESTORE "\n",
 // TODO: To implement
 "Commands to implement:\n",
 "  UPDATE " HT_IMPORT_EXPORT " USING " HT_PLE_SEARCH_F " [AND ...]" " [POLICY " HT_POLICYS " " HT_POLICY "]" "\n",
 "  AUTH [TO AUTHLEV {n|\"name\"}] USING authtype...\n",
 "Commands to remove (obsoleted):\n",
 NULL
};

static int proto_simple_client_puts(int client, char * text) {
 struct roar_vio_calls * vio = client_get_vio(client);
 ssize_t len = strlen(text);

 if ( roar_vio_write(vio, text, (size_t)len) != len )
  return -1;

 return 0;
}

int proto_simple_client_handle(int id, struct roar_vio_calls * vio, struct roar_buffer ** obuffer, void ** userdata, const struct roar_keyval * para, ssize_t paralen, struct roar_dl_librarypara * pluginpara) {
 int status;
 ssize_t len;
 char buffer[1024];
 const char * statustext;
 const char * errortext = NULL;

 (void)obuffer, (void)userdata, (void)para, (void)paralen, (void)pluginpara;

 if ( vio == NULL )
  return -1;

 if ( (len = roar_vio_read(vio, buffer, 1023)) < 1 )
  return -1;

 if ( buffer[len-1] != '\n' )
  return -1;

 if ( len > 1 && buffer[len-2] == '\r' ) {
  len -= 2;
 } else {
  len--;
 }

 buffer[len] = 0;

 if ( len > 0 && buffer[0] != '#' ) {
  status = proto_simple_parse(id, buffer, len);
 } else {
  status = PROTO_SIMPLE_STATUS_OK;
 }

 switch (status) {
  case PROTO_SIMPLE_STATUS_OK:       statustext = "OK";             break;
  case PROTO_SIMPLE_STATUS_ERROR:    statustext = "Error";          break;
  case PROTO_SIMPLE_STATUS_INPROC:   statustext = "In Process";     break;
  case PROTO_SIMPLE_STATUS_YES:      statustext = "Yes";            break;
  case PROTO_SIMPLE_STATUS_NO:       statustext = "No";             break;
  case PROTO_SIMPLE_STATUS_PROERR:   statustext = "Protocol Error"; break;
  case PROTO_SIMPLE_STATUS_GOODBYE:  statustext = "GoodBye";        break;
  case PROTO_SIMPLE_STATUS_NOTPERM:  statustext = "Not Permitted";  break;
  case PROTO_SIMPLE_STATUS_NFPROERR: statustext = "Protocol or Syntax Error"; break;
  default:                           statustext = "(UNKNOWN)";      break;
 }

 if ( status == PROTO_SIMPLE_STATUS_ERROR ) {
  errortext = roar_error2str(roar_error);
 }

 if ( errortext != NULL ) {
  snprintf(buffer, sizeof(buffer), ".\n> %i %s (%s)\n", status, statustext, errortext);
 } else {
  snprintf(buffer, sizeof(buffer), ".\n> %i %s\n", status, statustext);
 }
 buffer[sizeof(buffer)-1] = 0;

 if ( proto_simple_client_puts(id, buffer) == -1 )
  return -1;

 switch (status) {
  case PROTO_SIMPLE_STATUS_PROERR:
  case PROTO_SIMPLE_STATUS_GOODBYE:
    return -1;
   break;
 }

 return 0;
}

#define _CKPERM(p) if ( acclev < (p) ) return PROTO_SIMPLE_STATUS_NOTPERM
#define NULL_OK     0
#define NULL_NOT_OK 1
#define _CKARGS(ok) if ( (ok) && args == NULL ) return PROTO_SIMPLE_STATUS_NFPROERR

int proto_simple_parse(int client, char * line, size_t len) {
 enum   rpld_client_acclev    acclev = client_get_acclev(client);
 struct rpld_playlist_entry * plent;
 struct rpld_playlist       * cpl, * pl;
 char * cmd = line;
 char * args;
 char * tmp;
 float tmpf;
 size_t i;
 int mode;
 ssize_t pos = -2;

 if ( (args = strstr(cmd, " ")) != NULL ) {
  *args = 0;
   args++;
 }

 ROAR_DBG("proto_simple_parse(client=%i, ...): cmd='%s'", client, cmd);

 if ( !strcasecmp(cmd, "noop") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_OK);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "help") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_OK);
  for (i = 0; proto_simple_help_text[i] != NULL; i++) {
   if ( proto_simple_client_puts(client, proto_simple_help_text[i]) == -1 )
    return PROTO_SIMPLE_STATUS_PROERR;
  }

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "serverinfo") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_OK);
  return proto_simple_serverinfo(client);
 } else if ( !strcasecmp(cmd, "quit") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_OK);
  return PROTO_SIMPLE_STATUS_GOODBYE;


 } else if ( !strcasecmp(cmd, "auth") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_NOT_OK);
/* "  AUTH [TO AUTHLEV {n|\"name\"}] USING authtype...\n" */
  return PROTO_SIMPLE_STATUS_PROERR;
 } else if ( !strcasecmp(cmd, "unauth") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_NOT_OK);
/* "  UNAUTH [ACCLEV] {BY n|TO {n|\"name\"}}\n" */

  if ( !strncasecmp(args, "acclev ", 7) )
   args += 7;

  if ( !strncmp(args, "to ", 3) ) {
   args += 3;
   if ( *args == '"' ) {
    args++;
    for (i = 0; args[i] != 0; i++) {
     if ( args[i] == '"' ) {
      args[i] = 0;
      break;
     }
    }
    if ( client_set_acclev(client, client_str2acclev(args), client) == -1 ) {
     return PROTO_SIMPLE_STATUS_NOTPERM;
    } else {
     return PROTO_SIMPLE_STATUS_OK;
    }
   } else {
    if ( client_set_acclev(client, atoi(args), client) == -1 ) {
     return PROTO_SIMPLE_STATUS_NOTPERM;
    } else {
     return PROTO_SIMPLE_STATUS_OK;
    }
   }
  } else if ( !strncmp(args, "by ", 3) ) {
   args += 3;
   if ( client_inc_acclev(client, -1*atoi(args), client) == -1 ) {
    return PROTO_SIMPLE_STATUS_NOTPERM;
   } else {
    return PROTO_SIMPLE_STATUS_OK;
   }
  }

  return PROTO_SIMPLE_STATUS_PROERR;


 } else if ( !strcasecmp(cmd, "play") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( playback_play(client_get_queue(client)) != -1 )
   return PROTO_SIMPLE_STATUS_OK;
  return PROTO_SIMPLE_STATUS_ERROR;
 } else if ( !strcasecmp(cmd, "stop") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( playback_stop(client_get_queue(client), 0, 1) != -1 )
   return PROTO_SIMPLE_STATUS_OK;
  return PROTO_SIMPLE_STATUS_ERROR;
 } else if ( !strcasecmp(cmd, "next") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( playback_next(client_get_queue(client)) != -1 )
   return PROTO_SIMPLE_STATUS_OK;
  return PROTO_SIMPLE_STATUS_ERROR;
 } else if ( !strcasecmp(cmd, "prev") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( playback_prev(client_get_queue(client)) != -1 )
   return PROTO_SIMPLE_STATUS_OK;
  return PROTO_SIMPLE_STATUS_ERROR;
 } else if ( !strcasecmp(cmd, "autoqueue") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( autoqueue(client_get_queue(client)) != -1 )
   return PROTO_SIMPLE_STATUS_OK;
  return PROTO_SIMPLE_STATUS_ERROR;

 } else if ( !strcasecmp(cmd, "pause") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( !strcasecmp(args, "true") ) {
   mode = PLAYBACK_PAUSE_TRUE;
  } else if ( !strcasecmp(args, "false") ) {
   mode = PLAYBACK_PAUSE_FALSE;
  } else if ( !strcasecmp(args, "toggle") ) {
   mode = PLAYBACK_PAUSE_TOGGLE;
  } else {
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  if ( playback_pause(client_get_queue(client), mode) != -1 )
   return PROTO_SIMPLE_STATUS_OK;

  return PROTO_SIMPLE_STATUS_ERROR;

 } else if ( !strcasecmp(cmd, "isplaying") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  return playback_is_playing(client_get_queue(client)) ? PROTO_SIMPLE_STATUS_YES : PROTO_SIMPLE_STATUS_NO;
 } else if ( !strcasecmp(cmd, "showidentifier") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( (args = playback_stream_identifier(client_get_queue(client))) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  // TODO: do some cleanup here:
  if ( proto_simple_client_puts(client, args) == -1 )
   return PROTO_SIMPLE_STATUS_PROERR;
  if ( proto_simple_client_puts(client, "\n") == 1 )
   return PROTO_SIMPLE_STATUS_ERROR;

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "showplaying") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  return proto_simple_showplaying(client);

 } else if ( !strcasecmp(cmd, "listq") || !strcasecmp(cmd, "listple") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   if ( !strcasecmp(cmd, "listq") ) {
    pl = rpld_pl_get_by_id(client_get_queue(client));
   } else {
    pl = rpld_pl_get_by_id(client_get_playlist(client));
   }
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  plent = rpld_pl_get_first(pl);

  while (plent != NULL) {
   proto_simple_print_plent(client, plent);
   plent = plent->list.next;
  }

  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "showcur") || !strcasecmp(cmd, "showple") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   if ( !strcasecmp(cmd, "showcur") ) {
    pl = rpld_pl_get_by_id(client_get_queue(client));
    if ( pl == NULL ) {
     plent = NULL;
    } else {
     plent = rpld_pl_get_first(pl);
     rpld_pl_unref(pl);
    }
   } else {
    return PROTO_SIMPLE_STATUS_ERROR;
   }
  } else {
   plent = proto_simple_parse_ple_bopts(client, args, 0);
   if ( plent == NULL )
    return PROTO_SIMPLE_STATUS_ERROR;
  }

  if ( plent != NULL )
    proto_simple_print_plent(client, plent);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "flushq") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( playback_stop(client_get_queue(client), 0, 1) == -1 )
   return PROTO_SIMPLE_STATUS_ERROR;

  pl = rpld_pl_get_by_id(client_get_queue(client));
  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  rpld_pl_flush(pl);
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;

 } else if ( !strcasecmp(cmd, "setvolume") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (len = strlen(args)) == 0 )
   return PROTO_SIMPLE_STATUS_ERROR;

  switch (args[len-1]) {
   case '%':
     args[len-1] = 0;
     if ( playback_set_volume_mpc(client_get_queue(client), atoi(args)) == -1 )
      return PROTO_SIMPLE_STATUS_ERROR;
    break;
   default:
     if ( playback_set_volume_mu16(client_get_queue(client), atoi(args)) == -1 )
      return PROTO_SIMPLE_STATUS_ERROR;
    break;
  }

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "showvolume") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  return proto_simple_showvolume(client);

 } else if ( !strcasecmp(cmd, "listplaylists") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  for (i = 0; i < MAX_PLAYLISTS; i++) {
   pl = rpld_pl_get_by_id(i);
   if ( pl != NULL ) {
    proto_simple_print_playlist(client, pl);
    rpld_pl_unref(pl);
   }
  }
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "showlist") || !strcasecmp(cmd, "showqueue") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   if ( !strcasecmp(cmd, "showqueue") ) {
    pl = rpld_pl_get_by_id(client_get_queue(client));
   } else {
    pl = rpld_pl_get_by_id(client_get_playlist(client));
   }
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  proto_simple_print_playlist(client, pl);
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "delplaylist") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  // TODO: fix this by unsetting name?
  rpld_pl_set_name(pl, NULL);
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "addplaylist") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( *args != '"' )
   return PROTO_SIMPLE_STATUS_ERROR;

  args++;
  i = strlen(args);
  if ( args[i-1] != '"' )
   return PROTO_SIMPLE_STATUS_ERROR;

  args[i-1] = 0;

  pl = rpld_pl_new();

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  // TODO: check for errors:
  rpld_pl_set_name(pl, args);

  rpld_pl_register(pl);
  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "flushplaylist") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  rpld_pl_flush(pl);
  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "setplaylist") || !strcasecmp(cmd, "setqueue") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( !strcasecmp(cmd, "setplaylist") ) {
   if ( client_set_playlist(client, rpld_pl_get_id(pl)) == -1 ) {
    rpld_pl_unref(pl);
    return PROTO_SIMPLE_STATUS_ERROR;
   }
  } else {
   if ( client_set_queue(client, rpld_pl_get_id(pl)) == -1 ) {
    rpld_pl_unref(pl);
    return PROTO_SIMPLE_STATUS_ERROR;
   }
  }

  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "setparentlist") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  i = 0;

  if ( *args == 'O' || *args == 'o' ) {
   i    = 1;
   args = strstr(args, " ");

   if ( args == NULL )
    return PROTO_SIMPLE_STATUS_ERROR;
  }

  if ( i ) {
   cpl = proto_simple_parse_pli(args, NULL);
   args = strstr(args, " ");
   if ( args == NULL )
    return PROTO_SIMPLE_STATUS_ERROR;
  } else {
   cpl = rpld_pl_get_by_id(client_get_playlist(client));
  }

  if ( cpl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( *args == 'T' || *args == 't' ) {
   args = strstr(args, " "); 
   if ( args == NULL ) {
    rpld_pl_unref(cpl);
    return PROTO_SIMPLE_STATUS_ERROR;
   }
  }

  pl = proto_simple_parse_pli(args, NULL);

  if ( pl == NULL ) {
   rpld_pl_unref(cpl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  rpld_pl_set_parent(cpl, rpld_pl_get_id(pl));
  rpld_pl_unref(pl);
  rpld_pl_unref(cpl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "addhistory") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_OK);
  tmp = NULL;
  if ( args != NULL ) {
   if ( (args[0] == 't' || args[0] == 'T') && (args[1] == 'o' || args[1] == 'O') && args[2] == ' ' )
    args += 3;

   tmp = strstr(args, " SIZE ");
   if ( tmp == NULL )
    tmp = strstr(args, " size ");

   if ( tmp == NULL ) {
    tmp = strstr(args, "SIZE ");
    if ( tmp == NULL )
     tmp = strstr(args, "size ");
    if ( tmp == args ) {
     args = "";
    } else {
     tmp = NULL;
    }
   }

   if ( tmp != NULL && tmp[0] == ' ' ) {
    *tmp = 0;
    tmp += 6;
   } else if ( tmp != NULL ) {
    *tmp = 0;
    tmp += 5;
   }
   if ( *args == 0 )
    args = NULL;
  }

  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( tmp == NULL )
   tmp = "42"; // TODO: FIXME: use global const here.

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( rpld_pl_set_histsize(pl, atoi(tmp)) == -1 ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "delhistory") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( rpld_pl_set_histsize(pl, -1) == -1 ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "addqueue") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_NOT_OK);
  return proto_simple_addqueue(client, args);
 } else if ( !strcasecmp(cmd, "delqueue") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   pl = rpld_pl_get_by_id(client_get_playlist(client));
  } else {
   pl = proto_simple_parse_pli(args, NULL);
  }

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( playback_del_queue(pl) == -1 ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  rpld_pl_unref(pl);

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "copyple") || !strcasecmp(cmd, "queueple") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (plent = proto_simple_parse_ple_bopts(client, args, 0)) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( !strcasecmp(cmd, "queueple") ) {
   pl = rpld_pl_get_by_id(client_get_queue(client));
  } else {
   if ( (tmp = strstr(args, " TO ")) == NULL )
    if ( (tmp = strstr(args, " to ")) == NULL )
     return PROTO_SIMPLE_STATUS_ERROR;

   args = tmp + 4;

   pl = proto_simple_parse_pli(args, NULL);

   if ( pl == NULL )
    return PROTO_SIMPLE_STATUS_ERROR;
  }

  tmp = NULL;

  if ( (tmp = strstr(args, " AT ")) == NULL )
   tmp = strstr(args, " at ");

  if ( tmp != NULL ) {
   args = tmp + 4;
   pos = atoi(args);
  }

  if ( (plent = rpld_ple_copy(plent)) == NULL ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  if ( pos == -2 ) {
   rpld_pl_push(pl, plent);
  } else {
   if ( pos == -1 && pl->queue != NULL && playback_is_playing(rpld_pl_get_id(pl)) ) {
    playback_stop(rpld_pl_get_id(pl), 0, 1);
    rpld_pl_add(pl, plent, pos);
    playback_play(rpld_pl_get_id(pl));
   } else {
    rpld_pl_add(pl, plent, pos);
   }
  }

  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "moveple") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (tmp = strstr(args, " TO ")) == NULL )
   if ( (tmp = strstr(args, " to ")) == NULL )
    return PROTO_SIMPLE_STATUS_ERROR;

  pl = proto_simple_parse_pli(tmp+4, NULL);

  if ( pl == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (plent = proto_simple_parse_ple_bopts(client, args, 1)) == NULL ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  if ( (plent = rpld_ple_cut_out_next(plent)) == NULL ) {
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  tmp = NULL;

  if ( (tmp = strstr(args, " AT ")) == NULL )
   tmp = strstr(args, " at ");

  if ( tmp != NULL ) {
   args = tmp + 4;
   pos = atoi(args);
  }

  if ( pos == -2 ) {
   rpld_pl_push(pl, plent);
  } else {
   if ( pos == -1 && pl->queue == NULL && playback_is_playing(rpld_pl_get_id(pl)) ) {
    playback_stop(rpld_pl_get_id(pl), 0, 1);
    rpld_pl_add(pl, plent, pos);
    playback_play(rpld_pl_get_id(pl));
   } else {
    rpld_pl_add(pl, plent, pos);
   }
  }
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "delple") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

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

  if ( (plent = proto_simple_parse_ple_bopts(client, args, 1)) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

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

  if ( (plent = rpld_ple_cut_out_next(plent)) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

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

  rpld_ple_free(plent);

  return PROTO_SIMPLE_STATUS_OK;

 } else if ( !strcasecmp(cmd, "like") || !strcasecmp(cmd, "dislike") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  if ( args == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (plent = proto_simple_parse_ple_bopts(client, args, 0)) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  if ( (tmp = strrchr(args, ' ')) == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  tmpf = atof(tmp+1);

  if ( !strcasecmp(cmd, "dislike") )
   tmpf *= -1;

  plent->likeness += tmpf;

  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "import") || !strcasecmp(cmd, "export") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  return proto_simple_import_export(client, args, !strcasecmp(cmd, "export"));

 } else if ( !strcasecmp(cmd, "searchple") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  return proto_simple_searchple(client, args);

 } else if ( !strcasecmp(cmd, "store") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_OK);
  if ( store() == -1 )
   return PROTO_SIMPLE_STATUS_ERROR;
  return PROTO_SIMPLE_STATUS_OK;
 } else if ( !strcasecmp(cmd, "restore") ) {
  _CKPERM(ACCLEV_ALL);
  _CKARGS(NULL_OK);
  if ( restore() == -1 )
   return PROTO_SIMPLE_STATUS_ERROR;
  return PROTO_SIMPLE_STATUS_OK;

 } else if ( !strcasecmp(cmd, "setpointer") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  return proto_simple_setpointer(client, args);
 } else if ( !strcasecmp(cmd, "unsetpointer") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_NOT_OK);
  return proto_simple_unsetpointer(client, args);
 } else if ( !strcasecmp(cmd, "showpointer") ) {
  _CKPERM(ACCLEV_GUEST);
  _CKARGS(NULL_OK);
  if ( args == NULL ) {
   return proto_simple_showpointer_all(client);
  } else {
   return proto_simple_showpointer(client, args, 0);
  }
 } else if ( !strcasecmp(cmd, "listclients") ) {
  _CKPERM(ACCLEV_USER);
  _CKARGS(NULL_OK);
  return proto_simple_listclients(client);
 } else if ( !strcasecmp(cmd, "identify") ) {
  _CKPERM(ACCLEV_CONCTL);
  _CKARGS(NULL_OK);
  return proto_simple_identify(client, args);
 }

 // unknown command
 return PROTO_SIMPLE_STATUS_PROERR;
}

int proto_simple_print_plent(int client, struct rpld_playlist_entry * plent) {
 char buffer[1024];
 char uuid[37];

 roar_uuid2str(uuid, plent->uuid, sizeof(uuid));

//TYPE=LENGTH=ALBUM=TITLE=ARTIST=VERSION=FILE
 snprintf(buffer, sizeof(buffer) - 1,
          "%s=%s=%s=%s=%s=%s=%s=%s=long:0x%.16" __ll "X/short:0x%.8X/uuid:%s=0x%.8x/%i=%s(0x%x)=%f\n",
                        roar_codec2str(plent->codec),
                        rpld_ple_time_hr(plent),
                        plent->meta.album,
                        plent->meta.title,
                        plent->meta.artist,
                        plent->meta.performer,
                        plent->meta.version,
                        rpld_ple_get_filename(plent, -1, NULL),
                        (__longlong unsigned int)plent->global_tracknum,
                        plent->global_short_tracknum,
                        uuid,
                        plent->meta.discid,
                        plent->meta.tracknum,
                        roar_meta_strgenre(plent->meta.genre),
                        plent->meta.genre,
                        (double)plent->likeness
         );
 buffer[1023] = 0;

 return proto_simple_client_puts(client, buffer);
}

struct rpld_playlist * proto_simple_parse_pli(char * txt, char ** next) {
 struct rpld_playlist * ret = NULL;
 char * end = NULL;
 char saved;

 if ( txt == NULL )
  return NULL;

 if ( next != NULL )
  *next = NULL;

 ROAR_DBG("proto_simple_parse_pli(txt='%s') = ?", txt);

 if ( *txt == '"' ) {
  if ( (end = strstr(txt+1, "\"")) == NULL )
   return NULL;

  saved = *end;
  *end  = 0;

  ret   = rpld_pl_get_by_name(txt+1);

  *end  = saved;
  end++;
 } else {
  end = strstr(txt+1, " ");

  if ( end != NULL ) {
   saved = *end;
   *end  = 0;
  }

  ret   = rpld_pl_get_by_id(atoi(txt));

  if ( end != NULL )
   *end  = saved;
 }

 if ( next != NULL ) {
  if ( end != NULL ) {
   *next = end;
  } else {
   *next = txt + roar_mm_strlen(txt);
  }
 }

 ROAR_DBG("proto_simple_parse_pli(txt='%s') = %p", txt, ret);

 return ret;
}


int proto_simple_print_playlist (int client, struct rpld_playlist * pl) {
 const char * name;
 int id, parent;
 ssize_t histsize;
 struct rpld_queue * plq;
 char buf[768];
 char options[512] = "";

 if ( pl == NULL )
  return -1;

 id       = rpld_pl_get_id(pl);
 parent   = rpld_pl_get_parent(pl);
 name     = rpld_pl_get_name(pl);
 histsize = rpld_pl_get_histsize(pl);
 plq      = pl->queue;

 if ( histsize != -1 ) {
  snprintf(options, sizeof(options), "history size: %" __ll "u", (__longlong unsigned int)histsize);
 } else if ( plq != NULL ) {
  snprintf(options, sizeof(options), "volume: %u/65535, role: %s, history: %i, backend: \"%s\", mixer: %i",
                                     (unsigned int)plq->volume_mono,
                                     roar_role2str(plq->role),
                                     (int)rpld_pl_get_id(plq->history),
                                     backend_get_name(plq->backend),
                                     plq->mixer
          );
 }

 snprintf(buf, sizeof(buf), "%3i: [parent: %i%s%s] \"%s\"\n", id, parent, *options ? ", " : "", options, name);
 buf[sizeof(buf)-1] = 0;

 proto_simple_client_puts(client, buf);

 return 0;
}

int proto_simple_parse_ple_bopts_noeval(int client, char * txt, int need_prev, int * try_any, struct rpld_playlist ** pl, struct rpld_playlist_search * pls) {
 struct rpld_playlist_pointer * pointer = NULL;
 char * ei   = txt;
 char * from;
 char * pli  = NULL;
 char   from_save = 0;
 __longlong unsigned int tmp;

 if ( txt == NULL || client == -1 )
  return -1;

 if ( (from = strstr(ei, " ")) != NULL ) {
  if ( !strncasecmp(from, " from ", 6) ) {
   pli = strstr(from+1, " ");
  }
  from_save = *from;
  *from = 0;
 }

 if ( pli == NULL ) {
  *pl = rpld_pl_get_by_id(client_get_playlist(client));
 } else {
  pli++;
  ROAR_DBG("proto_simple_parse_ple_bopts(*): pli='%s'", pli);
  if ( !strcasecmp(pli, "any") || !strncasecmp(pli, "any ", 4) ) {
   *pl = NULL;
   *try_any = 1;
  } else {
   *pl = proto_simple_parse_pli(pli, NULL);
  }
 }

 if ( *pl == NULL && !*try_any ) {
  if ( from != NULL )
   *from = from_save;
  return -1;
 }

 ROAR_DBG("proto_simple_parse_ple_bopts(*): ei='%s', from='%s', pli='%s'", ei, from, pli);

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

 if ( need_prev )
  pls->options |= RPLD_PL_SO_PREVIOUS;

 if ( !strncmp(ei, "short:", 6) ) {
  if ( sscanf(ei+6, "0x%8X", &(pls->subject.short_tracknum)) != 1 )
   pls->subject.short_tracknum = 0;
  pls->type = RPLD_PL_ST_TRACKNUM_SHORT;
  ROAR_DBG("proto_simple_parse_ple_bopts(*): pls->subject.short_tracknum=0x%.8X", pls->subject.short_tracknum);
 } else if ( !strncmp(ei, "long:", 5) || *ei == '0' ) {
  if ( *ei == 'l' )
   ei += 5;

  if ( sscanf(ei, "0x%16" __ll "X", &tmp) != 1 ) {
   pls->subject.long_tracknum = 0;
  } else {
   pls->subject.long_tracknum = tmp;
  }

  pls->type = RPLD_PL_ST_TRACKNUM_LONG;
  ROAR_DBG("proto_simple_parse_ple_bopts(*): pls->subject.long_tracknum=0x%.16" __ll "X", (__longlong unsigned int)pls->subject.long_tracknum);
 } else if ( !strncmp(ei, "pointer:", 8) ) {
  pointer = rpld_pointer_get_by_name(ei+8, client);

  if ( pointer == NULL ) {
   if ( from != NULL )
    *from = from_save;
   return -1;
  } else {
   if ( pointer->pls.type == RPLD_PL_ST_RANDOM || pointer->pls.type == RPLD_PL_ST_RANDOM_LIKENESS ) {
    *pl = rpld_pl_get_by_id(pointer->pls.subject.pl);
   } else {
    *pl = pointer->hint.pl;
    if ( *pl != NULL )
     rpld_pl_ref(*pl);
   }
   if ( *pl == NULL )
    *try_any = 1;
   memcpy(pls, &(pointer->pls), sizeof(*pls));

   rpld_plp_unref(pointer);
  }

 } else if ( !strncmp(ei, "uuid:", 5) ) {
  if ( roar_str2uuid(pls->subject.uuid, ei+5) != 0 )
   roar_uuid_clear(pls->subject.uuid);

  pls->type = RPLD_PL_ST_UUID;
 } else if ( !strncmp(ei, "num:", 4) ) {
  pls->subject.num = (size_t)atoi(ei+4);
  pls->type = RPLD_PL_ST_NUM;
 } else if ( !strncmp(ei, "likeness:", 9) ) {
  pls->subject.likeness = (float)atof(ei+9);
  pls->type = RPLD_PL_ST_LIKENESS;
 } else if ( !strncmp(ei, "random:", 7) ) {
  pls->subject.pl = atoi(ei+7);
  if ( *pl != NULL && pls->subject.pl == 0 ) {
   pls->subject.pl = rpld_pl_get_id(*pl);
  }
  if ( *pl != NULL )
   rpld_pl_unref(*pl);
  *pl = rpld_pl_get_by_id(pls->subject.pl);
  pls->type = RPLD_PL_ST_RANDOM;
 } else if ( !strncmp(ei, "randomlike:", 11) ) {
  pls->subject.pl = atoi(ei+11);
  if ( *pl != NULL && pls->subject.pl == 0 ) {
   pls->subject.pl = rpld_pl_get_id(*pl);
  }
  if ( *pl != NULL )
   rpld_pl_unref(*pl);
  *pl = rpld_pl_get_by_id(pls->subject.pl);
  pls->type = RPLD_PL_ST_RANDOM_LIKENESS;
 } else {
  ROAR_WARN("proto_simple_parse_ple_bopts(*): Unknown PLE format: %s", ei);
  if ( from != NULL )
   *from = from_save;
  return -1;
 }

 if ( from != NULL )
  *from = from_save;

 return 0;
}

struct rpld_playlist_entry * proto_simple_parse_ple_bopts_eval(int try_any, struct rpld_playlist * pl, struct rpld_playlist_search * pls) {
 struct rpld_playlist_entry *   ret = NULL;
 size_t i;

 if ( try_any ) {
  for (i = 0; i < MAX_PLAYLISTS; i++) {
   if ( g_playlists[i] != NULL ) {
    if ( (ret = rpld_pl_search(g_playlists[i], pls, NULL)) != NULL )
     break;
   }
  }
 } else {
  ret = rpld_pl_search(pl, pls, NULL);
 }

 return ret;
}

struct rpld_playlist_entry * proto_simple_parse_ple_bopts(int client, char * txt, int need_prev) {
 struct rpld_playlist_entry   * ret;
 struct rpld_playlist_search    pls;
 struct rpld_playlist         * pl  = NULL;
 int    try_any = 0;

 if ( proto_simple_parse_ple_bopts_noeval(client, txt, need_prev, &try_any, &pl, &pls) == -1 ) {
  return NULL;
 }

 ret = proto_simple_parse_ple_bopts_eval(try_any, pl, &pls);

 if ( pl != NULL )
  rpld_pl_unref(pl);

 return ret;
}

int proto_simple_import_export (int client, char * args, int is_export) {
 struct roar_vio_defaults def;
 struct fformat_handle  * handle;
 struct roar_vio_calls    vio;
 struct rpld_playlist   * pl = NULL;
 int format;
 char * ftpos;
 char * tpos;
 const char * delm;
 int ret;

/*
  IMPORT [{"Name"|ID}] {TO|FROM} {STDIN|STDOUT|"Filename"} [AS {RPLD|PLS|M3U|VCLT|XSPF}]
  EXPORT [{"Name"|ID}] {TO|FROM} {STDIN|STDOUT|"Filename"} [AS {RPLD|PLS|M3U|VCLT|XSPF}]
*/

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( client == -1 || args == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 if ( is_export ) {
  if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, O_WRONLY|O_CREAT|O_TRUNC, 0644) == -1 )
   return PROTO_SIMPLE_STATUS_ERROR;
 } else {
  if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, O_RDONLY, 0644) == -1 )
   return PROTO_SIMPLE_STATUS_ERROR;
 }

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( *args == 'f' || *args == 'F' ) {
  pl    = rpld_pl_get_by_id(client_get_playlist(client));
  args += 5;
 } else if ( *args == 't' || *args == 'T' ) {
  pl    = rpld_pl_get_by_id(client_get_playlist(client));
  args += 3;
 } else {
  if ( (ftpos = strstr(args, " FROM ")) == NULL )
   if ( (ftpos = strstr(args, " from ")) == NULL )
    if ( (ftpos = strstr(args, " TO ")) == NULL )
     if ( (ftpos = strstr(args, " to ")) == NULL )
      return PROTO_SIMPLE_STATUS_ERROR;

  pl = proto_simple_parse_pli(args, NULL);
  args = ftpos + 1;
  if ( *ftpos == 'f' || *ftpos == 'F' ) {
   args += 5;
  } else {
   args += 3;
  }
 }

 if ( pl == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i): *args='%c'", client, args, is_export, *args);

 switch (*args) {
  case '\"': delm = "\""; break;
  case '\'': delm = "\'"; break;
  default:   delm = " ";  break;
 }

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i): delm='%s'", client, args, is_export, delm);

 if ( (tpos = strstr(*args == *delm ? args+1 : args, delm)) == NULL ) {
  format = RPLD_FF_PLF_DEFAULT;
 } else {
  *tpos = 0;
   tpos++;

  ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

  ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i): *tpos='%c'", client, args, is_export, *tpos);

  if ( *tpos == 0 ) {
   ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

   format = RPLD_FF_PLF_DEFAULT;
  } else {
   ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i): tpos='%s'", client, args, is_export, tpos);

   remove_spaces(&tpos);

   ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i): tpos='%s'", client, args, is_export, tpos);

   if ( strncasecmp(tpos, "as ", 3) )
    return PROTO_SIMPLE_STATUS_ERROR;

   tpos += 3;

   format = fformat_str2plf(tpos);
  }
 }

 if ( format == -1 ) {
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( (handle = fformat_handle_new(format)) == NULL ) {
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( !strcasecmp(args, "stdout") || !strcasecmp(args, "stdin") ) {
  if ( rpld_proto_simple_vio_open(&vio, client_get_vio(client)) == -1 ) {
   fformat_handle_close(handle);
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }
 } else if ( *args == '"' ) {
  args++;

  ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

  if ( roar_vio_open_dstr(&vio, args, &def, 1) == -1 ) {
   fformat_handle_close(handle);
   rpld_pl_unref(pl);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 } else {
  fformat_handle_close(handle);
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( is_export ) {
  ret = fformat_playlist_export(handle, &vio, pl);
 } else {
  ret = fformat_playlist_import(handle, &vio, pl);
 }

 roar_vio_close(&vio);

 fformat_handle_close(handle);

 rpld_pl_unref(pl);

 ROAR_DBG("proto_simple_import_export(client=%i, args='%s', is_export=%i) = ?", client, args, is_export);

 if ( ret == -1 ) {
  // we return PROTO_SIMPLE_STATUS_PROERR in case of import as we do not read up all input
  // which would result as input read as commands in case we would not terminate te connection.
  return is_export ? PROTO_SIMPLE_STATUS_ERROR : PROTO_SIMPLE_STATUS_PROERR;
 } else {
  return PROTO_SIMPLE_STATUS_OK;
 }
}

int proto_simple_showvolume (int client) {
 pli_t queue = client_get_queue(client);
 char buf[80];

 snprintf(buf, sizeof(buf), "VOLUME %i/65535 %i%%\n", playback_get_volume_mu16(queue), playback_get_volume_mpc(queue));

 proto_simple_client_puts(client, buf);

 return PROTO_SIMPLE_STATUS_OK;
}

#define _BS  80
int proto_simple_showplaying (int client) {
 struct rpld_playlist_entry * plent;
 const struct roar_stream   * s;
 char buf[_BS];
 char uuid[37];

 if ( !playback_is_playing(client_get_queue(client)) ) {
  proto_simple_client_puts(client, "STATE STOPPED\n");
  return PROTO_SIMPLE_STATUS_OK;
 }

 if ( playback_is_pause(client_get_queue(client)) == PLAYBACK_PAUSE_TRUE ) {
  proto_simple_client_puts(client, "STATE PAUSE\n");
 } else {
  proto_simple_client_puts(client, "STATE RUNNING\n");
 }

 plent = playback_get_ple(client_get_queue(client));

 if ( plent == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

/*
 *           "%s=%s=%s=%s=%s=%s=%s=%s=long:0x%.16llX/short:0x%.8X/uuid:%s=0x%.8x/%i=%s(0x%x)\n",
 */
 snprintf(buf, _BS, "LONGGTN long:0x%.16" __ll "X\n", (__longlong unsigned int)plent->global_tracknum);
 proto_simple_client_puts(client, buf);
 snprintf(buf, _BS, "SHORTGTN short:0x%.8X\n", plent->global_short_tracknum);
 proto_simple_client_puts(client, buf);

 roar_uuid2str(uuid, plent->uuid, sizeof(uuid));
 snprintf(buf, _BS, "UUID uuid:%s\n", uuid);
 proto_simple_client_puts(client, buf);

// send meta data:
 if ( *(plent->meta.album) != 0 ) {
  snprintf(buf, _BS, "META ALBUM \"%s\"\n", plent->meta.album);
  proto_simple_client_puts(client, buf);
 }

 if ( *(plent->meta.title) != 0 ) {
  snprintf(buf, _BS, "META TITLE \"%s\"\n", plent->meta.title);
  proto_simple_client_puts(client, buf);
 }

 if ( *(plent->meta.artist) != 0 ) {
  snprintf(buf, _BS, "META ARTIST \"%s\"\n", plent->meta.artist);
  proto_simple_client_puts(client, buf);
 }

 if ( *(plent->meta.performer) != 0 ) {
  snprintf(buf, _BS, "META PERFORMER \"%s\"\n", plent->meta.performer);
  proto_simple_client_puts(client, buf);
 }

 if ( *(plent->meta.version) != 0 ) {
  snprintf(buf, _BS, "META VERSION \"%s\"\n", plent->meta.version);
  proto_simple_client_puts(client, buf);
 }

 snprintf(buf, _BS, "MDUC %lu\n", (unsigned long int)playback_get_mduc(client_get_queue(client)));
 proto_simple_client_puts(client, buf);

// TODO: FIXME: add support for genre, tracknum and discid

 if ( (s = playback_get_stream(client_get_queue(client))) == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 if ( s->info.rate ) {
  snprintf(buf, _BS, "RATE %i\n", s->info.rate);
  proto_simple_client_puts(client, buf);
 }

 if ( s->info.channels ) {
  snprintf(buf, _BS, "CHANNELS %i\n", s->info.channels);
  proto_simple_client_puts(client, buf);
 }

 if ( s->info.bits ) {
  snprintf(buf, _BS, "BITS %i\n", s->info.bits);
  proto_simple_client_puts(client, buf);
 }

 if ( (int32_t) s->pos != -1 ) {
  if ( s->info.rate && s->info.channels ) {
   snprintf(buf, _BS, "TIME %lu S (%.3fs)\n", (unsigned long int) s->pos, (float)s->pos/(s->info.rate*s->info.channels));
  } else {
   snprintf(buf, _BS, "TIME %lu S\n", (unsigned long int) s->pos);
  }
  proto_simple_client_puts(client, buf);
 }

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_unsetpointer(int client, char * args) {
 if ( rpld_pointer_set_by_name(args, client, NULL) == -1 )
  return PROTO_SIMPLE_STATUS_ERROR;

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_setpointer(int client, char * args) {
 struct rpld_playlist         * pl  = NULL;
 struct rpld_playlist_search    pls;
 struct rpld_playlist_entry   * ple;
 struct rpld_playlist_pointer * plp;
 char * pointer_name;
 char * bopts;
 int    try_any = 0;

//  SETPOINTER {CURRENT|DEFAULT} {long:0xLongID|0xLongID|short:0xShortID|uuid:UUID} [FROM {"Name"|ID}]

 if ( args == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 pointer_name = args;

 if ( (bopts = strstr(args, " ")) == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 *bopts = 0;
  bopts++;

 if ( !strncasecmp(bopts, "to ", 3) ) {
   bopts += 3;
 }

 if ( proto_simple_parse_ple_bopts_noeval(client, bopts, 0, &try_any, &pl, &pls) == -1 )
  return PROTO_SIMPLE_STATUS_ERROR;

 if ( !(pls.type == RPLD_PL_ST_RANDOM || pls.type == RPLD_PL_ST_RANDOM_LIKENESS) ) {
  ple = proto_simple_parse_ple_bopts_eval(try_any, pl, &pls);
  if ( ple == NULL )
   return PROTO_SIMPLE_STATUS_ERROR;

  memset(&pls, 0, sizeof(pls));

  pls.type = RPLD_PL_ST_UUID;

  memcpy(pls.subject.uuid, ple->uuid, sizeof(pls.subject.uuid));
 }

 plp = rpld_plp_init(NULL, &pls);
 if ( rpld_pointer_set_by_name(pointer_name, client, plp) == -1 ) {
  rpld_plp_unref(plp);
  if ( pl != NULL )
   rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 rpld_plp_unref(plp);

 if ( pl != NULL )
  rpld_pl_unref(pl);

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_showpointer(int client, char * name, int no_rename) {
 pli_t queue;
 int pointer, pointer_client;
 struct rpld_playlist_pointer * plp;
 char * text = NULL;
 char uuid[37];

 if ( client == -1 || name == NULL || *name == 0 )
  return PROTO_SIMPLE_STATUS_ERROR;

 if ( rpld_pointer_parse_name(&pointer, &queue, &pointer_client, client, name) == -1 )
  return PROTO_SIMPLE_STATUS_ERROR;

 if ( no_rename ) {
  name = roar_mm_strdup(name);
 } else {
  name = rpld_pointer_get_name(pointer, queue, queue == (pli_t)-1 ? pointer_client : -1);
 }

 if ( name == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 plp = rpld_pointer_get(pointer, queue, pointer_client);

 if ( plp == NULL ) {
  asprintf(&text, "POINTER %s NOT SET\n", name);
 } else {
  switch (plp->pls.type) {
   case RPLD_PL_ST_TRACKNUM_LONG:
     asprintf(&text, "POINTER %s IS AT long:0x0x%.16" __ll "X\n", name, (__longlong unsigned int)plp->pls.subject.long_tracknum);
    break;
   case RPLD_PL_ST_TRACKNUM_SHORT:
     asprintf(&text, "POINTER %s IS AT short:0x0x%.8X\n", name, plp->pls.subject.short_tracknum);
    break;
   case RPLD_PL_ST_UUID:
     roar_uuid2str(uuid, plp->pls.subject.uuid, sizeof(uuid));
     asprintf(&text, "POINTER %s IS AT uuid:%s\n", name, uuid);
    break;
    case RPLD_PL_ST_RANDOM:
     asprintf(&text, "POINTER %s IS AT random:%i\n", name, plp->pls.subject.pl);
    break;
    case RPLD_PL_ST_RANDOM_LIKENESS:
     asprintf(&text, "POINTER %s IS AT randomlike:%i\n", name, plp->pls.subject.pl);
    break;
   default:
     return PROTO_SIMPLE_STATUS_ERROR;
  }
 }

 roar_mm_free(name);
 rpld_plp_unref(plp);

 if ( proto_simple_client_puts(client, text) == -1 ) {
  free(text);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 free(text);

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_showpointer_all(int client) {
 int pointer;
 pli_t queue;
 size_t i;
 char * name;

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

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

  queue = g_playlists[i]->id;

  for (pointer = 0; pointer < POINTER_NUM; pointer++) {
   if ( pointer == POINTER_TEMP )
    continue;

   name = rpld_pointer_get_name(pointer, queue, -1);
   proto_simple_showpointer(client, name, 1);
   roar_mm_free(name);
  }
 }

 for (i = 0; i < RPLD_CLIENTS_MAX; i++) {
  if ( client_get_proto(i) == RPLD_PROTO_UNUSED )
   continue;

  name = rpld_pointer_get_name(POINTER_TEMP, -1, i);
  proto_simple_showpointer(client, name, 1);
  roar_mm_free(name);
 }

 return PROTO_SIMPLE_STATUS_OK;
}

static struct addqueue_para proto_simple_parse_addqueue(int client, char * txt) {
//   ADDQUEUE [[TO] {"Name"|ID}] [WITH BACKEND backend] [MIXER n] WITH HISTORY {"Name"|ID} [WITH ROLE role]
 struct addqueue_para ret = {.queue = -1, .history = -1, .backend = "+default", .mixer = -1, .role = -1};
 struct rpld_playlist * pl;
 const char * tmp;

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 remove_spaces(&txt);

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( (txt[0] == 't' || txt[0] == 'T') && (txt[1] == 'o' || txt[1] == 'O') && txt[2] == ' ' ) {
  txt += 3;
  remove_spaces(&txt);
 }

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( *txt == '\'' || *txt == '\"' || (*txt >= '0' && *txt <= '9' ) ) {
  pl = proto_simple_parse_pli(txt, &txt);
 } else {
  pl = rpld_pl_get_by_id(client_get_playlist(client));
 }

 if ( pl == NULL )
  return ret; // queue is still set to -1, which means error.

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 ret.queue = rpld_pl_get_id(pl);
 rpld_pl_unref(pl);

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 while (txt != NULL && *txt) {
  remove_spaces(&txt);

  ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

  if ( !strncasecmp(txt, "with backend ", 13) ) {
   txt += 13;
   ret.backend = __parse_string(&txt, "+default");
   if ( ret.backend == NULL ) {
    ret.queue = -1;
    return ret;
   }
  } else if ( !strncasecmp(txt, "mixer ", 6) ) {
   txt += 6;
   ret.mixer = strtol(txt, &txt, 0);
  } else if ( !strncasecmp(txt, "with history ", 13) ) {
   txt += 13;
   pl = proto_simple_parse_pli(txt, &txt);
   if ( pl == NULL ) {
    ret.queue = -1;
    return ret;
   }

   ret.history = rpld_pl_get_id(pl);
   rpld_pl_unref(pl);
  } else if ( !strncasecmp(txt, "with role", 9) ) {
   txt += 9;
   tmp = __parse_string(&txt, "music");
   if (tmp == NULL) {
    ret.history = ret.queue = -1;
    roar_err_set(ROAR_ERROR_ILLSEQ);
    return ret;
   }
   ret.role = roar_str2role(tmp);
  } else {
   ret.history = ret.queue = -1;
   roar_err_set(ROAR_ERROR_ILLSEQ);
   return ret;
  }
 }

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( ret.history == (pli_t)-1 )
  ret.queue = -1;

 ROAR_DBG("proto_simple_parse_addqueue(client=%i, txt='%s') = ?", client, txt);
 return ret;
}

static int proto_simple_addqueue(int client, char * txt) {
 struct addqueue_para para;
 struct rpld_playlist * pl, * history;

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( txt == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 para = proto_simple_parse_addqueue(client, txt);

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( para.queue == (pli_t)-1 )
  return PROTO_SIMPLE_STATUS_ERROR;

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 pl = rpld_pl_get_by_id(para.queue);
 if ( pl == NULL )
  return PROTO_SIMPLE_STATUS_ERROR;

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 history = rpld_pl_get_by_id(para.history);
 if ( history == NULL ) {
  rpld_pl_unref(pl);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = ?", client, txt);

 if ( playback_add_queue(pl, para.backend, para.mixer, history) == -1 ) {
  rpld_pl_unref(pl);
  rpld_pl_unref(history);
  ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = PROTO_SIMPLE_STATUS_ERROR", client, txt);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 rpld_pl_unref(pl);
 rpld_pl_unref(history);

 playback_set_role(para.queue, para.role);

 ROAR_DBG("proto_simple_addqueue(client=%i, txt='%s') = PROTO_SIMPLE_STATUS_OK", client, txt);

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_listclients (int client) {
 char buf[256];
 int i;
 int proto;
 const char * name;

 for (i = 0; i < RPLD_CLIENTS_MAX; i++) {
  proto = client_get_proto(i);
  if ( proto == RPLD_PROTO_UNUSED )
   continue;

  name = client_get_name(i);

  buf[sizeof(buf)-2] = 0;
  snprintf(buf, sizeof(buf), "%3i: [protocol: \"%s\", acclev: %s, playlist: %i, queue: %i]%s%s%s\n",
           i, client_proto2str(proto), client_acclev2str(client_get_acclev(i)),
           client_get_playlist(i), client_get_queue(i),
           name == NULL ? "" : " \"", name == NULL ? "" : name, name == NULL ? "" : "\"");
  buf[sizeof(buf)-1] = 0;
  if ( buf[strlen(buf)-1] != '\n' ) {
   ROAR_WARN("proto_simple_listclients(client=%i): Buffer too small for client listing: client=%i, proto=%i", client, i, proto);
   snprintf(buf, sizeof(buf), "%3i: []\n", i);
   buf[sizeof(buf)-1] = 0;
  }
  proto_simple_client_puts(client, buf);
 }

 return PROTO_SIMPLE_STATUS_OK;
}

int proto_simple_identify(int client, char * txt) {
 const char * name = NULL;
 const char * nodename = NULL;
 pid_t pid = 0;
 long int hostid = 0;

 remove_spaces(&txt);

 while (txt != NULL && *txt) {
  remove_spaces(&txt);

  if ( !!strncasecmp(txt, "with ", 5) ) {
   roar_err_set(ROAR_ERROR_ILLSEQ);
   return PROTO_SIMPLE_STATUS_ERROR;
  }

  txt += 5;
  remove_spaces(&txt);

/*
 X NAME
 * PID
 * NODENAME
 * HOSTID
 */

  if ( !strncasecmp(txt, "name ", 5) ) {
   txt += 5;
   name = __parse_string(&txt, NULL);
  } else if ( !strncasecmp(txt, "pid ", 4) ) {
   txt += 4;
   pid = strtol(txt, &txt, 0);
  } else if ( !strncasecmp(txt, "nodename ", 9) ) {
   txt += 9;
   nodename = __parse_string(&txt, NULL);
  } else if ( !strncasecmp(txt, "hostid ", 7) ) {
   txt += 7;
   hostid = strtol(txt, &txt, 0);
  } else {
   roar_err_set(ROAR_ERROR_BADRQC);
   return PROTO_SIMPLE_STATUS_ERROR;
  }
 }

 // avoid compiler warnings. Those will likely be used later.
 _LIBROAR_IGNORE_RET(hostid);
 _LIBROAR_IGNORE_RET(pid);
 _LIBROAR_IGNORE_RET(nodename);

 if ( name != NULL )
  client_set_name(client, name);
 return PROTO_SIMPLE_STATUS_OK;
}

static int proto_simple_serverinfo(int client) {
 const struct roar_server_info * info = rpld_serverinfo_get();
 char buf[1024];

 proto_simple_client_puts(client, "X-APPLICATION \"" APPLICATION_NAME "\"\n");

#define _display2(var,prefix) \
 if ( info->var != NULL ) { \
  snprintf(buf, sizeof(buf), prefix "\n", info->var); \
  proto_simple_client_puts(client, buf); \
 }
#define _display(var,prefix) _display2(var,prefix " \"%s\"")
 _display(version,     "VERSION")
 _display(location,    "LOCATION")
 _display(description, "DESCRIPTION")
 _display(contact,     "CONTACT")
 _display(serial,      "SERIAL")
 _display(address,     "ADDRESS")
 _display(uiurl,       "UIURL")
 _display2(hostid,     "HOSTID %s")
 _display(license,     "LICENSE")
 _display(build,       "BUILD")
 _display(un.sysname,  "SYSTEM NAME")
 _display(un.release,  "SYSTEM RELEASE")
 _display(un.nodename, "SYSTEM NODENAME")
 _display(un.machine,  "SYSTEM MACHINE")
#undef _display
#undef _display2

 return PROTO_SIMPLE_STATUS_OK;
}

static inline void __searchple_handle_playlist(int client, struct rpld_playlist * pl, const void * needle, const char * key, int options, int neg) {
 struct rpld_playlist_entry * plent = rpld_pl_get_first(pl);
 int ret;

 while (plent != NULL) {
  ret = rpld_ple_filter(plent, needle, key, options);
//  if ( (ret == 1 && !neg) || (ret == 0 && neg) )
  if ( ret ^ neg )
   proto_simple_print_plent(client, plent);
  plent = plent->list.next;
 }
}

static int proto_simple_searchple(int client, char * args) {
 struct rpld_playlist       * pl = NULL;
 const void * needle;
 const char * key = NULL, * binkey = NULL;
 int options = RPLD_PLE_FILTER_NORMKEY;
 size_t i;
 char * next = NULL;
 int bneedle_si;
 discid_t bneedle_discid;
 roar_uuid_t bneedle_uuid;
 int neg = 0;
 int with_queues = 1, with_histories = 1;

 remove_spaces(&args);
 if ( *args == '"' || *args == '\'' ) {
  needle = __parse_string(&args, NULL);
 } else if ( !strncasecmp(args, "discid:", 7) ) {
  args += 7;
  binkey = "discid";
  if ( sscanf(args, "0x%x", &bneedle_si) != 1 ) {
   roar_err_set(ROAR_ERROR_ILLSEQ);
   return PROTO_SIMPLE_STATUS_ERROR;
  }
  bneedle_discid = bneedle_si;
  needle = &bneedle_discid;
 } else if ( !strncasecmp(args, "uuid:", 5) ) {
  args += 5;
  binkey = "uuid";
  if ( roar_str2uuid(bneedle_uuid, args) != 0 )
   return PROTO_SIMPLE_STATUS_ERROR;
  needle = &bneedle_uuid;
 } else if ( !strncasecmp(args, "genre:", 6) ) {
  args += 6;
  binkey = "genre";
  bneedle_si = roar_meta_intgenre(args);
  needle = &bneedle_si;
 } else if ( !strncasecmp(args, "tracknum:", 9) ) {
  args += 9;
  binkey = "tracknum";
  bneedle_si = atoi(args);
  needle = &bneedle_si;
 } else if ( !strncasecmp(args, "tracknumber:", 12) ) {
  args += 12;
  binkey = "tracknumber";
  bneedle_si = atoi(args);
  needle = &bneedle_si;
 } else {
  roar_err_set(ROAR_ERROR_PROTO);
  return PROTO_SIMPLE_STATUS_ERROR;
 }

 if ( binkey != NULL ) {
  args = strstr(args, " ");
  if ( args == NULL )
   args = "";
 }

 remove_spaces(&args);
 if ( !strncasecmp(args, "case", 4) ) {
  args += 4;
  options |= RPLD_PLE_FILTER_CASE;
  remove_spaces(&args);
  if ( !strncasecmp(args, "sensitive", 9) ) {
   args += 9;
   remove_spaces(&args);
  }
 }

 if ( !strncasecmp(args, "not ", 4) ) {
  neg ^= 1;
  args += 4;
  remove_spaces(&args);
 }

 if ( !strncasecmp(args, "in", 2) ) {
  args += 2;
 } else if ( !strncasecmp(args, "as", 2) || !strncasecmp(args, "is", 2) ) {
  args += 2;
  options |= RPLD_PLE_FILTER_ANCHORB|RPLD_PLE_FILTER_ANCHORE;
 } else if ( !strncasecmp(args, "at begin of", 11) ) {
  args += 11;
  options |= RPLD_PLE_FILTER_ANCHORB;
 } else if ( !strncasecmp(args, "at end of", 9) ) {
  args += 9;
  options |= RPLD_PLE_FILTER_ANCHORE;
 }
 remove_spaces(&args);

 if ( !strncasecmp(args, "not ", 4) ) {
  neg ^= 1;
  args += 4;
  remove_spaces(&args);
 }
 remove_spaces(&args);

 next = strstr(args, " ");
 if ( next != NULL ) {
  *next = 0;
  next++;
  remove_spaces(&next);
 }

 if ( !strcasecmp(args, "all") || !strcasecmp(args, "any") ) {
  key = NULL;
  options |= RPLD_PLE_FILTER_EXTKEY;
 } else if ( !strncasecmp(args, "tag:", 4) ) {
  args += 4;
  key = __parse_string(&args, NULL);
  options |= RPLD_PLE_FILTER_EXTKEY;
 } else if ( !!strcasecmp(args, "from") ){
  if ( *args ) {
   key = args;
  } else {
   key = NULL;
  }
 }

 if ( binkey != NULL ) {
  if ( key == NULL ) {
   key = binkey;
  } else if ( !!strcasecmp(key, binkey) ) {
   roar_err_set(ROAR_ERROR_USER);
   return PROTO_SIMPLE_STATUS_ERROR;
  }
  options |= RPLD_PLE_FILTER_BNEEDLE;
 }

 if ( next == NULL ) {
  pl = rpld_pl_get_by_id(client_get_playlist(client));
  if ( pl == NULL ) {
   roar_err_set(ROAR_ERROR_NOENT);
   return PROTO_SIMPLE_STATUS_OK;
  }
 } else {
  if ( !strncasecmp(next, "from", 4) )
   next += 4;
   remove_spaces(&next);
  if ( !strcasecmp(next, "any") || !strncasecmp(next, "any ", 4) ) {
   pl = NULL;
   next += 3;
   remove_spaces(&next);
   while ( !strncasecmp(next, "but ", 4) ) {
    next += 4;
    remove_spaces(&next);
    if ( !strncasecmp(next, "queues", 6) ) {
     with_queues = 0;
     next += 6;
    } else if ( !strncasecmp(next, "histories", 9) ) {
     with_histories = 0;
     next += 9;
    }
    remove_spaces(&next);
   }
  } else {
   pl = proto_simple_parse_pli(next, NULL);
   if ( pl == NULL ) {
    roar_err_set(ROAR_ERROR_NOENT);
    return PROTO_SIMPLE_STATUS_OK;
   }
  }
 }

 if ( pl != NULL ) {
  __searchple_handle_playlist(client, pl, needle, key, options, neg);
  rpld_pl_unref(pl);
 } else {
  for (i = 0; i < MAX_PLAYLISTS; i++) {
   pl = rpld_pl_get_by_id(i);
   if ( pl != NULL ) {
    if ( (pl->queue == NULL || with_queues) && (pl->histsize == -1 || with_histories) )
     __searchple_handle_playlist(client, pl, needle, key, options, neg);
    rpld_pl_unref(pl);
   }
  }
 }

 return PROTO_SIMPLE_STATUS_OK;
}

//ll
