//  BMP
//  Copyright (C) 2005 BMP development.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License Version 2 as 
//  published by the Free Software Foundation.
//
//  This program 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 program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  --
//
//  The BMPx project hereby grants permission for non-GPL compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif //HAVE_CONFIG_H

#include <glibmm.h>
#include <glibmm/i18n.h>

#include <boost/optional.hpp>
#include <boost/format.hpp>

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

#include "bmp/bmp.hh"

#include "src/util-string.hh"
#include "src/util-file.hh"
#include "src/uri.hh"
#include "src/xml.hh"
#include "src/vfs.hh"

#ifdef HAVE_HAL
#  include "src/x_hal.hh"
#endif //HAVE_HAL

using namespace Glib;
using namespace Bmp;

namespace
{
    struct XSPFTrack
    {
      ustring   location;
#ifdef HAVE_HAL
      boost::optional<ustring> volume_udi;
      boost::optional<ustring> device_udi;
        // ustrings here as we read it off XML

      boost::optional<ustring> volume_relative_path;
#endif //HAVE_HAL
    };

    ustring
    get_cstr (xmlChar* str)
    {
      ustring ustr = (char*)str;
      g_free (str);
      return ustr;
    }

    const char* XSPF_ROOT_NODE_NAME = "playlist";
    const char* XSPF_XMLNS = "http://xspf.org/ns/0/";
}

namespace Bmp
{
  namespace VFS
  {
      class PluginContainerXSPF
          : public Bmp::VFS::PluginContainerBase
      {
        public:

          virtual bool
          can_process (ustring const& uri)
          {
            URI u (uri);
            return Util::str_has_suffix_nocase (u.path, ".xspf");
          }

          virtual void 
          handle_read	(Handle & handle, VUri & list)
            throw (ProcessingError)
          {
            using namespace Glib;

            xmlXPathObjectPtr   xo;
            xmlNodeSetPtr	      nv;
            xmlDocPtr	          doc;

            if (!handle.get_buffer())
              return;

            doc = xmlParseDoc (BAD_CAST handle.get_buffer());
            if (!doc)
              return;

#ifdef HAVE_HAL
            xo = xpath_query
                  (doc,
                   BAD_CAST "//xspf:track",
                   BAD_CAST "xspf=http://xspf.org/ns/0/ bmp=http://beep-media-player.org/ns/0/"); 
#else
            xo = xpath_query
                  (doc,
                   BAD_CAST "//xspf:track",
                   BAD_CAST "xspf=http://xspf.org/ns/0/"); 
#endif //HAVE_HAL

            if (!xo) 
            {
              xmlFreeDoc (doc);
              return;
            }

            nv = xo->nodesetval;
            if (!nv)
            {
              xmlFreeDoc (doc);
              return;
            }

#ifdef HAVE_HAL
            xmlNodePtr trackNode, siblingNode;
            for (int n = 0; n < nv->nodeNr; n++)
            {
              trackNode = nv->nodeTab[n];
              siblingNode = trackNode->children;

              XSPFTrack track;
              while (siblingNode)
              {
                if ((siblingNode->type == XML_ELEMENT_NODE) && (siblingNode->children))
                {
                    if (!std::strcmp (reinterpret_cast<const char *> (siblingNode->name),
                                      reinterpret_cast<const char *> ("location")))
                    {
                        track.location = get_cstr (XML_GET_CONTENT (siblingNode->children));
                    }

                    if (!std::strcmp (reinterpret_cast<const char *> (siblingNode->name),
                                      reinterpret_cast<const char *> ("extension")))
                    {
                        ustring app = get_cstr (xmlGetProp (siblingNode, BAD_CAST "application"));
                        if (app == "http://beep-media-player.org")
                          {
                            xmlNodePtr extNode = siblingNode->children; 
                            while (extNode)
                              {
                                if (!std::strcmp (reinterpret_cast<const char *> (extNode->name),
                                                  reinterpret_cast<const char *> ("volume-udi")))
                                  {
                                    track.volume_udi = get_cstr (XML_GET_CONTENT (extNode->children));
                                  }

                                if (!std::strcmp (reinterpret_cast<const char *> (extNode->name),
                                                  reinterpret_cast<const char *> ("device-udi")))
                                  {
                                    track.device_udi = get_cstr (XML_GET_CONTENT (extNode->children));
                                  }

                                if (!std::strcmp (reinterpret_cast<const char *> (extNode->name),
                                                  reinterpret_cast<const char *> ("volume-relative-path")))
                                  {
                                    track.volume_relative_path = get_cstr (XML_GET_CONTENT (extNode->children));
                                  }
                                extNode = extNode->next;
                              }
                          }
                    }
                }
                siblingNode = siblingNode->next;
              }

              if (track.volume_udi && track.device_udi && track.volume_relative_path)
              {
                try{
                    std::string mount_path
                      (hal->get_mount_point_for_volume (track.volume_udi.get().c_str(),
                                                        track.device_udi.get().c_str()));
                    track.location =
                      filename_to_uri (build_filename (mount_path, track.volume_relative_path.get()));
                    list.push_back (track.location);
                  }
                catch (ConvertError & cxe)
                  {
                    throw ProcessingError (_("Unable to convert filename"));
                  }
                catch (HAL::Exception & cxe)
                  {
                    throw ProcessingError ((boost::format (_("HAL Error: %s")) % cxe.what()).str());
                  }
              }
            }
#else
            xmlNodePtr trackNode, siblingNode;
            for (int n = 0; n < nv->nodeNr; n++)
            {
              trackNode = nv->nodeTab[n];
              siblingNode = trackNode->children;

              XSPFTrack track;
              while (siblingNode)
              {
                if ((siblingNode->type == XML_ELEMENT_NODE) && (siblingNode->children))
                {
                  if (!std::strcmp (reinterpret_cast<const char *> (siblingNode->name),
                                    reinterpret_cast<const char *> ("location")))
                  {
                    track.location = get_cstr ((XML_GET_CONTENT (siblingNode->children)));
                  }
                }
                siblingNode = siblingNode->next;
              }
              list.push_back (track.location);
            }
#endif //HAVE_HAL
            xmlXPathFreeObject (xo);
          }

          virtual bool
          can_write ()
          {
            return true;
          }

          virtual void
          handle_write (Handle & handle, TrackV const& list)
            throw (ProcessingError)
          {

            xmlDocPtr  doc;
            xmlNsPtr   ns_bmp, ns_xspf;
            xmlNodePtr node_playlist,
                       node_tracklist,
                       node_track,
                       node_location,
                       node;
            int	       size;

            doc = xmlNewDoc(BAD_CAST "1.0"); 

            node_playlist = xmlNewNode (NULL, BAD_CAST XSPF_ROOT_NODE_NAME);
            xmlSetProp (node_playlist, BAD_CAST "version", BAD_CAST "1");
            xmlSetProp (node_playlist, BAD_CAST "xmlns", BAD_CAST XSPF_XMLNS);
            xmlDocSetRootElement (doc, node_playlist);

            ns_bmp  = xmlNewNs (node_playlist, BAD_CAST XML_NS_BMP, BAD_CAST "bmp");
            ns_xspf = xmlNewNs (node_playlist, BAD_CAST XSPF_XMLNS, BAD_CAST "xspf");

            node = xmlNewNode (ns_xspf, BAD_CAST "creator");
            xmlAddChild (node, xmlNewText(BAD_CAST "BMP 2.0"));
            xmlAddChild (node_playlist, node);

            node_tracklist = xmlNewNode (ns_xspf, BAD_CAST "trackList");
            xmlAddChild (node_playlist, node_tracklist);

            for (TrackV::const_iterator i = list.begin(); i != list.end(); ++i)
            {
              Track const& track (i.operator*());

              if (!track.location)
                continue;

              node_track = xmlNewNode (ns_xspf, BAD_CAST "track");
              node_location = xmlNewNode (ns_xspf, BAD_CAST "location");
              xmlAddChild (node_location, xmlNewText(BAD_CAST (track.location.get()).c_str()) );
              xmlAddChild (node_track, node_location); 
              xmlAddChild (node_tracklist, node_track);

              if (track.mb_track_id)
              {
                node = xmlNewNode (ns_xspf, BAD_CAST "identifier"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.mb_track_id.get().c_str())));
                xmlAddChild (node_track, node);

                node = xmlNewNode (ns_xspf, BAD_CAST "meta"); 
                ustring mb_rel ("http://musicbrainz.org/mm-2.1/track/");
                mb_rel.append (track.mb_track_id.get());
                xmlAddChild (node, xmlNewText(BAD_CAST (mb_rel.c_str())));
                xmlSetProp  (node, BAD_CAST "rel", BAD_CAST "http://musicbrainz.org/track");
                xmlAddChild (node_track, node);
              }
           
              if (track.artist) 
              {
                node = xmlNewNode (ns_xspf, BAD_CAST "creator"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.artist.get().c_str())));
                xmlAddChild (node_track, node);
              }

