//  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_PLAYLIST_HH
#define BMP_UI_PART_PLAYLIST_HH

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

#ifdef HAVE_TR1
#include<tr1/unordered_map>
#include<string>
#include<iostream>
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

#ifdef HAVE_LINUX
#  include "glib-inotify.hh"
#endif //HAVE_LINUX

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

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

using namespace Bmp;
using namespace DB;
using namespace Gtk;

namespace Bmp
{              
  typedef sigc::signal<void>  SVoidT;
  typedef std::map<UID, RowV::size_type> UidIndexMap;
  typedef UidIndexMap::value_type UidIndexPair;
  typedef UidIndexMap::iterator UidIndexMapIter;

  class PlaylistList
    : public Gtk::TreeView
  {
    private:

      Glib::RefPtr <Gdk::Pixbuf> m_pb_playing;
      Glib::RefPtr <Gnome::Glade::Xml> m_ref_xml;
      Glib::RefPtr <Gtk::UIManager> m_ui_manager;
      Glib::RefPtr <Gtk::ActionGroup> m_actions;

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

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

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

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

      guint64
      mTrackId;

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

      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  move_selected_rows_up ();
      void  move_selected_rows_down ();

      TreePath  mPathButtonPress;
      bool      mButtonDepressed;

      void  on_playlist_export ();
      void  on_playlist_create_from_lastfm_tag ();
      void  on_playlist_remove_items ();

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

#ifdef HAVE_LINUX
      void  on_playlist_watch_path_toggled();
      void  on_playlist_watch_cancel();
      void  on_new_inotify_events();
      Glib::RefPtr<Bmp::INotify> m_inotify;
      std::string m_watch_path;
#endif //HAVE_LINUX

    protected:

      virtual bool on_event (GdkEvent * event);
      virtual bool on_motion_notify_event (GdkEventMotion * event);

      virtual bool on_drag_motion (Glib::RefPtr<Gdk::DragContext> const& context, int x, int y, guint time);
      virtual bool on_drag_drop (Glib::RefPtr<Gdk::DragContext> const& context, int x, int y, guint time);
      virtual void on_drag_data_received (Glib::RefPtr<Gdk::DragContext> const& context, int x, int y,
                                            Gtk::SelectionData const& selection_data, guint info, guint time);
      virtual void on_drag_leave (Glib::RefPtr<Gdk::DragContext> const& context, guint time);
      virtual void on_row_activated (Gtk::TreeModel::Path const&, Gtk::TreeViewColumn*);

    public:

      Glib::ustring get_uri ();
      Glib::ustring get_type ();

      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  enable_drag_dest ();
      void  disable_drag_dest ();

      bool  mPlaying;

      void  clear ();

      typedef sigc::signal <void> SignalRecheckCaps;
      typedef sigc::signal <void> Signal;
      
    private:

      struct Signals
      {
        Signal            Activated;
        SignalRecheckCaps RecheckCaps;
      };

      Signals mSignals;

    public:

      Signal&
      signal_activated()
      {
        return mSignals.Activated;
      }

      SignalRecheckCaps&
      signal_recheck_caps ()
      {
        return mSignals.RecheckCaps;
      }

      PlaylistList (BaseObjectType                       * obj,
                    Glib::RefPtr<Gnome::Glade::Xml> const& xml);
      virtual ~PlaylistList ();
      void  set_ui_manager (Glib::RefPtr<Gtk::UIManager> const& ui_manager);
      void  add_uris (VUri const& uris, bool & playback);
      void  bad_track ();
  };

  namespace UiPart
  {
    class Playlist
      : public  PlaybackSource
      , public  Base
      , public  UriHandler
    {
        PlaylistList * m_playlist;

      public:

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

        void  add_uris (VUri const& uris, bool playback);

      private:

        void  on_playlist_selection_changed ();
        void  on_playlist_activated ();
        void  on_shuffle_repeat_toggled (MCS_CB_DEFAULT_SIGNATURE);

        void  query_playlist_caps ();
        void  send_metadata ();

        bool  mPlaying;

      protected:

        // UriHandler
        virtual StrV  
        get_schemes ();

        virtual void
        process (VUri const&);

        // PlaybackSource 
        virtual guint
        add_ui ();

        virtual Glib::ustring
        get_uri ();

        virtual Glib::ustring
        get_type ();

        virtual bool
        go_next ();

        virtual bool
        go_prev ();

        virtual void
        stop ();

        virtual bool
        play ();

        virtual void
        play_post ();

        virtual void
        next_post ();

        virtual void
        prev_post ();

        virtual void
        restore_context () {}

        virtual GHashTable*
        get_metadata ();

        virtual void
        bad_track ();
    };
  }
}
#endif //!BMP_UI_PART_PLAYLIST_HH
