//  BMP
//  Copyright (C) 2003-2007 BMP <http://bmpx.backtrace.info> 
//
//  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 <gtkmm.h>
#include <glibmm/i18n.h>
#include <cstdlib>
#include <cstring>
#include <set>

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

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

// BMP Musicbrainz
#include "musicbrainz/mbxml-v2.hh"
#include "mb-tagger.hh"
#include "ld.hh"

#include "amazon.hh"
#include "debug.hh"
#include "main.hh"
#include "paths.hh"
#include "stock.hh"
#include "util-file.hh"
#include "util.hh"
#include "ui-tools.hh"

using namespace Glib;
using namespace Gtk;
using namespace std;
using namespace Bmp::MusicBrainzXml;

#define ACTION_SEARCH                 "action-update"
#define ACTION_MOVE_UP                "action-move-up"
#define ACTION_MOVE_DOWN              "action-move-down"
#define ACTION_REMOVE                 "action-remove"
#define ACTION_RELAYOUT_NONE          "action-relayout-none"
#define ACTION_RELAYOUT_TRACKNUMBERS  "action-relayout-tracknumber"
#define ACTION_RELAYOUT_MATCH         "action-relayout-levenshtein"
#define ACTION_USE_FILENAMES          "action-use-filenames"
#define ACTION_RESTORE                "action-restore"
#define ACTION_SCAN_RELEASE           "action-scan-release"

namespace
{
  void
  cbe_set_sensitive (CheckButton *cb, ComboBoxEntry *cbe)
  {
      cbe->set_sensitive (cb->get_active());
  }

  void
  fcb_set_sensitive (CheckButton *cb, FileChooserButton *fcb)
  {
      fcb->set_sensitive (cb->get_active());
  }

  void
  set_cbox_sensitive (CheckButton *cb, ComboBoxEntry *cb_entry)
  {
    cb_entry->set_sensitive (cb->get_active());
  }

  const char *modify_menu =
  "<ui>"
  "   <menubar name='popup-modify'>"
  "   <menu action='dummy' name='menu-modify'>"
  "     <menuitem action='" ACTION_SCAN_RELEASE "'/>"
  "     <separator name='sep1'/>"
  "     <menuitem action='" ACTION_REMOVE "'/>"
  "     <separator name='sep2'/>"
  "     <menuitem action='" ACTION_RELAYOUT_NONE "'/>"
  "     <menuitem action='" ACTION_RELAYOUT_TRACKNUMBERS "'/>"
  "     <menuitem action='" ACTION_RELAYOUT_MATCH "'/>"
  "     <separator name='sep3'/>"
  "     <menuitem action='" ACTION_USE_FILENAMES "'/>"
  "     <menuitem action='" ACTION_RESTORE "'/>"
  "   </menu>"
  "   </menubar>"
  "</ui>";

  static boost::format guint64_f ("%lld");
}

using namespace std;
using namespace Gdk;
using namespace Gtk;
using namespace Glib;
using namespace Bmp::DB;
using namespace Bmp::MusicBrainzXml;

namespace Bmp
{
    MusicBrainzTagger::MusicBrainzTagger (BaseObjectType                 * obj,
                                          RefPtr<Gnome::Glade::Xml> const& xml)