              if (track.album)
              {
                node = xmlNewNode (ns_xspf, BAD_CAST "album"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.album.get().c_str())));
                xmlAddChild (node_track, node);
              }

              if (track.title)
              {
                node = xmlNewNode (ns_xspf, BAD_CAST "title"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.title.get().c_str())));
                xmlAddChild (node_track, node);
              }

#ifdef HAVE_HAL

              xmlNodePtr node_extension;
              node_extension = xmlNewNode (ns_xspf, BAD_CAST "extension");
              xmlSetProp (node_extension, BAD_CAST "application", BAD_CAST "http://beep-media-player.org");

              if (track.volume_udi)
              {
                node = xmlNewNode (ns_bmp, BAD_CAST "volume-udi"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.volume_udi.get().c_str())));
                xmlAddChild (node_extension, node);
              }

              if (track.device_udi)
              {
                node = xmlNewNode (ns_bmp, BAD_CAST "device-udi"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.device_udi.get().c_str())));
                xmlAddChild (node_extension, node);
              }

              if (track.volume_relative_path)
              {
                node = xmlNewNode (ns_bmp, BAD_CAST "volume-relative-path"); 
                xmlAddChild (node, xmlNewText(BAD_CAST (track.volume_relative_path.get().c_str())));
                xmlAddChild (node_extension, node);
              }
              xmlAddChild (node_track, node_extension);

#endif //HAVE_HAL

            }

            xmlKeepBlanksDefault(0);
            xmlChar *data;
            xmlDocDumpFormatMemoryEnc (doc, &data, &size, "UTF-8", 1);
            handle.set_buffer((const unsigned char*)data, strlen((const char*)data)+1);
            xmlFreeDoc (doc);
            g_free (data);
          }

          virtual Bmp::VFS::ExportData const&
          get_export_data()
          {
            static Bmp::VFS::ExportData export_data ("XSPF Playlist", "xspf"); 
            return export_data;
          }
      };
  }
}
  
extern "C" Bmp::VFS::PluginContainerBase* plugin_create ()
{
  return new Bmp::VFS::PluginContainerXSPF;
}

extern "C" void plugin_delete (Bmp::VFS::PluginContainerXSPF* p)
{
  delete p;
}


