//  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_LIBRARY_CLASS_HH
#define BMP_LIBRARY_CLASS_HH

#include <map>
#include <vector>
#include <string>

#include <glib/gtypes.h>
#include <glibmm/ustring.h>
#include <sigc++/signal.h>

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

#ifndef BMP_PLUGIN_BUILD
#  include <taglib/tag.h>
#endif //!BMP_PLUGIN_BUILD

#include "bmp/types/types-basic.hh"
#include "bmp/types/types-library.hh"
#include "bmp/library-ops.hh"

#include "database.hh"
#include "main.hh"

namespace TagLib
{
  class File;
}

namespace Bmp
{
  // Database flags
  enum DBFlags
  {
    DB_FLAG_NONE        = 0,
    DB_FLAG_USING_HAL   = 1 << 0,
  };

  enum AddOp
  {
    LIBRARY_ADD_ERROR     =  -1,
    LIBRARY_ADD_NOOP      =   0,
    LIBRARY_ADD_IMPORT    =   1,
    LIBRARY_ADD_UPDATE    =   2,
    LIBRARY_ADD_NOCHANGE  =   3,
  };

  enum TrackModFlags
  {
    TRACK_MOD_FLAG_NONE = 0, // unused
    TRACK_MOD_RETAG     = 1 << 0,
    TRACK_MOD_UPDATE_DB = 1 << 1,
  };

#include "exception.hh"
  EXCEPTION(MetadataWriteError)
  EXCEPTION(MetadataReadError)
  EXCEPTION(NoMetadataTaglibError)
  EXCEPTION(NoMetadataGstError)
#ifdef HAVE_HAL
  EXCEPTION(NoHALInformationError)
#endif //HAVE_HAL

  typedef std::map<guint64, Track> TrackMap;

  class Library
  {
      public:

        struct TaglibPlugin
        {
          bool          (*set)       (std::string const& filename, Track const& track);
          bool          (*get)       (std::string const& filename, DB::Row& row);
          const char ** (*mimetypes) ();
        };

        typedef boost::shared_ptr<TaglibPlugin>        TaglibPluginPtr;
        typedef std::vector<TaglibPluginPtr>           TaglibPluginsKeeper;
        typedef std::map<std::string, TaglibPluginPtr> TaglibPluginsMap;

      private:

        TaglibPluginsMap    m_taglib_plugins;
        TaglibPluginsKeeper m_taglib_plugins_keeper;

        bool load_taglib_plugin (std::string const& path);

      public:

        TaglibPluginsMap const&
        get_plugins ()
        {
          return m_taglib_plugins;
        }

        typedef sigc::signal <void, int>          SignalModifyStart;
        typedef sigc::signal <void>               SignalModifyStep;
        typedef sigc::signal <void>               SignalModifyEnd;
        typedef sigc::signal <void>               SignalModified;
        typedef sigc::signal <void, int>          SignalVacuumBegin;
        typedef sigc::signal <void>               SignalVacuumStep;
        typedef sigc::signal <void>               SignalVacuumEnd;
        typedef sigc::signal <void, Track const&> SignalTrackModified;


        SignalModifyStart &
        signal_modify_start ();

        SignalModifyStep &
        signal_modify_step ();

        SignalModifyEnd &
        signal_modify_end ();

        SignalModified &
        signal_modified ();

        SignalTrackModified &
        signal_track_modified ();

        SignalVacuumBegin &
        signal_vacuum_begin ();

        SignalVacuumStep &
        signal_vacuum_step ();

        SignalVacuumEnd &
        signal_vacuum_end ();

        Library ();
        ~Library ();

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

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

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

          return p;
        }

        bool    open ();
        void    init ();
        void    recreate_db ();
        DBFlags get_db_flags ();

        void  begin_txn ();
        void  close_txn ();
        void  cancel_txn ();

        void  vacuum_tables();
        void  vacuum_nonexistent();

