//  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 as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  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_UI_PART_LIBRARY_HH
#define BMP_UI_PART_LIBRARY_HH

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

#ifdef HAVE_TR1
#include<tr1/unordered_map>
#include<string>
using namespace std;
using namespace std::tr1;
#endif

#include <list>
#include <set>
#include <string>
#include <vector>
#include <queue>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <gdkmm/pixbuf.h>
#include <glibmm/ustring.h>
#include <gtkmm.h>
#include <libglademm/xml.h>
#include <libsexymm/icon-entry.h>

#include "mcs/mcs.h"
#include "bmp/types/types-basic.hh"
#include "bmp/types/types-library.hh"
#include "widgets/cell-renderer-cairo-surface.hh"

// BMP Audio
#include "audio/audio.hh"

// BMP Misc
#ifdef HAVE_HAL
#  include "hal.hh"
#endif //HAVE_HAL

#include "dialog-simple-progress.hh"
#include "util.hh"
#include "util-file.hh"

#include "playbacksource.hh"
#include "ui-part-base.hh"

using namespace Bmp::DB;

namespace Bmp
{              
  namespace UiPart
  {
    class Library;
  }

  typedef sigc::signal<void>  SVoidT;
  typedef std::map<UID, RowV::size_type> UidIndexMap;
  typedef UidIndexMap::value_type UidIndexPair;
  typedef UidIndexMap::iterator UidIndexMapIter;
#ifdef HAVE_TR1
  typedef std::tr1::unordered_map<std::string, Gtk::TreeIter> IndexMapT;
#else //!HAVE_TR1
  typedef std::map<std::string, Gtk::TreeIter> IndexMapT;
#endif //HAVE_TR1

  typedef IndexMapT::iterator IndexMapIter;
  typedef IndexMapT::value_type IndexMapPair;

  class Artist_V
    : public Gtk::TreeView
  {
    protected:

      virtual void on_row_activated (Gtk::TreeModel::Path const&, Gtk::TreeViewColumn*);

    private:

      enum NodeType
      {
        NODE_BRANCH,
        NODE_LEAF
      };

      class ArtistCR
        : public Gtk::TreeModel::ColumnRecord
      {
        public:

          Gtk::TreeModelColumn<Glib::ustring>   value;
          Gtk::TreeModelColumn<AlbumArtist>     artist;
          Gtk::TreeModelColumn<UID>             uid;
          Gtk::TreeModelColumn<NodeType>        type;

          ArtistCR ()
          {
            add (value);
            add (artist);
            add (uid);
            add (type);
          }
      };

      Glib::RefPtr<Gtk::TreeStore>  mStore;
      ArtistCR                      mStoreArtistCR;

      IndexMapT                     mIndexMap, mMBIDIterMap; 
      UidIterMap                    mUidIterMap;
      std::string                   mLastId;

      void  put_artist (Bmp::AlbumArtist const&);
      void  cell_data_func (Gtk::CellRenderer*, const Gtk::TreeIter&, int);
      int   default_sort_func (const Gtk::TreeIter&, const Gtk::TreeIter&);
      bool  searchEqualFunc (const Glib::RefPtr<Gtk::TreeModel>&, int, const Glib::ustring&, const Gtk::TreeModel::iterator&);

      void  on_selection_changed ();
      sigc::connection mConnChanged;

    public:

      typedef sigc::signal <void, UidSet const&, Glib::ustring const&>  SignalArtistSelected;
      typedef sigc::signal <void, guint64>                              SignalAllSelected;

      struct SignalsT
      {
        SignalArtistSelected  ArtistSelected;
        SignalAllSelected     AllSelected;
        SVoidT                Cleared;
        SVoidT                Activated;
      };

    private:

      SignalsT Signals;

    public:

      Artist_V (BaseObjectType*                        obj,
                Glib::RefPtr<Gnome::Glade::Xml> const& xml);
      virtual ~Artist_V () {}

      void  clear();
      void  display();
      void  go_to_mbid (std::string const&);

      SignalArtistSelected&
      signal_artist_selected ()
      {
        return Signals.ArtistSelected;
      }

      SignalAllSelected&
      signal_all_selected ()
      {
        return Signals.AllSelected;
      }

      SVoidT&
      signal_artist_cleared ()
      {
        return Signals.Cleared;
      }

      SVoidT&
      signal_activated ()
      {
        return Signals.Activated;
      }
  };

