//  BMP
//  Copyright (C) 2005-2007 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.

#ifndef BMP_LAST_FM_HH
#define BMP_LAST_FM_HH

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

#include "bmp/types/types-basic.hh"
#include "bmp/types/types-library.hh"
#include "bmp/types/types-xspf.hh"
#include "bmp/types/types-lastfm.hh"

#include <exception>
#include <vector>
#include <queue>
#include <string>

#include <glibmm/markup.h>
#include <glibmm/ustring.h>
#include <gtkmm.h>
#include <libglademm.h>
#include <boost/variant.hpp>

#include "mcs/mcs.h"
#include "xts.hh"

#include "main.hh"
#include "minisoup.hh"

using namespace Glib;

namespace Bmp
{
  namespace LastFM
  {

    typedef sigc::signal <void> Signal;

#include "exception.hh"

    EXCEPTION(LastFMStreamTuningError)
    EXCEPTION(LastFMNotConnectedError)

    namespace XMLRPC
    {
      /////////////////////////////////////////////////
      ///// Requests
      /////////////////////////////////////////////////

      ustring formatXmlRpc (ustring const& method, xmlRpcVariantV const& argv);

      class XmlRpcCall; 
      typedef Glib::RefPtr<XmlRpcCall> XmlRpcCallRefPtr;

      class XmlRpcCall
        : public Glib::Object
      {
        public:
          typedef sigc::signal<void, std::string const&, guint> SignalReply;
        public:
          SignalReply & reply() { return s_; }
        protected:
          xmlRpcVariantV m_v;
          Bmp::Soup::RequestRefP m_soup_request;
          SignalReply s_;
          XmlRpcCall ();
        public:
          virtual ~XmlRpcCall ();
          void cancel ();
          void setMethod (const ustring& method);
      };

      class XmlRpcCallSync
        : public Glib::Object
      {
        protected:
          xmlRpcVariantV m_v;
          Bmp::Soup::RequestSyncRefP m_soup_request;
          XmlRpcCallSync ();
        public:
          virtual ~XmlRpcCallSync ();
          void setMethod (const ustring& method);
      };

      class ArtistMetadataRequest;
      typedef Glib::RefPtr<ArtistMetadataRequest> ArtistMetadataRequestRefPtr;

      class ArtistMetadataRequest
        : public XmlRpcCall
      {
          ustring m_artist;
          void reply_cb (char const* data, guint size, guint status_code);
        private: 
          ArtistMetadataRequest (ustring const& artist); 
        public:
          static ArtistMetadataRequestRefPtr create (ustring const& artist); 
          virtual ~ArtistMetadataRequest () {};
          void run ();
      };

      class RequestBase
        : public XmlRpcCallSync
      {
        public:
  
          RequestBase (ustring const& method);
          virtual ~RequestBase () {};

          void run ();

        protected:

          std::string   m_name;
          std::string   m_pass;
          std::string   m_user;
          std::string   m_time;
          std::string   m_key;
          std::string   m_kmd5;
      };

      class TrackAction
        : public RequestBase
      {
        public:

          TrackAction (ustring const& method, XSPF::Item const& item);
          virtual ~TrackAction () {}
      };

      class TagAction 
        : public RequestBase
      {
        public:

          TagAction (ustring const& method, Bmp::XSPF::Item const& item, Bmp::UStrV const& tags);
          virtual ~TagAction () {}
      };

      class RecommendAction
        : public RequestBase
      {
        public:

          enum RecommendItem
          {
            RECOMMEND_TRACK,
            RECOMMEND_ARTIST,
            RECOMMEND_ALBUM,
          };

          RecommendAction (RecommendItem item,
                      ustring const& artist, ustring const& album, ustring const& title, ustring const& user, ustring const& message); 
          virtual ~RecommendAction () {}
      };
    } // namespace:XMLRPC

    namespace WS
    {
      class TagsGlobReq;
      typedef Glib::RefPtr<TagsGlobReq> TagsGlobReqRefP;

