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

#ifdef HAVE_LIB_XML2
#include <ctype.h>

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/uri.h>

static int ff_uras_write(void * context, const char * buffer, int len) {
 return roar_vio_write(context, (void*)buffer, len);
}

static int ff_uras_read (void * context, char * buffer, int len) {
 ssize_t ret = roar_vio_read(context, buffer, len);
 ROAR_DBG("ff_uras_read(context=%p, buffer=%p, len=%i) = %i(?)", context, buffer, len, (int)ret);
 return ret;
}

static xmlNodePtr new_text_node(xmlNodePtr parent, const char * name, const char * value) {
 xmlNodePtr ret = xmlNewNode(NULL, (const xmlChar *)name);

 if ( ret == NULL )
  return NULL;

 xmlAddChild(ret, xmlNewText((const xmlChar *)value));

 xmlAddChild(parent, ret);

 return ret;
}

static inline xmlNodePtr new_pos_node(xmlNodePtr parent, const char * name, rpld_mus_t pos) {
 xmlNodePtr ret = xmlNewNode(NULL, (const xmlChar *)name);
 char buf[80];

 if ( ret == NULL )
  return NULL;

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

 snprintf(buf, sizeof(buf), "%ld", (long int)pos/1000);
 xmlSetProp(ret, (xmlChar *)"pos", (const xmlChar *)buf);

 xmlAddChild(parent, ret);

 return ret;
}

int ff_uras_pl_export(struct fformat_handle * handle, struct roar_vio_calls * vio, struct rpld_playlist  * pl) {
 const struct roar_server_info * info = rpld_serverinfo_get();
 struct rpld_playlist_entry * cur    = rpld_pl_get_first(pl);
 char buf[RPLD_MAX_PLF_LEN+64];
 xmlDocPtr doc;
 xmlNodePtr rootnode, track;
 xmlOutputBufferPtr output;
 ssize_t i;
 struct roar_keyval * extra;
 const char * file;

 (void)handle;

 output = xmlAllocOutputBuffer(NULL);

 if ( output == NULL )
  return -1;

 output->context       = vio;
 output->writecallback = ff_uras_write;
 output->closecallback = NULL;

 doc = xmlNewDoc((xmlChar *)"1.0");

 if ( doc == NULL )
  return -1;

 doc->charset = XML_CHAR_ENCODING_UTF8;
 doc->encoding = xmlStrdup((xmlChar *)"UTF-8");

 rootnode = xmlNewNode(NULL, (xmlChar *)"playlist");
 xmlSetProp(rootnode, (xmlChar *)"uribase", (xmlChar *)"/");

 xmlDocSetRootElement(doc, rootnode);

 if ( rpld_pl_get_name(pl) != NULL ) {
  new_text_node(rootnode, "name", rpld_pl_get_name(pl));
 } else {
  new_text_node(rootnode, "name", "default");
 }

 new_text_node(rootnode, "location", info->location == NULL ? "default" : info->location);
 new_text_node(rootnode, "date", "default");
 new_text_node(rootnode, "rgain", "ON");
 new_text_node(rootnode, "resync", "ON");

 while ( cur != NULL ) {
  track = xmlNewNode(NULL, (xmlChar *)"track");

  file = rpld_ple_get_filename(cur, -1, NULL);
  if (file != NULL) {
   xmlSetProp(track, (xmlChar *)"uri", (const xmlChar *)file);
  }

  if (cur->meta.title[0] != 0)
   new_text_node(track, "name", cur->meta.title);

  if (cur->meta.tracknum) {
   snprintf(buf, sizeof(buf), "%d", (int)cur->meta.tracknum);
   new_text_node(track, "tracknum", buf);
  }

  switch (cur->meta.genre) {
#ifdef ROAR_META_GENRE_META_OTHER
   case ROAR_META_GENRE_META_OTHER:
     new_text_node(track, "Type", "Nonsong");
    break;
#endif
#ifdef ROAR_META_GENRE_META_COMMERCIAL
   case ROAR_META_GENRE_META_COMMERCIAL:
     new_text_node(track, "Type", "Comm");
    break;
#endif
   default:
     new_text_node(track, "Type", "Song");
    break;
  }

  if (cur->length) {
   snprintf(buf, sizeof(buf), "%d", (int)cur->length);
   new_text_node(track, "duration", buf);
  }

  new_pos_node(track, "start", cur->start);
  new_pos_node(track, "end", cur->end);

  for (i = 0; i < cur->meta.extra.kvlen; i++) {
   extra = &(cur->meta.extra.kv[i]);
   if ( extra->key == NULL || extra->value == NULL )
    continue;
   if ( !strcasecmp(extra->key, "X-URAS-START") ) {
    new_text_node(track, "start", extra->value);
   } else if ( !strcasecmp(extra->key, "X-URAS-MAPNUM") ) {
    new_text_node(track, "mapnum", extra->value);
   }
  }

  xmlAddChild(rootnode, track);
  cur = cur->list.next;
 }

 xmlSaveFormatFileTo(output, doc, "UTF-8", 1);

 xmlFreeDoc(doc);

 return 0;
}

