//proto_mpd.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2010-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"

#define YES 1
#define NO  0
#define ND -1

static struct {
 int auth;
 char * cmd;
} _commands[] = {
 {NO,  "setvol"},
 {NO,  "stop"},
 {NO,  "play"},
 {NO,  "playid"},
 {NO,  "next"},
 {NO,  "previous"},
 {NO,  "status"},
 {NO,  "ping"},
 {NO,  "close"},
 {NO,  "commands"},
 {NO,  "notcommands"},
 {NO,  "outputs"},
 {NO,  "urlhandlers"},
 {NO,  "currentsong"},
 {NO,  "playlistid"},
 {NO,  "stats"},
/* not supported commands: */
 {ND,  "disableoutput"},
 {ND,  "enableoutput"},
 {ND,  "kill"},
 {ND,  "update"},
 {ND,  "tagtypes"},
 {ND,  "find"},
 {ND,  "list"},
 {ND,  "listall"},
 {ND,  "listallinfo"},
 {ND,  "lsinfo"},
 {ND,  "search"},
 {ND,  "count"},
 {ND,  "add"},
 {ND,  "addid"},
 {ND,  "clear"},
 {ND,  "delete"},
 {ND,  "deleteid"},
 {ND,  "load"},
 {ND,  "rename"},
 {ND,  "move"},
 {ND,  "moveid"},
 {ND,  "playlist"},
 {ND,  "playlistinfo"},
 {ND,  "plchanges"},
 {ND,  "plchangesposid"},
 {ND,  "rm"},
 {ND,  "save"},
 {ND,  "shuffle"},
 {ND,  "swap"},
 {ND,  "swapid"},
 {ND,  "listplaylist"},
 {ND,  "listplaylistinfo"},
 {ND,  "playlistadd"},
 {ND,  "playlistclear"},
 {ND,  "playlistdelete"},
 {ND,  "playlistmove"},
 {ND,  "playlistfind"},
 {ND,  "playlistsearch"},
 {ND,  "crossfade"},
 {ND,  "random"},
 {ND,  "repeat"},
 {ND,  "seek"},
 {ND,  "seekid"},
 {ND,  "setvol"},
 {ND,  "volume"},
 {ND,  "clearerror"},
 {ND,  "password"},
 {NO,  NULL}
};

static char * _dequote (char * str) {
 ssize_t len;

 if ( str == NULL )
  return NULL;

 if ( *str != '"' )
  return str;

 str++;

 len = strlen(str);

 str[len-1] = 0;

 return str;
}

static void _print_handlers(struct roar_vio_calls * vio) {
 char * h[] = {
  "file",
  "fh",
  "socket",
  "http",
  "gopher",
  "icy",
  "tantalos",
  NULL
 };
 int i;

 for (i = 0; h[i] != NULL; i++)
  roar_vio_printf(vio, "handler: %s://\n", h[i]);
}

struct rpld_playlist_entry * proto_mpd_parse_id(int client, char * args) {
 struct rpld_playlist_search    pls;
 struct rpld_playlist_entry *   ret;
 int i;
 int bit31;

 (void)client;

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

 pls.type = RPLD_PL_ST_TRACKNUM_SHORT;
 pls.subject.short_tracknum = atol(args);

 for (bit31 = 0; bit31 == 0 || bit31 == 1; bit31++) {
  pls.subject.short_tracknum |= bit31 ? ((uint32_t)1<<31) : 0;
  for (i = 0; i < MAX_PLAYLISTS; i++) {
   if ( g_playlists[i] != NULL ) {
    if ( (ret = rpld_pl_search(g_playlists[i], &pls, NULL)) != NULL )
     return ret;
   }
  }
 }

 return NULL;
}

#define _pk(k, kt, v) roar_vio_printf(vio, k ": " kt "\n", (v))
#define _pks(k, v) _pk(k, "%s", v)
int proto_mpd_print_song (int id, struct roar_vio_calls * vio, struct rpld_proto_mpd_error * error, struct rpld_playlist_entry * ple, ssize_t pos) {
 uint32_t songid;
/*
Xfile: 2007-omc3-203_orange_sofa-the_one_you_need.ogg
XTime: 209
XAlbum: open music contest #3: "by perseverance the snail reached art" (black disc)
XArtist: orange sofa
Date: 2007
Disc: 2
XGenre: Creative Commons
XTitle: the one you need
XTrack: 03
Pos: 0
Id: 0
*/

 (void)id;
 (void)error;

 if ( ple == NULL )
  return -1;

 _pks("file", rpld_ple_get_filename(ple, -1, NULL));

 if ( ple->length )
  _pk("Time", "%lu", (long unsigned int)ple->length);

 if ( ple->meta.album[0] != 0 )
  _pks("Album", ple->meta.album);

 if ( ple->meta.artist[0] != 0 )
  _pks("Artist", ple->meta.artist);

 _pks("Genre", roar_meta_strgenre(ple->meta.genre));

 if ( ple->meta.title[0] != 0 )
  _pks("Title", ple->meta.title);

 if ( ple->meta.tracknum )
  _pk("Track", "%i", ple->meta.tracknum);

 if ( pos != -1 )
  _pk("Pos", "%lu", (long unsigned int)pos);

 songid = ple->global_short_tracknum & (uint32_t)0x7FFFFFFF;

 _pk("Id", "%lu", (long unsigned int)songid);

 return 0;
}