  //////////////////////////////////////////////////////////////////

  class Album_V
    : public Gtk::TreeView
  {
    private:
      Glib::RefPtr<Gnome::Glade::Xml> m_ref_xml;
      Glib::RefPtr <Gtk::UIManager>   m_ui_manager;
      Glib::RefPtr <Gtk::ActionGroup> m_actions;

      typedef sigc::signal <void, VUri const&> SignalEnqueueRequest;

    private:
      void  album_cell_data_func (Gtk::CellRenderer * basecell, Gtk::TreeIter const& iter);
      bool  slot_select (Glib::RefPtr <Gtk::TreeModel> const& model, Gtk::TreeModel::Path const& path, bool was_selected);
      void  insert (Album const& album);
      void  get_cover (UidList const& uid_list);

      class AlbumCR
        : public Gtk::TreeModel::ColumnRecord
      {
        public:

          Gtk::TreeModelColumn<RefSurface>      cover;
          Gtk::TreeModelColumn<Album>           album;
          Gtk::TreeModelColumn<UID>             uid;

          AlbumCR ()
          {
            add (cover);
            add (album);
            add (uid);
          }
      };

      bool
      searchEqualFunc (const Glib::RefPtr<Gtk::TreeModel>&, int, const Glib::ustring&, const Gtk::TreeModel::iterator&);

      RefSurface                      mCover;
      Glib::RefPtr<Gtk::ListStore>    mStore;
      AlbumCR                         mStoreAlbumCR;
      UidIterMap                      mUidAlbumMap;
      UidList                         mSelectedUids;
      std::string                     mLastId;

    protected:

      virtual void  on_row_activated (const Gtk::TreeModel::Path& /*path*/, Gtk::TreeViewColumn* /*column*/);
      virtual bool  on_event (GdkEvent * event);

    private:

      SVoidT                signal_changed_;
      SVoidT                signal_activated_;
      SignalEnqueueRequest  signal_enqueue_request_;

    public:

      SVoidT&
      signal_changed()
      {
        return signal_changed_;
      }

      SVoidT&
      signal_activated()
      {
        return signal_activated_;
      }

      SignalEnqueueRequest&
      signal_enqueue_request ()
      {
        return signal_enqueue_request_;
      }

    public:

      Album_V (BaseObjectType                       * obj,
               Glib::RefPtr<Gnome::Glade::Xml> const& xml);
      virtual ~Album_V () {}

      void
      set_ui_manager (Glib::RefPtr<Gtk::UIManager> const& ui_manager);

      // 'real' API
      UidList
      get_albums ();

      void
      clear ();

      void
      change ();

      // Handlers
      void  on_artist_selected (UidSet const& x, Glib::ustring const& name);
      void  on_artist_cleared ();
      void  on_artist_all_selected (guint64 n_artists);
      void  on_enqueue_album ();
  };

  //////////////////////////////////////////////////////////////////