static void ff_uras_pl_import_track(struct rpld_playlist  * pl, xmlNode * track, const char * uribase, int rgain) {
 struct rpld_playlist_entry * plent = rpld_ple_new();
 xmlNode * element;
 const char * name;
 const char * value;
 char * dst;
 size_t dst_len;
 int tmp;
 const char * start = NULL;
 const char * mapnum = NULL;
 char filenamebuffer[RPLD_MAX_PLF_LEN];

 ROAR_DBG("ff_uras_pl_import_track(pl=%p, track=%p, uribase='%s') = ?", pl, track, uribase);

 if ( plent == NULL )
  return;

 plent->rpg.mode = rgain ? ROAR_RPGMODE_TRACKALBUM : ROAR_RPGMODE_NONE;

 value = (const char * /* libxml2 cast-hell */) xmlGetProp(track, (xmlChar *)"uri");
 snprintf(filenamebuffer, sizeof(filenamebuffer)-1, "%s/%s", uribase, value);
 rpld_ple_push_filename(plent, filenamebuffer, RPLD_TYPE_UNKNOWN, 1);

 // guess codec:
 // as per sepcs only ogg_vorbis and native flac is allowed.
 value = strrchr(value, '.');
 if ( value != NULL ) {
  value++;
  if ( !strcasecmp(value, "ogg") ) {
   plent->codec = ROAR_CODEC_OGG_VORBIS;
  } else if ( !strcasecmp(value, "flac") ) {
   plent->codec = ROAR_CODEC_FLAC;
  }
 }

 for (element = track->children; element != NULL; element = element->next) {
  if ( element->type != XML_ELEMENT_NODE )
   continue;

  dst     = NULL;
  dst_len = 0;
  name    = (const char * /* libxml2 cast-hell */) element->name;
  value   = (const char * /* libxml2 cast-hell */) xmlNodeGetContent(element);

  ROAR_DBG("ff_uras_pl_import_track(pl=%p, track=%p, uribase='%s'): name='%s', value='%s'", pl, track, uribase, name, value);

  if (!strcmp(name, "tracknum")) {
   plent->meta.tracknum = atoi(value);
  } else if (!strcmp(name, "name")) {
   dst = plent->meta.title;
   dst_len = sizeof(plent->meta.title);
  } else if (!strcmp(name, "trackNum")) {
   if ( sscanf(value, "%d", &tmp) == 1 ) {
    plent->meta.tracknum = tmp;
   }
  } else if (!strcmp(name, "mapnum")) {
   mapnum = value;
  } else if (!strcmp(name, "duration")) {
   if ( sscanf(value, "%d", &tmp) == 1 ) {
    plent->length = tmp;
   }
  } else if (!strcmp(name, "start") && xmlGetProp(element, (const xmlChar *)"pos") == NULL ) { // this is the start time of the track in wallclock time.
   start = value;
  } else if (!strcmp(name, "start") && xmlGetProp(element, (const xmlChar *)"pos") != NULL ) { // this is the start time of the content within the track.
   value = (const char * /* libxml2 cast-hell */)xmlGetProp(element, (const xmlChar *)"pos");
   if ( value == NULL ) {
    ROAR_WARN("ff_uras_pl_import_track(pl=%p, track=%p, uribase='%s'): No Prop \"pos\" in \"start\" tag: Bad. Playlist file is corruped.", pl, track, uribase);
    continue;
   }
   plent->start = 1000*atoi(value);
   if ( plent->start == 0 )
    plent->start = -1;
  } else if (!strcmp(name, "end")) {
   value = (const char * /* libxml2 cast-hell */)xmlGetProp(element, (const xmlChar *)"pos");
   if ( value == NULL ) {
    ROAR_WARN("ff_uras_pl_import_track(pl=%p, track=%p, uribase='%s'): No Prop \"pos\" in \"end\" tag: Bad. Playlist file is corruped.", pl, track, uribase);
    continue;
   }
   plent->end = 1000*atoi(value);
   if ( plent->end == 0 )
    plent->end = -1;
  } else if (!strcmp(name, "Type")) {
   if ( 0 ) {
    /* noop to ensure correct syntax */
#ifdef ROAR_META_GENRE_META_MUSIC
   } else if ( !strcasecmp(value, "Song") ) {
    plent->meta.genre = ROAR_META_GENRE_META_MUSIC;
#endif
#ifdef ROAR_META_GENRE_META_OTHER
   } else if ( !strcasecmp(value, "Nonong") ) {
    plent->meta.genre = ROAR_META_GENRE_META_OTHER;
#endif
#ifdef ROAR_META_GENRE_META_COMMERCIAL
   } else if ( !strcasecmp(value, "Comm") ) {
    plent->meta.genre = ROAR_META_GENRE_META_COMMERCIAL;
#endif
   }
  }

  if ( dst != NULL ) {
   ROAR_DBG("ff_uras_pl_import_track(pl=%p, track=%p): Copy '%s' to %p (element type: %s).", pl, track, value, dst, name);
   strncpy(dst, value, dst_len);
   dst[dst_len-1] = 0;
  } else {
   ROAR_DBG("ff_uras_pl_import_track(pl=%p, track=%p): no data to copy (element type: %s).", pl, track, name);
  }
 }

 // push in, do this now so we can do more easy error handling in the next block:
 rpld_pl_push(pl, plent);


 plent->meta.extra.kv = roar_mm_malloc(sizeof(struct roar_keyval)*2);
 if ( plent->meta.extra.kv == NULL )
  return;

 plent->meta.extra.kvlen = 2;
 plent->meta.extra.kv[0].key = "X-URAS-START";
 plent->meta.extra.kv[1].key = "X-URAS-MAPNUM";

 if ( start == NULL )
  start = "";
 if ( mapnum == NULL )
  mapnum = "";

 plent->meta.extra.kv_storelen = roar_mm_strlen(start) + roar_mm_strlen(mapnum) + 2;
 plent->meta.extra.kv_store = roar_mm_malloc(plent->meta.extra.kv_storelen);
 if ( plent->meta.extra.kv_store == NULL ) {
  roar_mm_free(plent->meta.extra.kv);
  plent->meta.extra.kv = NULL;
  plent->meta.extra.kvlen = -1;
  return;
 }

 plent->meta.extra.kv[0].value = plent->meta.extra.kv_store;
 plent->meta.extra.kv[1].value = plent->meta.extra.kv_store + roar_mm_strlen(start) + 1;

 strcpy(plent->meta.extra.kv[0].value, start);
 strcpy(plent->meta.extra.kv[1].value, mapnum);
}