        void  remove( Track const& track );
        void  remove( guint64 bmpx_track_id );

        void  modify( TrackV & tracks, TrackModFlags flags, bool display_progress = true );
        void  query( DB::Query const& query, DB::RowV & rows, bool reopen_db = false ) const;

        void  get (DB::RowV & rows, std::string const& sql, bool newConn = false);

        AddOp insert( std::string const& location, std::string const& insert_path );

#ifdef HAVE_HAL
        bool  volume_exists       (std::string const& volume_udi,
                                   std::string const& device_udi) const;

        void  volume_insert_paths (std::string const& volume_udi,
                                   std::string const& device_udi,
                                   StrV             & insert_paths) const;

        void  remove_volume       (std::string const& volume_udi,
                                   std::string const& device_udi);

        void  remove_path         (std::string const& volume_udi,
                                   std::string const& device_udi,
                                   std::string const& insert_path);

        void  vacuum_nonexistent
                                  (std::string const& volume_udi,
                                   std::string const& device_udi,
                                   std::string const& insert_path = std::string());
#endif //HAVE_HAL

        void
        volume_insert_paths (StrV & insert_paths) const;

        void
        metadata_set_taglib (Track & track);

        void
        metadata_get_taglib (std::string const& location, DB::Row & row, std::string const& type) const;

        void
        metadata_get_gst    (std::string const& location, DB::Row & row) const;

        void
        get_metadata        (std::string const& location, DB::Row & row, bool reopen_db = false) const;

        GHashTable*
        get_metadata_hash_table_for_uri (std::string const& location) const;
                            
        /////////////

        /* The following four functions are not const because the create an ID if it doesn't exist yet */
        guint64   get_track_artist_id   (DB::Row const& row,
                                         bool           only_if_exists = false);

        guint64   get_album_artist_id   (DB::Row const& row,
                                         bool           only_if_exists = false);

        guint64   get_album_id          (DB::Row const& row,
                                         guint64        artist_id,
                                         bool           only_if_exists = false);

        guint64   get_tag_id            (std::string  const&  attr_id,
                                         std::string  const&  tag,
                                         bool                 only_if_exists = false);

        Track     get_track             (std::string const& uri, bool reopen_db = false) const;
        Track     get_track             (guint64 track_id, bool reopen_db = false); // non const b/c of the cache
        guint64   get_track_mtime       (Track const& track) const;
        guint64   get_track_id          (Track const& track) const;

        DB::RowV  get_artist            (guint64 artist_id) const;
        DB::RowV  get_artists           () const;

        DB::RowV  get_album             (guint64 album_id) const;
        DB::RowV  get_albums            () const;
        DB::RowV  get_albums_by_artist  (guint64 artist_id) const;
        DB::RowV  get_album_tracks      (guint64 album_id) const;

        StrV      get_asins             () const;

        void  tag_track (guint64 bmpx_track_id, StrV const& tags, bool refresh = false);
        void  tag_artist (guint64 bmpx_artist_id, StrV const& tags, bool refresh = false);
        void  track_set_rating (guint64 bmpx_track_id, int rating);
        void  track_set_active (guint64 bmpx_track_id, bool active);

    private:

        SignalTrackModified
        signal_track_modified_;

        SignalModifyStart
        signal_modify_start_;
        SignalModifyStep
        signal_modify_step_;
        SignalModifyEnd
        signal_modify_end_;

        SignalModified
        signal_modified_;

        SignalVacuumBegin
        signal_vacuum_begin_;
        SignalVacuumStep
        signal_vacuum_step_;
        SignalVacuumEnd
        signal_vacuum_end_;

        DB::Database *  m_db;
        int             m_db_flags;

        UidSet        m_track_cache_tags;
        TrackMap      m_track_cache;

        void  mark_dirty (guint64 id);
    };
} // namespace Bmp

#endif // !BMP_LIBRARY_CLASS_HH