      class TagsGlobReq
        : public Glib::Object
      {
          TagsGlobReq (TagGlobalType type, std::string const& id1, std::string const& id2 = std::string() );
          void reply_cb (char const* data, guint size, guint status_code);
        public:
          ~TagsGlobReq ();
          static TagsGlobReqRefP create (TagGlobalType type, std::string const& id1, std::string const& id2 = std::string() );
          void run ();
          void cancel ();
        private:
          Soup::RequestRefP m_soup_request;
          TagV m_tags;
        public:
          typedef sigc::signal<void, TagV const&> SignalTags;
        private:
          SignalTags s_tags_;
        public:
          SignalTags & tags () { return s_tags_; }
      };

      class TagsUserReq;
      typedef Glib::RefPtr<TagsUserReq> TagsUserReqRefP;

      class TagsUserReq
        : public Glib::Object
      {
          TagsUserReq (TagUserType type, std::string const& id1, std::string const& id2 = std::string() );
          void reply_cb (char const* data, guint size, guint status_code);
        public:
          ~TagsUserReq ();
          static TagsUserReqRefP create (TagUserType type, std::string const& id1, std::string const& id2 = std::string() );
          void run ();
        private:
          Soup::RequestRefP m_soup_request;
          TagV m_tags;
        public:
          typedef sigc::signal<void, TagV const&> SignalTags;
        private:
          SignalTags s_tags_;
        public:
          SignalTags & tags () { return s_tags_; }
      };


      // md XXX: Sync API (try to deprecate it)

      void tags_glob (TagGlobalType type,
                      TagV & tags,
                      std::string const& id1,
                      std::string const& id2 = std::string());

      void tags_user (TagUserType type,
                      TagV & tags,
                      std::string const& id1 = std::string(),
                      std::string const& id2 = std::string());

      void artists (ArtistType type, std::string const& id, LastFMArtistV & x);
      void users (UserType type, std::string const& username, UserV & x);
      void matchtags (std::string const& username, RankedTagV & ranked);
      std::string avatar_url (std::string const& username);
                              
    } // end namespace WS
  } // end namespace LastFM
} // end namespace Bmp

namespace Bmp
{
  namespace LastFM
  {
    enum LFMErrorCode
    {
        LFM_ERROR_NOT_ENOUGH_CONTENT = 1, // There is not enough content to play this station.
        LFM_ERROR_FEWGROUPMEMBERS,        // This group does not have enough members for radio.
        LFM_ERROR_FEWFANS,                // This artist does not have enough fans for radio.
        LFM_ERROR_UNAVAILABLE,            // This item is not available for streaming.
        LFM_ERROR_SUBSCRIBE,              // This feature is only available to subscribers.
        LFM_ERROR_FEWNEIGHBOURS,          // There are not enough neighbours for this radio.
        LFM_ERROR_OFFLINE                 // The streaming system is offline for maintenance, please try again later
    };

    /** Client code for Last.fm Radio
     *
     **/
    class Radio
    {
      public:

        struct Session
        {
          std::string session;
          std::string base_url;
          std::string base_path;
          bool        subscriber;

          Session ()
          : subscriber (0)
          {}
        };

      public:

        typedef sigc::signal<void, XSPF::Playlist const&> SignalPlaylist;
        typedef sigc::signal<void, Glib::ustring const&>  SignalTuneError; 

        Radio ();
        ~Radio ();

        static Radio* Obj (ObjCreateFlag F = OBJ_FLAG_NONE)
        {
          static Radio* p = 0;

          if( F == OBJ_FLAG_DELETE ) 
          {
            delete p;
            p = 0;
          }

          if( F == OBJ_FLAG_CREATE ) 
          {
            p = new Radio();
          }

          return p;
        }

        Session const& session () const;

        bool  connected () const;
        void  handshake ();
        void  disconnect ();

        void  get_xspf_playlist ();
        void  get_xspf_playlist_cancel ();

        void  playurl (std::string const& url);
        bool  is_playlist (std::string const& url);
 
      private:

        struct SignalsT
        {
          Signal          Disconnected;
          Signal          Connected;
          Signal          NoPlaylist;
          SignalPlaylist  Playlist;
          SignalTuneError TuneError;
          Signal          Tuned;
        };

        SignalsT Signals;
    
      public:

        Signal&
        signal_tuned();