  class Playlist_V
    : public Gtk::TreeView
  {
    private:

      Glib::RefPtr <Gdk::Pixbuf> m_pb_playing;
      Glib::RefPtr <Gnome::Glade::Xml> m_ref_xml;

      // History
      class History
      {
        public:

          History () : mPosition (mQueue.begin()) {}
          ~History () {}
    
          void 
          integrity ()
          {
            if(mPosition == mQueue.end())
            {
              clear ();
            }
          }

          bool
          boundary ()
          {
            return ((mPosition == mQueue.begin()) || (mPosition == (mQueue.end() - 1))); 
          }
    
          bool         
          have_prev ()
          {
            return ((mQueue.size() > 1) && (mPosition > mQueue.begin())); 
          }

          bool
          have_next ()
          {
            return ((mQueue.size() > 1) && (mPosition < (mQueue.end() - 1)));
          }

          UID
          get_prev ()
          {
            --mPosition;
            return UID (*mPosition);
          }

          UID
          get_next ()
          {
            ++mPosition;
            return UID (*mPosition);
          }

          void
          append (UID uid)
          {
            if (mQueue.size() == 1024)      
            {
              Position p (mQueue.begin());
              mQueue.erase (p);
            }
            mQueue.push_back (uid);
            mPosition = mQueue.end() - 1; 
          }

          void
          prepend (UID uid)
          {
            if (mQueue.size() == 1024)      
            {
              Position p (mQueue.end() - 1);
              mQueue.erase (p);
            }
            mQueue.push_front (uid);
            mPosition = mQueue.begin ();
          }

          void
          erase  (UID uid)
          {
            enum RelativePosition
            {
              POS_OTHER,
              POS_EQUAL,
              POS_PREV,
            };

            Position p = mQueue.begin();
            for ( ; p != mQueue.end() ; )
            {
              if (*p == uid)
              {
                RelativePosition pos = POS_OTHER;

                if( p == mPosition )
                  pos = POS_EQUAL;
                else if( p < mPosition )
                  pos = POS_PREV;

                p = mQueue.erase (p);

                if( pos == POS_EQUAL ) 
                  mPosition = mQueue.end();
                else if( pos == POS_PREV ) 
                  mPosition--;

              }
              else
                ++p;
            }
          } 

          bool
          has_uid (UID uid)
          {
            for (Position p = mQueue.begin() ; p != mQueue.end() ; ++p)
            {
              if (*p == uid)
                return true;
            }
            return false;
          }

          void
          clear ()
          {
            mQueue.clear ();
            mPosition = mQueue.begin();
          }

          void
          set (UID uid)
          {
            if (!mQueue.empty() && !boundary())
              mQueue.erase (mPosition+1, mQueue.end());
            append (uid);
          }

          void
          rewind ()
          {
            mPosition = mQueue.begin();
          }

          bool
          get (UID & uid)
          {
            if (mQueue.size())
            {
              uid = *mPosition;
              return true;
            }
            return false;
          }

          bool
          empty ()
          {
            return mQueue.empty();
          }

          void
          print (const char* func)
          {
            printf ("Func: %s\nSize: %llu\n", func, guint64 (mQueue.size()));
            for (Queue::const_iterator i = mQueue.begin(); i != mQueue.end(); ++i)
            {
              if (i == mPosition)
              {
                printf ("%llu\t*\n", *i);
              }
              else
              {
                printf ("%llu\t-\n", *i);
              }
            }
            printf ("\n");
          }

        private:

          typedef std::deque <UID>  Queue;
          typedef Queue::iterator   Position;

          Queue     mQueue; 
          Position  mPosition;
      };

      UidIterSetMap                   mUidIterMap;
      UidIterMap                      mLocalUidIterMap;
      UID                             mLocalUid;
      History                         mHistory;
      gint                            mIndex;

      // Main Datastore
      class ColumnsTrack
        : public Gtk::TreeModel::ColumnRecord
      {
        public:
        
          ColumnTrack   track;
          ColumnUID     uid;
          ColumnUID     localUid;
          ColumnString  searchKey;
          ColumnBool    playTrack;
          ColumnBool    present;

          ColumnsTrack ()
          {
            add (track);
            add (uid);
            add (localUid);
            add (searchKey);
            add (playTrack);
            add (present);
          }
      };

      ColumnsTrack
      m_track_cr;

      Glib::RefPtr <Gtk::ListStore>
      mStore;

      boost::optional <Gtk::TreeIter>
      m_current_iter;

      guint64
      m_bmpx_track_id;

      void  put_track_at_iter (Track const& track, Gtk::TreeIter & iter);
      void  put_track_at_iter (Track const& track); // this will append the track
      bool  display_next_row ();

      void  cell_data_func (Gtk::CellRenderer * basecell, Gtk::TreeIter const& iter, int column, int renderer);
      void  cell_play_track_toggled (Glib::ustring const& path);
      bool  slot_select (Glib::RefPtr <Gtk::TreeModel> const& model, Gtk::TreePath const& path, bool was_selected);

      void  assign_current_iter (Gtk::TreeIter const& iter);
      void  clear_current_iter ();

      void  on_tracks_retag ();
      void  on_playlist_export ();
      void  on_delete_files ();
      void  on_enqueue_files ();
      void  on_track_info ();

      Glib::RefPtr <Gtk::UIManager>   m_ui_manager;
      Glib::RefPtr <Gtk::ActionGroup> m_actions;

#ifdef HAVE_HAL
      void  hal_volume_del (HAL::Volume const& volume);
      void  hal_volume_add (HAL::Volume const& volume);
      void  unselect_missing ();
#endif //HAVE_HAL

      void  on_library_track_modified (Track const& track);

    protected:

      virtual bool on_event (GdkEvent * event);
      virtual bool on_motion_notify_event (GdkEventMotion * event);
      virtual void on_row_activated (Gtk::TreeModel::Path const&, Gtk::TreeViewColumn*);

    public:

      Glib::ustring get_uri ();
      Track get_track ();

      bool  notify_next ();
      bool  notify_prev ();
      bool  notify_play ();
      void  notify_stop ();
      bool  check_play ();

      void  has_next_prev (bool & next, bool & prev);
      bool  has_playing ();
      void  set_first_iter ();

      void  append_album (guint64 bmpx_album_id);
      void  clear ();

      bool  m_playing;

      typedef sigc::signal <void> SignalRecheckCaps;
      typedef sigc::signal <void> SignalUpdated;
      typedef sigc::signal <void> Signal;
      typedef sigc::signal <void, VUri const&> SignalEnqueueRequest;
      
    private:

      Signal                signal_activated_;
      SignalRecheckCaps     signal_recheck_caps_;
      SignalUpdated         signal_updated_;
      SignalEnqueueRequest  signal_enqueue_request_;

    public:

      Signal&
      signal_activated()
      {
        return signal_activated_;
      }

      SignalRecheckCaps&
      signal_recheck_caps ()
      {
        return signal_recheck_caps_;
      }

      SignalUpdated&
      signal_updated ()
      {
        return signal_updated_;
      }

      SignalEnqueueRequest&
      signal_enqueue_request ()
      {
        return signal_enqueue_request_;
      }

      Playlist_V (BaseObjectType                       * obj,
                  Glib::RefPtr<Gnome::Glade::Xml> const& xml);
      virtual ~Playlist_V ();

      void
      set_ui_manager (Glib::RefPtr<Gtk::UIManager> const& ui_manager);
  };

