//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>

#define META_BASE_URL_PREFIX "http://roaraudio.keep-cool.org/spec/xspf/meta-"
#define META_BASE_URL_SUFFIX ".txt"
#define META_BASE_URL_STATIC(x) (META_BASE_URL_PREFIX x META_BASE_URL_SUFFIX)
#define META_BASE_URL META_BASE_URL_STATIC("%s")

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

static int ff_xspf_read (void * context, char * buffer, int len) {
 ssize_t ret = roar_vio_read(context, buffer, len);
 ROAR_DBG("ff_xspf_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 xmlNodePtr new_meta_text_node(xmlNodePtr parent, const char * name, const char * value) {
 xmlNodePtr ret = xmlNewNode(NULL, (const xmlChar *)"meta");
 char buf[256];

 if ( ret == NULL )
  return NULL;

 snprintf(buf, sizeof(buf), META_BASE_URL, name);

 xmlSetProp(ret, (xmlChar *)"rel", (const xmlChar *)buf);

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

 xmlAddChild(parent, ret);

 return ret;
}

static void __add_filenames(xmlNodePtr track, struct rpld_playlist_entry * cur) {
 char buf[RPLD_MAX_PLF_LEN+64];
 const char * filename;
 xmlChar * location;
 ssize_t i, num;

 num = rpld_ple_num_filename(cur);

 for (i = 0; i < num; i++) {
  filename = rpld_ple_get_filename(cur, i, NULL);
  if ( filename == NULL )
   continue;

  if ( isalpha(filename[0]) ) {
   new_text_node(track, "location", filename);
  } else {
   location = xmlPathToURI((const xmlChar *)filename);
   snprintf(buf, sizeof(buf), "file://%s", location);
   xmlFree(location);
   new_text_node(track, "location", buf);
  }
 }
}

int ff_xspf_pl_export(struct fformat_handle * handle, struct roar_vio_calls * vio, struct rpld_playlist  * pl) {
 struct rpld_playlist       * parentpl;
 struct rpld_playlist_entry * cur    = rpld_pl_get_first(pl);
 int                          parent = rpld_pl_get_parent(pl);
 char buf[RPLD_MAX_PLF_LEN+64];
 xmlDocPtr doc;
 xmlNodePtr rootnode, tracklist, track;
 xmlOutputBufferPtr output;

 (void)handle;

 output = xmlAllocOutputBuffer(NULL);

 if ( output == NULL )
  return -1;

 output->context       = vio;
 output->writecallback = ff_xspf_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 *)"version", (xmlChar *)"1");
 xmlSetProp(rootnode, (xmlChar *)"xmlns", (xmlChar *)"http://xspf.org/ns/0/");

 xmlDocSetRootElement(doc, rootnode);

 if ( rpld_pl_get_name(pl) != NULL )
  new_text_node(rootnode, "title", rpld_pl_get_name(pl));

 if (parent != 0 && parent != -1) {
  snprintf(buf, sizeof(buf), "%d", parent);
  new_meta_text_node(rootnode, "parent-playlist-id", buf);
  parentpl = rpld_pl_get_by_id(parent);
  new_meta_text_node(rootnode, "parent-playlist-name", rpld_pl_get_name(parentpl));
  rpld_pl_unref(parentpl);
 }

 tracklist = xmlNewNode(NULL, (xmlChar *)"trackList");
 xmlAddChild(rootnode, tracklist);

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

  __add_filenames(track, cur);

  strcpy(buf, "tantalos://");
  roar_uuid2str(buf+11, cur->uuid, sizeof(buf)-11);
  new_text_node(track, "identifier", buf);

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

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

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

  if (cur->meta.performer[0] != 0)
   new_meta_text_node(track, "performer", cur->meta.performer);

  if (cur->meta.version[0] != 0)
   new_meta_text_node(track, "version", cur->meta.version);

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

  if (cur->meta.discid != 0 && cur->meta.discid != (discid_t)-1) {
   snprintf(buf, sizeof(buf), "0x%.8X", (int)cur->meta.discid);
   new_meta_text_node(track, "discid", buf);
  }

  if (cur->meta.genre != -1)
   new_meta_text_node(track, "genre", roar_meta_strgenre(cur->meta.genre));

  if (cur->likeness) {
   snprintf(buf, sizeof(buf), "%f", (double)cur->likeness);
   new_meta_text_node(track, "likeness", buf);
  }

  if (cur->codec != -1) {
   new_meta_text_node(track, "codec", roar_codec2str(cur->codec));
  }

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

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

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

 xmlFreeDoc(doc);

 return 0;
}