int proto_mpd_client_set_proto(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) {
#define NEW_CLIENT_STRING "OK MPD " RPLD_PROTO_MPD_VERSION "\n"

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

 if ( roar_vio_puts(vio, NEW_CLIENT_STRING) != (sizeof(NEW_CLIENT_STRING)-1) )
  return -1;

 return 0;
}

int proto_mpd_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) {
 struct rpld_proto_mpd_error error;
 ssize_t len;
 char * args;
 int    ret;
 char buffer[1024];

 (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 ( (args = strstr(buffer, " ")) != NULL ) {
  *args = 0;
   args++;
 }

 error.error  = RPLD_PROTO_MPD_ACK_ERROR_UNKNOWN;
 error.offset = 0;
 error.cmd    = buffer;
 error.msg    = "Unknown error";

 ret = proto_mpd_pcmd(id, vio, &error, buffer, args);

 if ( error.error == RPLD_PROTO_MPD_ACK_ERROR_DISCONNECT )
  return -1;

 if ( ret == -1 ) {
  //ACK [5@2] {} unknown command "pay"
  roar_vio_printf(vio, "ACK [%i@%i] {%s} %s\n", error.error, error.offset, error.cmd, error.msg);
 } else {
  if ( roar_vio_puts(vio, "OK\n") != 3 )
   return -1;
 }

 return 0;
}

#define NULL_OK     0
#define NULL_NOT_OK 1
#define _CK_ARGS(ok) if ( (ok) && args == NULL ) { \
                    error->error  = RPLD_PROTO_MPD_ACK_ERROR_UNKNOWN; \
                    error->msg    = "Protocol error"; \
                    return -1; \
                   }