    : Dialog              (obj)
    , m_ref_xml           (xml)
    , m_current_ordering  (ORDERING_AUTO)
    , m_processed         (0)
    {
        set_title (_("Edit Metadata - BMP"));

        m_defaultcover = Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR, BMP_COVER_IMAGE_DEFAULT))->scale_simple (128, 128, Gdk::INTERP_BILINEAR);

        m_ref_xml->get_widget ("button-search", b_search);
        m_ref_xml->get_widget ("button-up", b_up);
        m_ref_xml->get_widget ("button-down", b_down);

        m_ref_xml->get_widget ("combo-box-artist", m_cb_artist);
        m_ref_xml->get_widget ("combo-box-album", m_cb_album);
        m_ref_xml->get_widget ("combo-box-genre", m_cb_genre);
        m_ref_xml->get_widget ("combo-box-release-date", m_cb_release_date);

        m_cb_artist_store = ListStore::create (mCrArtists);
        m_cb_album_store = ListStore::create (mCrAlbums);

        m_cb_release_event_store = ListStore::create (mCrEvents);
        CellRendererText * cell = manage (new CellRendererText());
        m_cb_release_date->pack_start (*cell, false);
        m_cb_release_date->add_attribute (*cell, "text", mCrEvents.event_string);

        m_cb_artist->set_model (m_cb_artist_store);
        m_cb_album->set_model (m_cb_album_store);
        m_cb_release_date->set_model (m_cb_release_event_store);

        m_cb_artist->signal_changed().connect
          (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_artist_changed));
        m_cb_artist->set_text_column (0);

        m_cb_album->signal_changed().connect
          (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_album_changed));
        m_cb_album->set_text_column (0);

        m_ref_xml->get_widget ("entry-mb-album-id", m_entry_album_id);

        m_ref_xml->get_widget ("image-coverart", m_image);
        m_image->set (m_defaultcover);

        m_ref_xml->get_widget ("check-button-modify-genre", m_cb_modify_genre);
        m_cb_genre_entry = m_cb_genre->get_entry ();

        m_cb_modify_genre->signal_toggled().connect
          (sigc::bind (sigc::ptr_fun (&set_cbox_sensitive), m_cb_modify_genre, m_cb_genre));

        m_ref_xml->get_widget ("release-view", m_view_release);
        m_ref_xml->get_widget ("local-view", m_view_local);

        m_ui_manager = UIManager::create ();
        m_actions = ActionGroup::create ("Actions_LibraryModify");

        m_actions->add (Action::create ("dummy", "dummy"));
        m_actions->add (Action::create (ACTION_SEARCH,
                                        Stock::NETWORK,
                                        _("Search")),
                                        sigc::mem_fun (*this, &MusicBrainzTagger::on_action_search));

        m_actions->add (Action::create (ACTION_MOVE_UP,
                                        Stock::GO_UP,
                                        _("Move Up")),
                                        sigc::mem_fun (*this, &MusicBrainzTagger::on_action_up));

        m_actions->add (Action::create (ACTION_MOVE_DOWN,
                                        Stock::GO_DOWN,
                                        _("Move Down")),
                                        sigc::mem_fun (*this, &MusicBrainzTagger::on_action_down));

        m_actions->get_action (ACTION_SEARCH)->connect_proxy (*b_search);
        m_actions->get_action (ACTION_MOVE_UP)->connect_proxy (*b_up);
        m_actions->get_action (ACTION_MOVE_DOWN)->connect_proxy (*b_down);
        m_actions->get_action (ACTION_MOVE_UP)->set_sensitive (0);
        m_actions->get_action (ACTION_MOVE_DOWN)->set_sensitive (0);

        m_actions->add (Action::create (ACTION_REMOVE,
                                             StockID (BMP_STOCK_DELETE),
                                             _("Remove Track From List")),
                                             sigc::mem_fun (*this, &MusicBrainzTagger::on_action_remove));

        m_actions->add (Action::create (ACTION_RELAYOUT_NONE,
                                             StockID (BMP_STOCK_SCAN),
                                             _("Re-Layout Tracks Using No Ordering")),
                                             sigc::bind (sigc::mem_fun (*this, &MusicBrainzTagger::on_action_relayout),
                                             ORDERING_NONE));

        m_actions->add (Action::create (ACTION_RELAYOUT_TRACKNUMBERS,
                                             StockID (BMP_STOCK_SCAN),
                                             _("Re-Layout Tracks Using Tracknumbers")),
                                             sigc::bind (sigc::mem_fun (*this, &MusicBrainzTagger::on_action_relayout),
                                             ORDERING_TRACKNUMBERS));

        m_actions->add (Action::create (ACTION_RELAYOUT_MATCH,
                                             StockID (BMP_STOCK_SCAN),
                                             _("Re-Layout Tracks Using Name Matching")),
                                             sigc::bind (sigc::mem_fun (*this, &MusicBrainzTagger::on_action_relayout),
                                             ORDERING_MATCH));

        m_actions->add (Action::create (ACTION_RESTORE,
                                             Stock::REFRESH,
                                             _("Restore Original Track List")),
                                             sigc::mem_fun (*this, &MusicBrainzTagger::on_action_restore));

        m_actions->add (Action::create (ACTION_SCAN_RELEASE,
                                             Stock::FIND,
                                             _("Find Release based on Selected File")),
                                             AccelKey (""),
                                             sigc::mem_fun (*this, &MusicBrainzTagger::on_action_find));

        m_actions->get_action (ACTION_SCAN_RELEASE)->set_sensitive (false);

        m_actions->add (ToggleAction::create (ACTION_USE_FILENAMES,
                                                   Stock::FILE,
                                                   _("Display/Use Filenames Rather than Track Names")),
                                                   sigc::mem_fun (*this, &MusicBrainzTagger::on_action_use_filenames_toggled));

        m_action_use_filenames = RefPtr<ToggleAction>::cast_static (m_actions->get_action (ACTION_USE_FILENAMES));

        m_ui_manager->insert_action_group (m_actions);
        m_ui_manager->add_ui_from_string (modify_menu);

        m_view_local->signal_event().connect
          (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_popup));

        m_cb_artist_entry = m_cb_artist->get_entry ();
        m_cb_album_entry  = m_cb_album->get_entry ();

        m_cb_artist_entry->set_text ("");
        m_cb_album_entry->set_text ("");

        m_image->set (m_defaultcover);

        struct {
            char const* title;
            double      xalign;
            int         width;
        } const columns[] = {
            { N_("Track"),  0.5, 0,  },
            { N_("Title"),  0.0, 100 },
            { N_("Length"), 0.0, 0,  },
        };

        // Release
        {
          for( unsigned int n = 0; n < G_N_ELEMENTS (columns); ++n )
          {
            CellRendererText * cell = manage (new CellRendererText());
            cell->property_xalign() = columns[n].xalign;
            m_view_release->append_column (_(columns[n].title), *cell);
            TreeViewColumn * column = m_view_release->get_column (n);
            column->set_resizable (true);
            column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::cellDataFuncRelease), ColumnId(n))));
          }

          m_store_release = ListStore::create (mCrRelease);
          m_view_release->set_model (m_store_release);
          m_view_release->get_selection()->set_mode (SELECTION_NONE);
        }

        // Local
        {
          {
            CellRendererToggle * cell = manage (new CellRendererToggle());
            m_view_local->append_column ("", *cell);
            TreeViewColumn *column = m_view_local->get_column (0);

            column->add_attribute (*cell, "active", 0);
            column->set_resizable (false);

            cell->signal_toggled().connect (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_modify_toggled));
          }

          {
            CellRendererText * cell = manage (new CellRendererText());
            m_view_local->append_column (_("Track"), *cell);
            TreeViewColumn * column = m_view_local->get_column (1);

            cell->property_xalign() = 1.;
            cell->property_editable () = true;
            cell->signal_edited().connect (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_track_cell_edited));
            column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::cellDataFuncLocal), COLUMN_NUMBER)));
          }

          {
            CellRendererText *cell = manage (new CellRendererText());
            m_view_local->append_column (_("Title"), *cell);
            TreeViewColumn * column = m_view_local->get_column (2);

            cell->property_xalign() = 0.;
            column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::cellDataFuncLocal), COLUMN_TITLE)));
          }

          {
            CellRendererText *cell = manage (new CellRendererText());
            m_view_local->append_column (_("Length"), *cell);
            TreeViewColumn * column = m_view_local->get_column (3);

            cell->property_xalign() = 0.;
            column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::cellDataFuncLocal), COLUMN_DURATION)));
          }

          {
            CellRendererProgress * cell = manage (new CellRendererProgress());
            m_view_local->append_column ("", *cell);
            TreeViewColumn * column = m_view_local->get_column (4);

            column->set_expand (false);
            column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::cellDataFuncLocal), COLUMN_PROGRESS)));
          }

          m_store_local = ListStore::create (mCrLocal);
          m_view_local->set_model (m_store_local);
          m_view_local->get_selection()->set_mode (SELECTION_SINGLE);
          m_view_local->get_selection()->signal_changed().connect (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::on_local_selection_changed));
        }

        set_response_sensitive (RESPONSE_OK, false);
    }

    MusicBrainzTagger*
    MusicBrainzTagger::Create ()
    {
      std::string const path (BMP_GLADE_DIR G_DIR_SEPARATOR_S "library-ui-modify-import.glade");
      RefPtr<Gnome::Glade::Xml> xml = Gnome::Glade::Xml::create (path);
      MusicBrainzTagger * p = 0;
      xml->get_widget_derived ("dialog-window", p);
      return p;
    }

    void
    MusicBrainzTagger::set_processed ()
    {
      g_atomic_int_set (&m_processed, 1);
    }

    void
    MusicBrainzTagger::on_track_cell_edited (ustring const& path_1, ustring const& new_text)
    {
      if (new_text.empty())
        return;

      TreeNodeChildren::size_type n_target (g_ascii_strtoll (new_text.c_str(), NULL, 10));

      if (n_target == 0)
        return;

      TreeNodeChildren::size_type n_children (m_store_local->children().size());
      TreeNodeChildren::size_type n_children_release (m_store_local->children().size());

      if( (n_children < n_target) && (n_target <= n_children_release) )
      {
        for (TreeNodeChildren::size_type i = 0; i < ((n_target) - n_children); ++i)
        {
          m_store_local->append ();
        }
      }

      TreeIter iter1 (m_store_local->get_iter (path_1));
      TreeIter iter2 (m_store_local->get_iter (TreeModel::Path (1, n_target-1)));

      if( ! bool ((*iter2)[mCrLocal.isTrack]) )
      {
        Track t ((*iter1)[mCrLocal.track]);
        t.tracknumber = n_target;
        (*iter1)[mCrLocal.track] = t; 
        m_store_local->iter_swap (iter1, iter2);
      }
    }

    void
    MusicBrainzTagger::cellDataFuncRelease (CellRenderer * basecell, TreeIter const& i, ColumnId column)
    {
      CellRendererText * cell_t = 0; 

      MusicBrainzTrack t ((*i)[mCrRelease.track]);

      switch (column)
      {
            case COLUMN_NUMBER:
            case COLUMN_TITLE:
            case COLUMN_DURATION:
                cell_t = dynamic_cast<CellRendererText*>(basecell);
                break;

            case COLUMN_PROGRESS:
                g_assert_not_reached ();
                return;
      }

      switch (column)
      {
        case COLUMN_NUMBER:
          cell_t->property_text() = (guint64_f % t.trackTrackNumber).str();
        break;
   
        case COLUMN_TITLE: 
          cell_t->property_text() = t.trackTitle; 
        break;

        case COLUMN_DURATION:
          cell_t->property_text() = (boost::format ("%02d:%02d") % int((t.trackDuration/1000) / 60) % int((t.trackDuration/1000) % 60)).str();
        break;
       
        case COLUMN_PROGRESS: 
            g_assert_not_reached ();
        break;
      }
    }

    void
    MusicBrainzTagger::cellDataFuncLocal (CellRenderer * basecell, TreeIter const& i, ColumnId column)
    {
      CellRendererText * cell_t = 0; 
      CellRendererProgress * cell_p = 0;
      guint64 n = 0;

      Track t ((*i)[mCrLocal.track]);

      switch (column)
      {
            case COLUMN_NUMBER:
            case COLUMN_TITLE:
            case COLUMN_DURATION:
                cell_t = dynamic_cast<CellRendererText*>(basecell);
                if(!t.location)
                {
                    cell_t->property_text() = "";
                    return;
                }
                break;

            case COLUMN_PROGRESS:
                cell_p = dynamic_cast<CellRendererProgress*>(basecell);
                break;
      }

      switch (column)
      {
        case COLUMN_NUMBER:
          n = guint64 (t.tracknumber ? t.tracknumber.get() : 0);
          cell_t->property_text() = ((n == 0) ? "" : (guint64_f % n).str().c_str());
        break;
   
        case COLUMN_TITLE: 
          if( m_action_use_filenames->get_active() )
            cell_t->property_text() = path_get_basename (filename_from_uri (t.location.get()));
          else
            cell_t->property_text() = t.title ? t.title.get() : path_get_basename (filename_from_uri (t.location.get()));
        break;

        case COLUMN_DURATION:
          n = guint64 (t.duration ? t.duration.get() : 0);
          cell_t->property_text() = (t.duration ? (boost::format ("%02llu:%02llu") % (n / 60) % (n % 60)).str() : std::string());
        break;
       
        case COLUMN_PROGRESS: 
          int progress = ((*i)[mCrLocal.puidProgress]);
          if(progress)
            cell_p->property_visible() = true;
          else
            cell_p->property_visible() = false;
          cell_p->property_value() = progress; 

        break;
      }
    }

    void
    MusicBrainzTagger::on_modify_toggled (ustring const& path)
    {
      TreeIter iter( m_store_local->get_iter (path) );
      if( iter && ((*iter)[mCrLocal.isTrack]) ) 
      { 
        (*iter)[mCrLocal.modify] = !(*iter)[mCrLocal.modify];
      }
    }

    void
    MusicBrainzTagger::on_local_selection_changed ()
    {
      bool selected (m_view_local->get_selection()->count_selected_rows () != 0);

      m_actions->get_action (ACTION_MOVE_UP)->set_sensitive (selected);
      m_actions->get_action (ACTION_MOVE_DOWN)->set_sensitive (selected);
      m_actions->get_action (ACTION_SCAN_RELEASE)->set_sensitive (selected);
    }

    void
    MusicBrainzTagger::on_action_remove ()
    {
      TreeIter iter = m_view_local->get_selection()->get_selected ();
      TreePath path = m_store_local->get_path (iter);
      std::string location = Track ((*iter)[mCrLocal.track]).location.get();

      m_store_local->erase (iter);
      m_store_local->insert (m_store_local->get_iter (path));

      for (TrackV::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i)
      {
        if (i->location.get() == location) 
        {
          m_tracks.erase (i);
          break;
        }
      }
    }

    void
    MusicBrainzTagger::on_action_relayout (OrderingType ordering_type)
    {
      populate_local_store (ordering_type);
    }

    void
    MusicBrainzTagger::on_action_restore ()
    {
      m_tracks = m_tracks_original;
      populate_local_store (m_current_ordering);
    }

    void
    MusicBrainzTagger::on_action_up ()
    {
      TreeIter i (m_view_local->get_selection()->get_selected ());
      if( i == m_store_local->children().begin() )
        return;

      Track t ((*i)[mCrLocal.track]);
      t.tracknumber = 0;
      (*i)[mCrLocal.track] = t;

      TreeIter d_iter = i;
      d_iter--; // yes, this is slow, but it doesn't matter much as a release doesn't have that many tracks
      m_store_local->iter_swap (i, d_iter);
      
    }

    void
    MusicBrainzTagger::on_action_down ()
    {
      TreeIter i (m_view_local->get_selection()->get_selected ());
      if( i == m_store_local->children().end() )
        return;

      Track t ((*i)[mCrLocal.track]);
      t.tracknumber = 0;
      (*i)[mCrLocal.track] = t;

      TreeIter d_iter = i;
      d_iter++;
      m_store_local->iter_swap (i, d_iter);
    }

    void
    MusicBrainzTagger::populate_matches (MusicBrainzReleaseV const& x)
    {
      m_cb_artist_entry->set_text ("");
      m_cb_album_entry->set_text ("");
      m_entry_album_id->set_text ("");

      m_image->set (m_defaultcover);
      m_artist_to_releases_map.clear ();

      m_store_release->clear ();
      m_store_local->clear ();
      m_cb_artist_store->clear (); 
      m_cb_album_store->clear (); 
      m_cb_release_event_store->clear ();

      for (MusicBrainzReleaseV::const_iterator i = x.begin (); i != x.end(); ++i)
      {
        MusicBrainzRelease const& release = (*i);
        m_releases.insert (make_pair (release.releaseId, release));
        
        ArtistIdToReleasesMap::iterator map_iter (m_artist_to_releases_map.find (release.mArtist.artistId));
        if( map_iter != m_artist_to_releases_map.end () )
        {
          map_iter->second.push_back (release.releaseId);
        }
        else
        {
          TreeIter iter = m_cb_artist_store->append ();
          (*iter)[mCrArtists.name] = release.mArtist.artistName;
          (*iter)[mCrArtists.id]   = release.mArtist.artistId;

          UStrV id_v;
          id_v.push_back (release.releaseId);
          m_artist_to_releases_map.insert (make_pair (release.mArtist.artistId, id_v));
        }
      }

      m_cb_artist->set_model (m_cb_artist_store);
      m_cb_artist->set_active (0);

      m_cb_album->set_model (m_cb_album_store);
      m_cb_album->set_active (0);

      set_response_sensitive (RESPONSE_OK, m_store_release->children().size());
    }

    bool
    MusicBrainzTagger::on_popup (GdkEvent * e)
    {
      GdkEventButton * event = 0;
      if( e->type == GDK_BUTTON_PRESS )
      {
        event = reinterpret_cast<GdkEventButton *>(e);
        if( event->button == 3 )
        {
          m_actions->get_action (ACTION_REMOVE)->set_sensitive (m_view_local->get_selection()->count_selected_rows());
          m_actions->get_action (ACTION_RELAYOUT_MATCH)->set_sensitive (m_store_release->children().size());

          Menu * menu = dynamic_cast<Menu *>(Util::get_popup (m_ui_manager, "/popup-modify/menu-modify"));
          if (menu) // better safe than screwed
          {
            menu->popup (event->button, event->time);
          }
          return true;
        }
      }
      return false;
    }

    void
    MusicBrainzTagger::on_action_find ()
    {
      scan_action_begin ();

      TreeIter i (m_view_local->get_selection()->get_selected ());
      boost::optional <ustring> puid (scan_file (i));

      if( puid )
      {
        MusicBrainzTracklistTrackV tracks;
        mb_tracks_by_puid (puid.get(), tracks);
        MusicBrainzReleaseV releases;
        for (MusicBrainzTracklistTrackV::const_iterator i = tracks.begin(); i != tracks.end(); ++i)
        {
          if( !i->mReleaseV.empty() )
          {
            for (MusicBrainzReleaseV::const_iterator n = i->mReleaseV.begin(); n != i->mReleaseV.end(); ++n)
            {
              mb_releases_by_id (n->releaseId, releases);
            }
          }
        }
        populate_matches (releases);
      }
      scan_action_end ();
    }

    void
    MusicBrainzTagger::scan_action_begin ()
    {
      set_sensitive (false);
    }
    
    void
    MusicBrainzTagger::scan_action_end ()
    {
      set_sensitive (true);
    }
    
    boost::optional <ustring>
    MusicBrainzTagger::scan_file (TreeIter & i)
    {
      g_atomic_int_set (&m_processed, 0);

      Audio::ProcessorPUID * p = new Bmp::Audio::ProcessorPUID;
      p->signal_eos().connect (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::set_processed));
      p->signal_position().connect (sigc::bind (sigc::mem_fun (*this, &Bmp::MusicBrainzTagger::set_iter_puid_progress), i));

      Track const& track = (*i)[mCrLocal.track];
      p->set_uri (track.location.get());
      p->run ();

      while (!m_processed)
        while (Main::events_pending()) Main::iteration ();

      (*i)[mCrLocal.puidProgress] = 0; 

      boost::optional <ustring> puid (p->get_puid ());
      delete p;
      return puid;
    }

    void
    MusicBrainzTagger::set_iter_puid_progress (int position, TreeIter & i)
    {
      (*i)[mCrLocal.puidProgress] = int (((1.*position) / 135.) * 100);
      m_store_local->row_changed (TreePath (i), i);
    }

    void
    MusicBrainzTagger::on_action_search ()
    {
      MusicBrainzReleaseV releases;

      if( !m_entry_album_id->get_text().empty() && m_cb_artist_entry->get_text().empty() &&  m_cb_album_entry->get_text ().empty() )
      {
        mb_releases_by_id (m_entry_album_id->get_text(), releases);
      }
      else
      {
        mb_releases_query (m_cb_artist_entry->get_text(), m_cb_album_entry->get_text(), releases);
      }

      populate_matches (releases);
      populate_local_store (ORDERING_AUTO);
    }

    void
    MusicBrainzTagger::on_action_use_filenames_toggled ()
    {
      if( m_action_use_filenames->get_active() )
      {
        m_actions->get_action (ACTION_RELAYOUT_TRACKNUMBERS)->set_sensitive (false);
        m_current_ordering = ORDERING_MATCH;
        populate_local_store (m_current_ordering);
      }
      else
      {
        m_actions->get_action (ACTION_RELAYOUT_TRACKNUMBERS)->set_sensitive (true);
        m_current_ordering = ORDERING_AUTO;
        populate_local_store (m_current_ordering);
      }
    }

    void
    MusicBrainzTagger::populate_local_store (OrderingType requested_ordering_type)
    {
      TreeModel::Children const&  nodes_r (m_store_release->children());
      std::set <unsigned int>     tracknumbers;
      OrderingType                ordering_type;
      unsigned int                n_rows;

      m_current_ordering = requested_ordering_type;

      if( m_release.mTrackV.size() > m_tracks.size() )
        n_rows = m_release.mTrackV.size();
      else
        n_rows = m_tracks.size();

      if( !nodes_r )
      {
        // don't order by track number or levenshtein matching when there is no release selected
        requested_ordering_type = ORDERING_NONE;
      }

      m_store_local->clear();

      for (unsigned int n = 0 ; n < n_rows; ++n)
      {
        TreeIter iter = m_store_local->append ();

        Track t;
        t.tracknumber = 0;
        (*iter)[mCrLocal.track] = t;
        (*iter)[mCrLocal.isTrack] = 0;
        (*iter)[mCrLocal.modify] = 0;
        (*iter)[mCrLocal.puidProgress] = 0; 
      }

      if( requested_ordering_type == ORDERING_AUTO )
      {

        if( m_action_use_filenames->get_active() )
        {
            ordering_type = ORDERING_MATCH;
        }
        else
        {
            ordering_type = ORDERING_TRACKNUMBERS;

          for (TrackV::const_iterator i = m_tracks.begin (); i != m_tracks.end (); ++i)
          {
                Track const& track = *i;
                if( !track.tracknumber || !track.tracknumber.get() || (tracknumbers.find (track.tracknumber.get()) != tracknumbers.end()) )
                {
                      ordering_type = ORDERING_MATCH;
                      break;
                }
                tracknumbers.insert (track.tracknumber.get());
          }
        }

      }
      else
      {
            ordering_type = requested_ordering_type;
      }

      TreePath::value_type path_index = 0;
      for (TrackV::const_iterator i = m_tracks.begin (); i != m_tracks.end(); ++i)
      {
        Track const& track = *i;
        int index = 0; 

        switch (ordering_type)
        {
          case ORDERING_MATCH:
          {
                ustring::size_type d = ustring::npos;
                for (TreeNodeChildren::const_iterator n = nodes_r.begin(); n != nodes_r.end(); ++n)
                {
                      MusicBrainzTrack t ((*n)[mCrRelease.track]);
                      ustring::size_type d_calc = ld_distance<ustring> (track.title.get(), t.trackTitle);
                      if( d_calc < d )
                      {
                          d = d_calc;
                          index = m_store_release->get_path (n).get_indices().data()[0];
                      }
                }
                break;
              }

          case ORDERING_TRACKNUMBERS:
          {
                index = track.tracknumber.get() - 1;
                break;
          }

          case ORDERING_NONE:
          {
                index = path_index++;
                break;
          }

          default: break;
        }

        TreePath path (TreePath::size_type (1), TreePath::value_type (index));

        TreeIter iter = m_store_local->get_iter (path);
        (*iter)[mCrLocal.track] = track;
        (*iter)[mCrLocal.modify] = bool (m_store_release->get_iter (path));
        (*iter)[mCrLocal.isTrack] = 1;
      }
      m_view_local->columns_autosize ();
    }

    void
    MusicBrainzTagger::on_album_changed ()
    {
      if( m_cb_album->get_active_row_number () >= 0 )
      {
        m_store_release->clear ();

        MusicBrainzReleaseV v;
        ustring id = m_releases.find ((*m_cb_album->get_active())[mCrAlbums.id])->second.releaseId; 
        mb_releases_by_id (id, v);
        if (v.empty())
        {
          g_warning("%s: Couldn't fetch release %s from musicbrainz!", G_STRLOC, id.c_str()); 
          return; 
        }
        else
          debug("musicbrainz","%s: %u Releases", G_STRLOC, guint(v.size())); 
      
        m_release = v[0];
        m_entry_album_id->set_text (m_release.releaseId);

        try{
            if( !m_release.releaseASIN.empty() )
            {
              RefPtr <Pixbuf> cover;
              Amazon::Covers::Obj()->fetch (m_release.releaseASIN, cover);
              m_image->set (cover->scale_simple (128, 128, INTERP_BILINEAR));
            }
            else
              m_image->set (m_defaultcover);
          }
        catch (...)
          {
              m_image->set (m_defaultcover);
          }

        MusicBrainzTrackV const& x (m_release.mTrackV);
        for (MusicBrainzTrackV::const_iterator i = x.begin(); i != x.end() ; ++i)
        {
              TreeIter iter = m_store_release->append();
              (*iter)[mCrRelease.track] = *i;
        }

        m_cb_release_date->set_sensitive (0);
        m_cb_release_event_store->clear ();

        if( m_release.mReleaseEventV.size() )
        {
          for (MusicBrainzReleaseEventV::const_iterator i = m_release.mReleaseEventV.begin(); i != m_release.mReleaseEventV.end(); ++i)
          {
            TreeIter iter = m_cb_release_event_store->append ();
            if (! i->releaseEventCountry.empty())
              (*iter)[mCrEvents.event_string] = (boost::format ("%s (%s)") % i->releaseEventDate % i->releaseEventCountry).str();
            else
              (*iter)[mCrEvents.event_string] = i->releaseEventDate; 
          }
          m_cb_release_date->set_active (0);
          m_cb_release_date->set_sensitive (1);
        }

        m_view_release->columns_autosize ();
        populate_local_store (ORDERING_AUTO);
        set_response_sensitive (RESPONSE_OK, true);
      }
    }

    void
    MusicBrainzTagger::on_artist_changed ()
    {
      if( m_cb_artist->get_active_row_number () >= 0 )
      {
        m_cb_album_store->clear ();
        m_cb_album_entry->set_text ("");

        m_store_release->clear ();

        TreeIter const& i = m_cb_artist->get_active ();
        UStrV const& id_v = m_artist_to_releases_map.find ((*i)[mCrArtists.id])->second;

        std::set<ustring> ReleaseSet;

        for (UStrV::const_iterator i  = id_v.begin(); i != id_v.end(); ++i)
        {
          MusicBrainzRelease const& release = m_releases.find (*i)->second;
          if( ReleaseSet.find (release.releaseId) == ReleaseSet.end() )
          {
            ReleaseSet.insert (release.releaseId);

            TreeIter iter = m_cb_album_store->append ();
            (*iter)[mCrAlbums.name] = release.releaseTitle;
            (*iter)[mCrAlbums.id] = release.releaseId;
          }
        }

        populate_local_store (ORDERING_AUTO);
        m_cb_album->set_active (0);
        set_response_sensitive (RESPONSE_OK, m_store_release->children().size());
      }
    }

    int
    MusicBrainzTagger::run (TrackV const& tracks, TrackV & modify_tracks)
    {
      m_tracks          = tracks;
      m_tracks_original = tracks;

      populate_local_store (m_current_ordering);

      int response = Dialog::run ();

      if( response == RESPONSE_OK )
      {
        TreeNodeChildren const& nodes_r (m_store_release->children());
        TreeNodeChildren const& nodes_l (m_store_local->children());

        TreeNodeChildren::const_iterator iter_r (nodes_r.begin());
        TreeNodeChildren::const_iterator iter_l (nodes_l.begin());

        int release_event_index = -1;
        if( m_release.mReleaseEventV.size() )
        {
           release_event_index = m_cb_release_date->get_active_row_number ();
        }

        for (; iter_r != nodes_r.end(), iter_l != nodes_l.end(); ++iter_r, ++iter_l)
        {
              if( (*iter_l)[mCrLocal.modify] )
              {
                    Track track ((*iter_l)[mCrLocal.track]);
                    MusicBrainzTrack track_mb ((*iter_r)[mCrRelease.track]);

                    imbue_album (m_release, track, release_event_index);
                    imbue_track (track_mb, track);

                    if( m_cb_modify_genre->get_active() )
                    {
                      track.genre = m_cb_genre_entry->get_text ();
                    }

                    modify_tracks.push_back (track);
              }

              if( (iter_l == nodes_l.end()) || (iter_r == nodes_r.end()) )
              {
                    break;
              }
        }
      }
      return response;
    }
}