int ff_uras_pl_import(struct fformat_handle * handle, struct roar_vio_calls * vio, struct rpld_playlist  * pl) {
 xmlDoc * doc;
 xmlNode * main_element;
 xmlNode * playlist_element;
 const char * uribase = "/DUMMY/";
 int rgain = 0;
 const xmlChar * value;

 (void)handle;

 doc = xmlReadIO(ff_uras_read, NULL, vio, "__dummy.xml", NULL, XML_PARSE_RECOVER);

 if ( doc == NULL )
  return -1;

 for (main_element = doc->children; main_element != NULL; main_element = main_element->next) {
  if ( main_element->type != XML_ELEMENT_NODE )
   continue;

  if ( !!xmlStrcmp(main_element->name, (xmlChar *)"playlist") )
   continue;

  uribase = (const char * /* libxml2 cast-hell */) xmlGetProp(main_element, (xmlChar *)"uribase");
  if ( uribase == NULL )
   uribase = "/DUMMY/";

  for (playlist_element = main_element->children; playlist_element != NULL; playlist_element = playlist_element->next) {
   if ( playlist_element->type != XML_ELEMENT_NODE )
    continue;

   if ( !xmlStrcmp(playlist_element->name, (xmlChar *)"track") ) {
    ff_uras_pl_import_track(pl, playlist_element, uribase, rgain);
   } else if ( !xmlStrcmp(playlist_element->name, (xmlChar *)"rgain") ) {
    value = xmlNodeGetContent(playlist_element);
    if ( value == NULL )
     continue;
    rgain = !xmlStrcmp(value, (xmlChar *)"ON") ? 1 : 0;
//   } else if ( !xmlStrcmp(playlist_element->name, (xmlChar *)"resync") ) {
   }
  }
 }

 xmlFreeDoc(doc);

 return 0;
}
#endif

//ll