int proto_mpd_pcmd(int id, struct roar_vio_calls * vio, struct rpld_proto_mpd_error * error, char * cmd, char * args) {
 pli_t queue = client_get_queue(id);
 struct rpld_playlist * pl;
 struct rpld_playlist_entry * plent;
 const struct roar_stream   * s;
 ssize_t pos = 0;
 int i;

 ROAR_DBG("proto_mpd_pcmd(id=%i, vio=%p, error=%p, cmd='%s', args='%s') = ?", id, vio, error, cmd, args);

 // handle null-string-command:
 if ( *cmd == 0 )
  return 0;

 /* basic commands */
 if ( !strcasecmp(cmd, "close") ) {
  _CK_ARGS(NULL_OK);
  error->error = RPLD_PROTO_MPD_ACK_ERROR_DISCONNECT;
  return 0;
 } else if ( !strcasecmp(cmd, "ping") ) {
  _CK_ARGS(NULL_OK);
  return 0;
 /* status commands */
 } else if ( !strcasecmp(cmd, "stats") ) {
  _CK_ARGS(NULL_OK);
/*
artists: 1
albums: 1
songs: 1
uptime: 6562
playtime: 82
db_playtime: 209
db_update: 1264178670
*/
  return 0; // send no info at the moment
 } else if ( !strcasecmp(cmd, "status") ) {
  _CK_ARGS(NULL_OK);
  roar_vio_printf(vio, "volume: %i\n", playback_get_volume_mpc(queue));
  roar_vio_printf(vio, "repeat: 0\n");
  roar_vio_printf(vio, "random: 0\n");
  roar_vio_printf(vio, "playlist: 0\n");
  roar_vio_printf(vio, "playlistlength: 0\n"); // count global number of PLEs
  roar_vio_printf(vio, "xfade: 0\n");
  if ( playback_is_playing(queue) ) {
   if ( playback_is_pause(queue) == PLAYBACK_PAUSE_TRUE ) {
    roar_vio_printf(vio, "state: pause\n");
   } else {
    roar_vio_printf(vio, "state: play\n");
   }
   if ( (plent = playback_get_ple(queue)) != NULL && (s = playback_get_stream(queue)) != NULL ) {
    roar_vio_printf(vio, "song: 0\n");
    roar_vio_printf(vio, "songid: %u\n", plent->global_short_tracknum & (uint32_t)0x7FFFFFFF);
    roar_vio_printf(vio, "time: %u:%u\n", (int)((float)s->pos/(s->info.rate*s->info.channels)), (unsigned)plent->length);
    roar_vio_printf(vio, "audio: %i:%i:%i\n", s->info.rate, s->info.bits, s->info.channels);
   }
  } else {
   roar_vio_printf(vio, "state: stop\n");
  }
  return 0;
 } else if ( !strcasecmp(cmd, "outputs") ) {
  _CK_ARGS(NULL_OK);
   roar_vio_printf(vio, "outputid: 0\n");
   roar_vio_printf(vio, "outputname: RoarAudio output\n");
   roar_vio_printf(vio, "outputenabled: 1\n");
  return 0;
 } else if ( !strcasecmp(cmd, "urlhandlers") ) {
  _CK_ARGS(NULL_OK);
   _print_handlers(vio);
  return 0;
 /* playback commands */
 } else if ( !strcasecmp(cmd, "next") ) {
  _CK_ARGS(NULL_OK);
  if ( playback_next(queue) != -1 )
   return 0;
  return -1;
 } else if ( !strcasecmp(cmd, "previous") ) {
  _CK_ARGS(NULL_OK);
  if ( playback_prev(queue) != -1 )
   return 0;
  return -1;
 } else if ( !strcasecmp(cmd, "play") || !strcasecmp(cmd, "playid") ) {
  _CK_ARGS(NULL_OK);
  args = _dequote(args);
  if ( args != NULL && !strcmp(args, "-1") )
   args = NULL;

  if ( args != NULL ) {
   plent = proto_mpd_parse_id(id, args);
   if ( plent == NULL ) {
    error->msg = "Song not found";
    return -1;
   }

   plent = rpld_ple_copy(plent);
   if ( plent == NULL ) {
    error->msg = "Can not copy playlist entry into queue";
    return -1;
   }

   pl = rpld_pl_get_by_id(queue);
   if ( pl != NULL ) {
    rpld_pl_add(pl, plent, 0);
    rpld_pl_unref(pl);
   }
   playback_next(queue);
  }

  if ( playback_play(queue) != -1 )
   return 0;
  return -1;
 } else if ( !strcasecmp(cmd, "stop") ) {
  _CK_ARGS(NULL_OK);
  if ( playback_stop(queue, 0, 1) != -1 )
   return 0;
  return -1;
 } else if ( !strcasecmp(cmd, "setvol") ) {
  _CK_ARGS(NULL_NOT_OK);
  args = _dequote(args);
  if ( playback_set_volume_mpc(queue, atoi(args)) == -1 )
   return -1;
  return 0;
 } else if ( !strcasecmp(cmd, "pause") ) {
  _CK_ARGS(NULL_OK);
  args = _dequote(args);
  i = PLAYBACK_PAUSE_TOGGLE;

  if ( args != NULL ) {
   if ( atoi(args) ) {
    i = PLAYBACK_PAUSE_TRUE;
   } else {
    i = PLAYBACK_PAUSE_FALSE;
   }
  }
  if ( playback_pause(queue, i) != -1 )
   return 0;

  return -1;
 /* plylist commands */
 } else if ( !strcasecmp(cmd, "currentsong") ) {
  _CK_ARGS(NULL_OK);
  if ( playback_is_playing(queue) ) {
   plent = playback_get_ple(queue);
  } else {
   pl = rpld_pl_get_by_id(queue);
   if ( pl == NULL ) {
    plent = NULL;
   } else {
    plent = rpld_pl_get_first(pl);
    rpld_pl_unref(pl);
   }
  }

  if ( plent == NULL ) {
   error->error = RPLD_PROTO_MPD_ACK_ERROR_UNKNOWN; // FIXME
   error->msg   = "Can not find current song";
   return -1;
  }
  return proto_mpd_print_song(id, vio, error, plent, 0);
 } else if ( !strcasecmp(cmd, "playlistid") ) {
  _CK_ARGS(NULL_OK);
  ROAR_DBG("proto_mpd_pcmd(id=%i, ...): cmd='%s', args='%s'", id, cmd, args);
  args = _dequote(args);

  if ( args != NULL && !strcmp(args, "-1") )
   args = NULL;

  if ( args != NULL ) {
   plent = proto_mpd_parse_id(id, args);
   if ( plent == NULL ) {
    error->msg = "Song not found";
    return -1;
   }
   proto_mpd_print_song(id, vio, error, plent, pos++);
   return 0;
  }

  for (i = 0; i < MAX_PLAYLISTS; i++) {
   pl = rpld_pl_get_by_id(i);
   plent = rpld_pl_get_first(pl);

   while (plent != NULL) {
    proto_mpd_print_song(id, vio, error, plent, pos++);
    plent = plent->list.next;
   }

   rpld_pl_unref(pl);
  }

  return 0;
 /* control commands */
 } else if ( !strcasecmp(cmd, "commands") ) {
  _CK_ARGS(NULL_OK);
  for (i = 0; _commands[i].cmd != NULL; i++) {
   if ( _commands[i].auth == NO )
    roar_vio_printf(vio, "command: %s\n", _commands[i].cmd);
  }
  return 0;
 } else if ( !strcasecmp(cmd, "notcommands") ) {
  _CK_ARGS(NULL_OK);
  for (i = 0; _commands[i].cmd != NULL; i++) {
   if ( _commands[i].auth != NO )
    roar_vio_printf(vio, "command: %s\n", _commands[i].cmd);
  }
  return 0;
 }

 error->error = RPLD_PROTO_MPD_ACK_ERROR_UNKNOWN;
 error->msg   = "Unsupported command";
 return -1;
}

//ll