static void ff_xspf_pl_import_track_parse_meta(struct rpld_playlist_entry * plent,
                                               char ** dst, size_t * dst_len, const char ** value,
                                               xmlNode * element) {
 const char * name = (const char * /* libxml2 cast-hell */) xmlGetProp(element, (xmlChar *)"rel");
 int tmp;
 //Audacious: ???

 if ( !strcmp(name, META_BASE_URL_STATIC("performer")) ) {
  *dst = plent->meta.performer;
  *dst_len = sizeof(plent->meta.performer);
 } else if ( !strcmp(name, META_BASE_URL_STATIC("version")) ) {
  *dst = plent->meta.version;
  *dst_len = sizeof(plent->meta.version);
 } else if ( !strcmp(name, META_BASE_URL_STATIC("discid")) ) {
  if ( sscanf(*value, "0x%8x", &tmp) == 1 ) {
   plent->meta.discid = tmp;
  }
 } else if ( !strcmp(name, META_BASE_URL_STATIC("genre")) ) {
  plent->meta.genre = roar_meta_intgenre(*value);
 } else if ( !strcmp(name, META_BASE_URL_STATIC("likeness")) ) {
  plent->likeness = atof(*value);
 } else if ( !strcmp(name, META_BASE_URL_STATIC("codec")) ) {
  plent->codec = roar_str2codec(*value);
 }
}

static void ff_xspf_pl_import_track(struct rpld_playlist  * pl, xmlNode * track) {
 struct rpld_playlist_entry * plent = rpld_ple_new();
 int uuid_set = 0;
 char buf[RPLD_MAX_PLF_LEN+64];
 xmlNode * element;
 const char * name;
 const char * value;
 char * dst;
 size_t dst_len;
 int tmp;

 if ( plent == NULL )
  return;

 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);

  if (!strcmp(name, "location")) {
   // TODO.
   if ( !uuid_set && !strncmp(value, "tantalos://", 11) ) {
    // For error checking see below.
    value += 11;
    roar_str2uuid(plent->uuid, value);
    uuid_set = 1;
   }
   // Very ugly workaround as libxml2 has bad docs.
   // Prey this will never explode.
   if ( strlen(value) < (sizeof(buf)-1) ) {
    (void)xmlURIUnescapeString(value, -1, buf);
    buf[sizeof(buf)-1] = 0;
    rpld_ple_push_filename(plent, buf, RPLD_TYPE_UNKNOWN, 0);
   }
  } else if (!strcmp(name, "identifier")) {
   if ( !strncmp(value, "tantalos://", 11) ) {
    value += 11;
    // We can ignore errors here as uuid_parse() does not alter the UUID if parsing failed.
    // This is the documented behavior. Hope nobody will break it somewhen...
    roar_str2uuid(plent->uuid, value);
    uuid_set = 1;
   }
  } else if (!strcmp(name, "title")) {
   dst = plent->meta.title;
   dst_len = sizeof(plent->meta.title);
  } else if (!strcmp(name, "creator")) {
   dst = plent->meta.artist;
   dst_len = sizeof(plent->meta.artist);
  } else if (!strcmp(name, "album")) {
   dst = plent->meta.album;
   dst_len = sizeof(plent->meta.album);
  } else if (!strcmp(name, "trackNum")) {
   if ( sscanf(value, "%d", &tmp) == 1 ) {
    plent->meta.tracknum = tmp;
   }
  } else if (!strcmp(name, "duration")) {
   if ( sscanf(value, "%d", &tmp) == 1 ) {
    plent->length = tmp / 1000;
   }
  } else if (!strcmp(name, "meta")) {
   ff_xspf_pl_import_track_parse_meta(plent, &dst, &dst_len, &value, element);

#if 0
// The following is not relevant for us.
  } else if (!strcmp(name, "extension")) {
  } else if (!strcmp(name, "link")) {
  } else if (!strcmp(name, "annotation")) {
  } else if (!strcmp(name, "info")) {
  } else if (!strcmp(name, "image")) {
#endif
  }

  if ( dst != NULL ) {
   ROAR_DBG("ff_xspf_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_xspf_pl_import_track(pl=%p, track=%p): no data to copy (element type: %s).", pl, track, name);
  }
 }

 rpld_pl_push(pl, plent);
}

int ff_xspf_pl_import(struct fformat_handle * handle, struct roar_vio_calls * vio, struct rpld_playlist  * pl) {
 xmlDoc * doc;
 xmlNode * main_element;
 xmlNode * playlist_element;
 xmlNode * tracklist_element;

 (void)handle;

 doc = xmlReadIO(ff_xspf_read, NULL, vio, "__dummy.xspf", 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;

  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 *)"trackList") )
    continue;

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

    if ( !!xmlStrcmp(tracklist_element->name, (xmlChar *)"track") )
     continue;

    ff_xspf_pl_import_track(pl, tracklist_element);
   }
  }
 }

 xmlFreeDoc(doc);

 return 0;
}
#endif

//ll