  //////////////////////////////////////////////////////////////////

  namespace UiPart
  {
    class Library
      : public  PlaybackSource,
        public  Base
    {
      public:

        Library (Glib::RefPtr<Gnome::Glade::Xml> const& xml, Glib::RefPtr<Gtk::UIManager> ui_manager);
        virtual ~Library ();

        void  update ();
        void  go_to_mbid (std::string const&);

      private:

        Artist_V          * m_view_artists;
        Album_V           * m_view_albums;
        Playlist_V        * m_view_playlist;
        Gtk::Notebook     * m_library_notebook;

        void  on_playlist_selection_changed ();
        void  on_playlist_activated ();
        void  on_playlist_rows_reordered ();

        void  on_albums_activated ();
        void  on_albums_changed ();
        void  on_artists_activated ();

        void  query_playlist_caps ();
        void  send_metadata ();

        bool  m_playing;
        bool  m_queue_playback;
        bool  m_queue_block_changes;

        void  on_shuffle_repeat_toggled (MCS_CB_DEFAULT_SIGNATURE);
        void  on_library_backend_modified ();

        Glib::RefPtr <Gtk::ActionGroup> m_actions;
    
      protected:

        virtual guint
        add_ui ();

        virtual Glib::ustring
        get_uri ();

        virtual bool
        go_next ();

        virtual bool
        go_prev ();
    
        virtual void
        next_post ();
    
        virtual void
        prev_post ();

        virtual void
        stop ();

        virtual bool
        play ();

        virtual void
        play_post ();

        virtual void
        restore_context () {}

        virtual void
        send_caps ();

        virtual GHashTable*
        get_metadata ();
    };
  }
}
#endif //!BMP_UI_PART_LIBRARY_HH