        SignalTuneError&
        signal_tune_error();

        Signal&
        signal_connected();

        Signal&
        signal_disconnected();

        SignalPlaylist&
        signal_playlist ();

        Signal&
        signal_no_playlist ();

      private:

        void  handshake_cb (char const * data, guint size, guint code);
        void  playlist_cb (char const * data, guint size, guint code);
        void  playurl_cb (char const * data, guint size, guint code, bool); 

        void  parse_playlist (char const * data, guint size);

        Soup::RequestRefP m_handshake_request;
        Soup::RequestRefP m_playlist_request;
        Soup::RequestRefP m_playurl_request;

        Session           m_session;
        bool              m_connected;
    };
  }
}

namespace Bmp
{
  namespace LastFM
  {
    /** Client code for Scrobbler
     *
     * Bmp::LastFM::Scrobbler is a client implementation of Audioscrobbler Protocol 1.2,
     * (http://www.audioscrobbler.com), used by Last.FM (http://last.fm), a public,
     * free service that tracks your listening habits and provides you with statistics
     * and friends.
     *
     **/
    class Scrobbler
    {
      private:

        struct SignalsT
        {
          Signal Disconnected;
          Signal Connected;
        };

        SignalsT Signals;
    
      public:

        Signal&
        signal_disconnected()
        {
          return Signals.Disconnected;
        }

        Signal&
        signal_connected()
        {
          return Signals.Connected;
        }

      public:

        Scrobbler ();
        ~Scrobbler ();

        static Scrobbler* Obj (ObjCreateFlag F = OBJ_FLAG_NONE)
        {
          static Scrobbler* p = 0;

          if( F == OBJ_FLAG_DELETE ) 
          {
            delete p;
            p = 0;
          }

          if( F == OBJ_FLAG_CREATE ) 
          {
            p = new Scrobbler();
          }

          return p;
        }

        bool  connected () const;
        void  handshake ();
        void  disconnect ();

        bool  scrobble_idle (XSPF::Item const& item);
        bool  scrobble_idle_tqi (TrackQueueItem const& item);

        void  scrobble (XSPF::Item const& item);
        void  scrobble (TrackQueueItem const& item);

        void  now_playing (TrackQueueItem const& item);

      private:

        void mcs_queue_enable (MCS_CB_DEFAULT_SIGNATURE);

        enum LastFmHandshakeState
        {
          AS_HANDSHAKE_STATE_UNDEFINED  = 0,
          AS_HANDSHAKE_BANNED           = 1,
          AS_HANDSHAKE_BADAUTH          = 2,
          AS_HANDSHAKE_BADTIME          = 3,
          AS_HANDSHAKE_FAILED           = 4,
          AS_HANDSHAKE_STATE_OK         = 5,
        };

        struct HandshakeData
        {
          /** Session ID 
           */
          std::string session;

          /** URI for track submissions
           */
          std::string uri;

          /** Now-Playing URI 
           */
          std::string np;

          /** Used for FAILED reason and UPDATE url
           */
          std::string info;

          /** Password as calculated during handshake
           */
          std::string pwd;

          /** The handshake state 
           */
          int state;

          HandshakeData () : state (AS_HANDSHAKE_STATE_UNDEFINED) {}
        };

        bool  now_playing_idle (TrackQueueItem const& item);
        void  now_playing_cb (char const* /*data*/, guint /*size*/, guint /*code*/);

        void  scrobble_cb (char const* /*data*/, guint /*size*/, guint /*code*/, LQM::size_type /*n_items*/);
        void  handshake_cb (char const* /*data*/, guint /*size*/, guint /*code*/);

        void  load_lqm (std::string const& filename);
        void  save_lqm (std::string const& filename);

        void  scrobble_request_cancel ();
        void  scrobble_request_run ();

        Soup::RequestRefP m_handshake_request;
        Soup::RequestRefP m_scrobble_request;
        Soup::RequestRefP m_now_playing_request;

        HandshakeData   m_handshake_data;

        LQM             m_queue;
        std::string     m_queue_filename;
        Mutex           m_queue_lock;
    };
  } // namespace LastFM
} // namespace Bmp

#endif // !BMP_LAST_FM_HH

