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

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

#include <glibmm.h>
#include <glibmm/i18n.h>
#include <glib/gstdio.h>
#include <gtkmm.h>
#include <libglademm.h>

#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

#include "base64.h"
#include "sha1.h"

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

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

// BMP Widgets
#include "widgets/taskdialog.hh"

// BMP Display
#include "debug.hh"
#include "main.hh"
#include "network.hh"
#include "paths.hh"
#include "stock.hh"
#include "ui-tools.hh"
#include "uri.hh"
#include "util.hh"
#include "util-file.hh"

#include "amazon.hh"
#include "core.hh"
#ifdef HAVE_HAL
#  include "x_hal.hh"
#endif //HAVE_HAL
#include "x_library.hh"

#include "ui-part-cdda.hh"

extern "C"
{
#if defined (HAVE_CDPARANOIA)
  #include <cdda_interface.h>
#elif defined (HAVE_CDIO)
  #define NEED_STRERROR_TR

  #include <cdio/cdda.h>
  #define IS_AUDIO(d, n) cdio_cddap_track_audiop(d, n+1)
#endif

  namespace { const char * _dummy = strerror_tr[0]; }
}

using namespace Gtk;
using namespace Glib;
using namespace std;

using namespace Bmp::MusicBrainzXml;
using namespace Bmp::Audio;

namespace
{
  const char *unknown_artist = N_("Unknown Artist");
  const char *unknown_album  = N_("Unknown Album");
  const char *unknown_title  = N_("Unknown Title");

  int
  get_duration_from_sectors (int sectors)
  {
    const int bytes_per_sector  = 2352;
    const int bytes_per_seconds = (44100 / 8) / 16 / 2;
    return (sectors * bytes_per_sector / bytes_per_seconds)/1000;
  }

  void
  get_data_track_mapping (cdrom_drive * d, Bmp::BoolV & tracks, int & n_tracks)
  {
    for (int n = 0; n < d->tracks; ++n)
    {
      tracks.push_back (IS_AUDIO(d, n) ? true : false);
    }
    n_tracks = d->tracks;
  }

  void 
  get_discid (cdrom_drive * d, std::string & disc_id)
  {
    SHA_INFO        sha;
    unsigned char   digest[20], *base64;
    unsigned long   size;
    char            tmp[17]; /* for 8 hex digits (16 to avoid trouble) */
    char            buf[33];

    sha_init(&sha);
    sprintf(tmp, "%02X", d->disc_toc[0].bTrack);
    sha_update(&sha, (unsigned char *) tmp, strlen(tmp));

    sprintf(tmp, "%02X", d->disc_toc[d->tracks-1].bTrack);
    sha_update(&sha, (unsigned char *) tmp, strlen(tmp));

    int track_offsets[100];
    for (int n = 0; n < 100; track_offsets[n++] = 0);

    track_offsets[0] = d->disc_toc[d->tracks].dwStartSector + 150;

    for (int n = d->disc_toc[0].bTrack-1; n <= d->disc_toc[d->tracks-1].bTrack-1; ++n) 
    {
      track_offsets[n+1] = d->disc_toc[n].dwStartSector + 150;
    }

    for (int i = 0; i < 100; i++)
    {
      sprintf(tmp, "%08X", track_offsets[i]);
      sha_update(&sha, (unsigned char *) tmp, strlen(tmp));
    }

    sha_final(digest, &sha);
    base64 = rfc822_binary (digest, sizeof(digest), &size);
    memcpy(buf, base64, size);
    buf[size] = '\0';
    free(base64);
    disc_id = buf;
  }

  const char *processor_names[] =
  {
    "MP3 (MPEG 1 Layer III)",
    "Ogg Vorbis",
    "FLAC Lossless Encoding"
  };

  const char *suffixes[] =
  {
    ".mp3",
    ".ogg",
    ".flac"
  };

  static boost::format track_f ("%02d");
  static boost::format cdda_uri_f ("cdda:///%d");

}

namespace
{
  using namespace Bmp;
  const PlaybackSource::Flags flags =
    PlaybackSource::Flags (PlaybackSource::F_ALWAYS_IMAGE_FRAME | PlaybackSource::F_HANDLE_LASTFM | PlaybackSource::F_HANDLE_LASTFM_ACTIONS |
                           PlaybackSource::F_USES_REPEAT | PlaybackSource::F_USES_SHUFFLE);
}

namespace Bmp
{
  namespace UiPart
  {
    SignalRequestImport&
    CDDA::signal_request_import ()
    { 
      return Signals.RequestImport;
    }

    CDDA::CDDA (RefPtr<Gnome::Glade::Xml> const& xml, RefPtr<UIManager> ui_manager)
    : PlaybackSource  (_("Audio CD"), CAN_SEEK, flags) 
    , Base            (xml, ui_manager)
    , m_processing_cd (false)
    , m_anonymous_cd  (false)
    , m_stream_eos    (false)
    {
      Data.IconDefaultCover = Gdk::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 ("notebook-cdda", Widgets.Display.Notebook);
      m_ref_xml->get_widget ("cdda-label-current-artist", Widgets.Label.Artist);
      m_ref_xml->get_widget ("cdda-label-current-album", Widgets.Label.Album);
      m_ref_xml->get_widget ("cdda-label-current-date", Widgets.Label.Date);
      m_ref_xml->get_widget ("cdda-image-cover", Widgets.Image.Cover);

      Audio::Caps audiocaps (Bmp::Audio::get_caps());
      if( !(audiocaps & Audio::CAPS_CDDA) )
      {
        dynamic_cast<Image *>(m_ref_xml->get_widget ("i-audiocd-bad-2"))->set (BMP_IMAGE_DIR_CDDA G_DIR_SEPARATOR_S "audiocd-unplayable.png");
        Widgets.Display.Notebook->set_current_page (PAGE_NO_SUPPORT);
        m_ref_xml->get_widget ("hbox-cdda-controls")->hide ();
        return;
      }

      m_ref_xml->get_widget ("b-cdda-rip", Widgets.Buttons.Rip);
      m_ref_xml->get_widget ("b-cdda-reload", Widgets.Buttons.Reload);
      m_ref_xml->get_widget ("b-cdda-stop", Widgets.Buttons.Stop);
      m_ref_xml->get_widget ("cdda-cbox-format", Widgets.CBox.Format);
      m_ref_xml->get_widget ("cdda-cbox-quality", Widgets.CBox.Quality);

      Widgets.CBox.Quality->set_active (1);

      Data.StoreProcessors = ListStore::create (cr_processors);
      CellRendererText *cell = manage (new CellRendererText());
      Widgets.CBox.Format->clear ();
      Widgets.CBox.Format->pack_start (*cell);
      Widgets.CBox.Format->add_attribute (*cell, "text", 0);
      Widgets.CBox.Format->set_model (Data.StoreProcessors);

      for (int n = 0; n < N_PROCS; ++n)
      {
        if( Audio::ProcessorFactory::test_processor (ProcessorType (n)) )
        {
          TreeIter iter (Data.StoreProcessors->append ());
          (*iter)[cr_processors.name] = processor_names[n];
          (*iter)[cr_processors.type] = ProcessorType (n);
        }
      }

      dynamic_cast<Image *>(m_ref_xml->get_widget ("throbber-cdda"))->set
                      (BMP_IMAGE_DIR G_DIR_SEPARATOR_S BMP_THROBBER);

      dynamic_cast<Image *>(m_ref_xml->get_widget ("i-audiocd"))->set
                      (BMP_IMAGE_DIR_CDDA G_DIR_SEPARATOR_S "audiocd-big.png");

      dynamic_cast<Image *>(m_ref_xml->get_widget ("i-audiocd-bad"))->set
                      (BMP_IMAGE_DIR_CDDA G_DIR_SEPARATOR_S "audiocd-unplayable.png");

      const char *headers[] =
      {
         N_("Track"),
         N_("Artist"),
         N_("Title"),
         N_("Duration"),
         N_("Progress"),
         N_("State"),
      };

      // --- CDDA View+Store
      m_ref_xml->get_widget ("cdda-view", Widgets.Display.View);
      Widgets.Display.View->get_selection()->set_mode (SELECTION_SINGLE);

      TreeViewColumn *column = 0;
      Widgets.Display.CellPlaying = manage (new CellRendererPixbuf());
      Widgets.Display.CellPlaying->property_xalign() = 0.5;
      Widgets.Display.View->append_column ("", *Widgets.Display.CellPlaying);

      column = Widgets.Display.View->get_column (0);
      column->set_resizable (false);
      column->set_expand (false);
      column->property_min_width() = 30;
      column->property_max_width() = 30;
      column->set_cell_data_func (*Widgets.Display.CellPlaying, sigc::mem_fun (*this, &Bmp::UiPart::CDDA::cell_data_func_playing));

      for (unsigned int n = 0; n < G_N_ELEMENTS(headers); ++n)
      {
        if( n == 3 )
        {
            CellRendererText *cell = manage ( new CellRendererText() );

            Widgets.Display.View->append_column (_(headers[n]), *cell);

            column = Widgets.Display.View->get_column (n+1);
            column->set_resizable (false);
            column->set_expand (false);
            column->set_cell_data_func (*cell, sigc::mem_fun (*this, &Bmp::UiPart::CDDA::cell_data_func_numbers));
        }
        else
        if( n == 4 )
        {
            CellRendererProgress *cell = manage ( new CellRendererProgress() );

            Widgets.Display.View->append_column (_(headers[n]), *cell);

            column = Widgets.Display.View->get_column (n+1);
            column->add_attribute (*cell, "value", n);
            column->set_resizable (false);
            column->set_expand (false);
            column->set_fixed_width (100);
            column->set_sizing (TREE_VIEW_COLUMN_FIXED);
        }
        else
        {
            CellRendererText *cell = manage ( new CellRendererText() );
            if (n == 0) cell->property_xalign() = 1.0;

            Widgets.Display.View->append_column (_(headers[n]), *cell);
            if( n != 5 )
            {
              column = Widgets.Display.View->get_column (n+1);
              column->add_attribute (*cell, "markup", n);
              column->set_resizable (false);
              column->set_expand (false);
            }
            else
            {
              column = Widgets.Display.View->get_column (n+1);
              column->set_resizable (false);
              column->set_expand (true);
              column->set_cell_data_func (*cell, sigc::mem_fun (*this, &Bmp::UiPart::CDDA::cell_data_func_state));
            }
        }
      }

      Widgets.Display.View->get_column (5)->set_visible (false);
      Widgets.Display.View->get_column (6)->set_visible (false);

      Data.Store = ListStore::create (cr_audio_cd);
      Widgets.Display.View->set_model (Data.Store);
      Widgets.Display.View->get_selection()->signal_changed().connect
        (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::selection_changed));

      if( Data.StoreProcessors->children().size() )
      {
        Widgets.CBox.Format->set_active (0);

#ifdef HAVE_HAL
        Widgets.Buttons.Reload->hide();
        hal->signal_cdda_inserted().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::on_hal_cdda_inserted));
        hal->signal_device_removed().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::on_hal_device_removed));
        hal->signal_ejected().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::on_hal_ejected));
        Widgets.Display.Notebook->set_current_page (PAGE_CD_INSERT);
#else
        Widgets.Buttons.Reload->signal_clicked().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::refresh));
        Widgets.Display.Notebook->set_current_page (PAGE_TOC);
#endif //HAVE_HAL

        Widgets.Buttons.Rip->signal_clicked().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::rip_start));
        Widgets.Buttons.Stop->signal_clicked().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::rip_stop));

        Widgets.Buttons.Rip->set_sensitive (false);
      }
      else
      {
        Widgets.CBox.Format->set_sensitive (false);
        Widgets.CBox.Quality->set_sensitive (false);

#ifdef HAVE_HAL
        Widgets.Buttons.Reload->hide();
        Widgets.Buttons.Reload->set_sensitive (false);
#endif //HAVE_HAL

        Widgets.Buttons.Rip->set_sensitive (false);
        Widgets.Display.View->set_sensitive (false);
        Widgets.Display.Notebook->set_current_page (PAGE_TOC);
      }

      Widgets.Display.View->signal_row_activated().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::activate_default));

      Core::Obj()->signal_shutdown_request().connect
            (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::on_shutdown_request));
    }

    guint
    CDDA::add_ui ()
    {
      return 0;
    }

    CDDA::~CDDA () {}

    bool
    CDDA::on_shutdown_request ()
    {
      if( get_processing_cd() )
      {
        MessageDialog dialog ((*(dynamic_cast<Window *>(Widgets.Display.View->get_toplevel()))),
                                _("<b>Ripping in Progress: Can't Shutdown!</b>\n\nBMP currently cannot shut down because a "
                                  "CD is being ripped. "
                                  "Please either abort the encoding process, or wait until it is finished to exit BMP."),
                                  true, MESSAGE_WARNING, BUTTONS_OK, true);
        dialog.set_position (WIN_POS_CENTER);
        dialog.run();
        return false;
      }
      else
      {
        return true;
      }
    }

    void
    CDDA::cell_data_func_numbers (CellRenderer* cell, TreeIter const& iter)
    {
      static boost::format fduration ("%d:%02d");
      CellRendererText *cell_text = dynamic_cast<CellRendererText*>(cell);
      unsigned int duration = (*iter)[cr_audio_cd.duration];
      unsigned int minutes = duration / 60;
      unsigned int seconds = duration % 60;
      cell_text->property_text() = (fduration % minutes % seconds).str();
    }

    void
    CDDA::activate_default (TreeModel::Path const& path, TreeViewColumn* column)
    {
      assign_current_iter (Data.Store->get_iter (path));
      mSignalPlayRequest.emit ();
    }

    void
    CDDA::view_fill (void * _d, MusicBrainzReleaseV const& releases, BoolV const& audiotracks)
    {
      cdrom_drive * d = static_cast<cdrom_drive*>(_d);
      if( releases.empty() )
      {
        Widgets.Label.Artist->set_markup ((boost::format ("<b>%s</b>") % _(unknown_artist)).str());
        Widgets.Label.Album->set_markup ((boost::format ("<i>%s</i>") % _(unknown_album)).str());
        Widgets.Label.Date->set_text ("");
        Widgets.Image.Cover->set (Data.IconDefaultCover);

        for (int n = d->disc_toc[0].bTrack; n <= d->disc_toc[d->tracks-1].bTrack; ++n)
        {
          TreeIter iter = Data.Store->append ();

          (*iter)[cr_audio_cd.tracknumber]  = n;
          (*iter)[cr_audio_cd.artist]       = _(unknown_artist);
          (*iter)[cr_audio_cd.duration]     = get_duration_from_sectors (cdda_track_lastsector (d, n) - cdda_track_firstsector (d, n));
          (*iter)[cr_audio_cd.audio]        = audiotracks[n];

          if( audiotracks[n-1] )
          {
#if defined (HAVE_CDPARANOIA)
            (*iter)[cr_audio_cd.title] = (boost::format ("Track %02d") % (n+1)).str();
#elif defined (HAVE_CDIO)
            (*iter)[cr_audio_cd.title] = (boost::format ("Track %02d") % (n)).str();
#endif
          }
          else
          {
            (*iter)[cr_audio_cd.title] = _("(Data Track)");
          }
        }

        m_anonymous_cd = true;
        Widgets.Buttons.Rip->set_sensitive (false);
        Widgets.CBox.Format->set_sensitive (false);
        Widgets.CBox.Quality->set_sensitive (false);
      }
      else
      {
        Data.MBRelease = releases[0];
        Widgets.Label.Artist->set_markup ((boost::format ("<b>%s</b>") %
                        Markup::escape_text (!Data.MBRelease.mArtist.artistSortName.empty()
                                             ? Data.MBRelease.mArtist.artistSortName
                                             : Data.MBRelease.mArtist.artistName).c_str()).str());

        Widgets.Label.Album->set_markup ((boost::format ("<i>%s</i>") %
                        Markup::escape_text (Data.MBRelease.releaseTitle).c_str()).str());

        if( !Data.MBRelease.mReleaseEventV.empty() )
          Widgets.Label.Date->set_text (Util::get_date_string (Data.MBRelease.mReleaseEventV[0].releaseEventDate));
        else
          Widgets.Label.Date->set_text ("");

        if( !Data.MBRelease.releaseASIN.empty() ) 
        {
          RefPtr<Gdk::Pixbuf> cover;
          Amazon::Covers::Obj()->fetch (Data.MBRelease.releaseASIN, cover, false);
          Widgets.Image.Cover->set (cover->scale_simple (128, 128, Gdk::INTERP_BILINEAR));
        }
        else
        {
          Widgets.Image.Cover->set (Data.IconDefaultCover);
        }

        unsigned int i = d->disc_toc[0].bTrack;
        MusicBrainzTrackV const& mbtracks (Data.MBRelease.mTrackV);
        for (MusicBrainzTrackV::const_iterator n = mbtracks.begin(); n != mbtracks.end(); ++n)
        {
          TreeIter iter = Data.Store->append ();

          if( n->trackTrackNumber != i )
          {
            debug("ui-cdda","%s: CDParanoia says track is %d, but release has it as track %llu", G_STRFUNC, i, n->trackTrackNumber);
          }

          (*iter)[cr_audio_cd.tracknumber]  = n->trackTrackNumber;
          (*iter)[cr_audio_cd.artist]       = Markup::escape_text (n->artistName);
          (*iter)[cr_audio_cd.duration]     = get_duration_from_sectors (cdda_track_lastsector (d, i) - cdda_track_firstsector (d, i));
          (*iter)[cr_audio_cd.percentage]   = 0;
          (*iter)[cr_audio_cd.state]        = ES_UNPROCESSED;

          if( audiotracks[i-1] )
          {
              (*iter)[cr_audio_cd.title] = Markup::escape_text (n->trackTitle);
              (*iter)[cr_audio_cd.track] = *n;
          }
          else
          {
              (*iter)[cr_audio_cd.title] = _("(Data Track)");
          }

          (*iter)[cr_audio_cd.audio] = audiotracks[i-1];
          ++i;
        }

        m_anonymous_cd = false;
        Widgets.Buttons.Rip->set_sensitive (true);
        Widgets.CBox.Format->set_sensitive (true);
        Widgets.CBox.Quality->set_sensitive (true);
      }
    }

    void
    CDDA::view_clear ()
    {
      Data.Store->clear();
      Widgets.Label.Artist->set_text ("");
      Widgets.Label.Album->set_text ("");
      Widgets.Label.Date->set_text ("");
      Widgets.Image.Cover->clear();
      Widgets.Buttons.Rip->set_sensitive (false);
      Widgets.Buttons.Stop->set_sensitive (false);
      Widgets.CBox.Format->set_sensitive (false);
      Widgets.CBox.Quality->set_sensitive (false);
    }

    void
    CDDA::notebook_set_page (int page)
    {
      Widgets.Display.Notebook->set_current_page (page);
      while (gtk_events_pending()) gtk_main_iteration ();
    }

#ifdef HAVE_HAL
    void
    CDDA::on_hal_cdda_inserted (string udi, string devicefile)
    {
      if( !get_processing_cd() )
      {
        m_current_udi = udi;
        m_current_device = devicefile;
        mcs->key_set ("audio", "cdrom-device", devicefile);

        view_clear ();
        notebook_set_page (PAGE_LOADING);

        MusicBrainzReleaseV releases;
        BoolV               audiotracks;
        int                 n_tracks = 0;
        std::string         disc_id;

        cdrom_drive *d = 0;

        d = cdda_identify (devicefile.c_str(), 0, 0);

        if( !d )
        {
          g_warning ("%s: cdda_identify", G_STRLOC);
          notebook_set_page (PAGE_CD_BAD);
          return;
        }

        if( cdda_open (d) )
        {
          g_warning ("%s: cdda_open", G_STRLOC);
          notebook_set_page (PAGE_CD_BAD);
          return;
        }

        d->read_toc (d);

        get_data_track_mapping (d, audiotracks, n_tracks);
        get_discid (d, disc_id);

        if( disc_id.size() )
        {
          mb_releases_by_disc_id (disc_id, releases);
        }

        view_fill (d, releases, audiotracks);
        if (d) cdda_close (d);

        notebook_set_page (PAGE_TOC);
      }
    }

    void
    CDDA::on_hal_ejected (string udi)
    {
      if( udi == m_current_udi )
      {
        if( get_processing_cd() )
          rip_stop ();
        else
          mSignalStopRequest.emit ();
      }
    }

    void
    CDDA::on_hal_device_removed (string udi)
    {
      if( (!get_processing_cd()) && udi == m_current_udi )
      {
        Data.Store->clear ();
        Widgets.Label.Artist->set_text ("");
        Widgets.Label.Album->set_text ("");
        Widgets.Label.Date->set_text ("");
        Widgets.Image.Cover->clear();
        Widgets.Buttons.Rip->set_sensitive (false);
        Widgets.Display.Notebook->set_current_page (PAGE_CD_INSERT);
        Widgets.CBox.Format->set_sensitive (false);
        Widgets.CBox.Quality->set_sensitive (false);
        Widgets.Display.View->get_column (5)->set_visible (false);
        Widgets.Display.View->get_column (6)->set_visible (false);
      }
    }

#else //!HAVE_HAL

    void
    CDDA::refresh ()
    {
      if( !get_processing_cd() )
      {
        view_clear ();
        notebook_set_page (PAGE_LOADING);

        MusicBrainzReleaseV releases;
        BoolV               audiotracks;
        int                 n_tracks = 0;
        std::string         disc_id;

        cdrom_drive *d = 0;

        d = cdda_identify (mcs->key_get<string>("audio", "cdrom-device").c_str(), 0, 0);

        if( !d )
        {
          g_warning ("%s: cdda_identify", G_STRLOC);
          notebook_set_page (PAGE_CD_BAD);
          return;
        }

        if( cdda_open (d) )
        {
          g_warning ("%s: cdda_open", G_STRLOC);
          notebook_set_page (PAGE_CD_BAD);
          return;
        }

        d->read_toc (d);

        get_data_track_mapping (d, audiotracks, n_tracks);
        get_discid (d, disc_id);

        if( disc_id.size() )
        {
          mb_releases_by_disc_id (disc_id, releases);
        }

        view_fill (d, releases, audiotracks);
        if (d) cdda_close (d);

        notebook_set_page (PAGE_TOC);
      }
   }
#endif //HAVE_HAL

    void
    CDDA::rip_stop ()
    {
      Data.RipLock.unlock ();
    }

    void
    CDDA::rip_start ()
    {
      if( get_processing_cd() )
        return;

      mSignalStopRequest.emit (); 

      unsigned int current_rip_track;
      std::string  current_rip_basepath;

      // Disable Controls
      Widgets.Buttons.Rip->set_sensitive (false);
      Widgets.CBox.Format->set_sensitive (false);
      Widgets.CBox.Quality->set_sensitive (false);
      set_processing_cd (true);

      // Select the Destination Folder
      FileChooserDialog file_chooser_dialog (_("Select Destination Folder - BMP"), FILE_CHOOSER_ACTION_SELECT_FOLDER);
      file_chooser_dialog.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
      file_chooser_dialog.add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
      file_chooser_dialog.set_default_response (Gtk::RESPONSE_OK);
      file_chooser_dialog.set_current_folder (mcs->key_get<std::string>("bmp", "file-chooser-path"));

      if( file_chooser_dialog.run () == Gtk::RESPONSE_CANCEL )
      {
        file_chooser_dialog.hide ();
        Widgets.Buttons.Rip->set_sensitive (true);
        Widgets.CBox.Format->set_sensitive (true);
        Widgets.CBox.Quality->set_sensitive (true);
        set_processing_cd (false);
        return;
      }

      file_chooser_dialog.hide ();
      current_rip_basepath = filename_from_uri (file_chooser_dialog.get_current_folder_uri());

      for (TreeNodeChildren::iterator iter  = Data.Store->children().begin()  ;
                                      iter != Data.Store->children().end()    ; ++iter)
      {
        (*iter)[cr_audio_cd.state] = ES_WAITING;
      }

      Data.RipLock.lock ();
      Widgets.Buttons.Stop->set_sensitive (true);

#ifndef HAVE_HAL
      Widgets.Buttons.Reload->set_sensitive (false);
#endif //HAVE_HAL

      Widgets.Display.View->get_column (5)->set_visible (true);
      Widgets.Display.View->get_column (6)->set_visible (true);

      Widgets.Display.View->get_selection()->unselect_all ();
      Widgets.Display.View->get_selection()->set_mode (SELECTION_NONE);

      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PLAY);
      mSignalCaps.emit (m_caps);

      TreeIter c_iter = Widgets.CBox.Format->get_active ();
      ProcessorType processor_type = (*c_iter)[cr_processors.type];

      for (unsigned int n = 0; n < Data.MBRelease.mTrackV.size(); ++n)
      {
        current_rip_track = n;

        TreePath path;
        path.append_index (current_rip_track);

        Data.RipIter = Data.Store->get_iter (path);

        if( (*Data.RipIter)[cr_audio_cd.state] == ES_DONE )
          continue;

        MusicBrainzTrack const& track = (*Data.RipIter)[cr_audio_cd.track];

        ustring filename;
        filename  .append ((track_f % (n+1)).str())
                  .append (" - ")
                  .append (Data.MBRelease.mArtist.artistName)
                  .append (" - ")
                  .append (Data.MBRelease.releaseTitle)
                  .append (" - ")
                  .append (track.trackTitle);

        filename.append (suffixes[processor_type]);

        Audio::ProcessorBase *processor = 0;
        string rip_basename = filename_from_utf8 (filename);
        boost::algorithm::replace_all (rip_basename, "/" , "-");
        boost::algorithm::replace_all (rip_basename, ":" , "_");
        string rip_filename = build_filename (current_rip_basepath, rip_basename);
        ustring rip_uri = filename_to_uri (rip_filename);
        (*Data.RipIter)[cr_audio_cd.location] = rip_uri;

#ifdef HAVE_HAL
        std::string device (m_current_device);
#else
        std::string device (mcs->key_get<string>("audio", "cdrom-device"));
#endif //HAVE_HAL

        processor = Audio::ProcessorFactory::get_processor (processor_type, rip_filename, current_rip_track+1, device,
          Widgets.CBox.Quality->get_active_row_number());

        processor->signal_position().connect
          (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::stream_position));
        processor->signal_eos().connect
          (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::stream_eos));
        processor->signal_error().connect
          (sigc::mem_fun (*this, &Bmp::UiPart::CDDA::stream_error));

        set_eos (false);
        (*Data.RipIter)[cr_audio_cd.state] = ES_RIPPING;
        processor->run ();

        while (!get_eos())
        {
          while (gtk_events_pending()) gtk_main_iteration ();
          if( Data.RipLock.trylock() )
          {
            processor->stop ();
            delete processor;

            Widgets.Display.View->get_column (5)->set_visible (false);
            Widgets.Display.View->get_column (6)->set_visible (false);

            for (TreeNodeChildren::iterator iter  = Data.Store->children().begin()  ;
                                            iter != Data.Store->children().end()    ; ++iter)
            {
              (*iter)[cr_audio_cd.state] = ES_UNPROCESSED;
              (*iter)[cr_audio_cd.percentage] = 0; 
            }

            Widgets.Buttons.Rip->set_sensitive (true);
            Widgets.Buttons.Stop->set_sensitive (false);
#ifndef HAVE_HAL
            Widgets.Buttons.Reload->set_sensitive (true);
#endif //!HAVE_HAL

            Widgets.Display.View->get_selection()->set_mode (SELECTION_SINGLE);

            set_processing_cd (false);
            Data.RipLock.unlock ();
            return;
          }
        }

        delete processor;

        (*Data.RipIter)[cr_audio_cd.state] = ES_TAGGING;

        Track local_track;
        imbue_track ((*Data.RipIter)[cr_audio_cd.track], local_track);
        imbue_album (Data.MBRelease, local_track);

        local_track.location = rip_uri;

        try {
            Library::Obj()->metadata_set_taglib (local_track);
            (*Data.RipIter)[cr_audio_cd.percentage] = 100;
            (*Data.RipIter)[cr_audio_cd.state] = ES_DONE;
          }
        catch (Bmp::MetadataWriteError & cxe)
          {
            g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "Unable to modify metadata for file %s, Error: %s", rip_filename.c_str(), cxe.what());
            (*Data.RipIter)[cr_audio_cd.percentage] = 0;
            (*Data.RipIter)[cr_audio_cd.state] = ES_ERROR;
            g_unlink (rip_filename.c_str());
          }
      }

      Widgets.Display.View->get_selection()->set_mode (SELECTION_SINGLE);
      Widgets.Buttons.Rip->set_sensitive (true);
      Widgets.Buttons.Stop->set_sensitive (false);

      set_processing_cd (false);
      Data.RipLock.unlock ();

      Widgets.Display.View->get_column (5)->set_visible (false);
      Widgets.Display.View->get_column (6)->set_visible (false);

      VUri uri_list;
      bool warn = false;
      for (TreeNodeChildren::iterator iter  = Data.Store->children().begin();
                                      iter != Data.Store->children().end(); ++iter)
      {
        if( (*iter)[cr_audio_cd.state] == ES_DONE )
        {
          uri_list.push_back ((*iter)[cr_audio_cd.location]);
        }
        else
        {
          warn = true;
        }
      }

      ustring message = _("You can now insert the new album into the Library if you wish to.");

      if( warn )
      {
        message += _("\n<b>NOTE:</b> Not all files were ripped successfully, the import will be incomplete!");
      }

      TaskDialog dialog ( Widgets.Display.View->get_toplevel(),
                          _("Audio CD Ripping - BMP"),
                          _("Ripping Complete."),
                          MESSAGE_INFO, message);

      dialog.add_button ( _("Import Album"),
                          _("This will import the album into your Music Library"),
                          Stock::APPLY,
                          GTK_RESPONSE_OK);

      dialog.add_button ( _("Don't Import"),
                          _("Do not import the Album at this time"),
                          Stock::CANCEL,
                          GTK_RESPONSE_CANCEL);

      dialog.set_default_response (GTK_RESPONSE_OK);

      int response = dialog.run ();
      dialog.hide ();

      if( response == GTK_RESPONSE_OK )
      {
        if( !Data.MBRelease.releaseASIN.empty() ) 
        {
          try{
            Amazon::Covers::Obj()->cache (Data.MBRelease.releaseASIN);
            }
          catch (...) {}
        }
        Signals.RequestImport.emit (uri_list);
      }
    }

    void
    CDDA::stream_eos ()
    {
      set_eos (true);
    }

    void
    CDDA::stream_error (ustring error)
    {
      set_eos (true);
      MessageDialog dialog (error, true, MESSAGE_ERROR, BUTTONS_OK, true);
      dialog.run ();
    }

    void
    CDDA::stream_position (int position)
    {
      unsigned int duration = (*Data.RipIter)[cr_audio_cd.duration];
      int percentage = (int(((1.*position)/(1.*duration)) * 100));
      if( !duration || !percentage )
        {
          (*Data.RipIter)[cr_audio_cd.percentage] = 0;
        }
      else
        {
          (*Data.RipIter)[cr_audio_cd.percentage] = percentage;
        }

      while (gtk_events_pending()) gtk_main_iteration ();

    }

    void
    CDDA::cell_data_func_state (CellRenderer* cell, TreeIter const& iter)
    {
      const char *states[] =
      {
        N_("Unprocessed"),
        N_("Waiting"),
        N_("Encoding"),
        N_("Tagging"),
        N_("Done"),
        N_("Error!")
      };
      CellRendererText *cell_text = dynamic_cast<CellRendererText *>(cell);
      cell_text->property_text() = _(states[int ((*iter)[cr_audio_cd.state])]);
    }

    void
    CDDA::send_title ()
    {
      TreeIter const& iter = Data.CurrentIter.get();

      if( !m_anonymous_cd )
      {
        MusicBrainzTrack const& track = (*iter)[cr_audio_cd.track];

        TrackMetadata metadata;

        //FIXME: Use MusicBrainzXml imbue_track() ?
        metadata.mb_track_id                = track.trackId;
        metadata.artist                     = track.artistName;
        metadata.mb_artist_id               = track.artistId;
        metadata.mb_artist_sort_name        = track.artistSortName;
        metadata.mb_album_artist            = Data.MBRelease.mArtist.artistName;
        metadata.mb_album_artist_sort_name  = Data.MBRelease.mArtist.artistSortName;
        metadata.mb_album_artist_id         = Data.MBRelease.mArtist.artistId;
        metadata.title                      = track.trackTitle;
        metadata.album                      = Data.MBRelease.releaseTitle;
        metadata.duration                   = track.trackDuration/1000;
        metadata.asin                       = Data.MBRelease.releaseASIN;
        metadata.tracknumber                = track.trackTrackNumber;
        metadata.genre                      = _("Audio CD"); 

        if( !Data.MBRelease.releaseASIN.empty() )
        {
          Amazon::Covers::Obj()->fetch (Data.MBRelease.releaseASIN, metadata.image);
          metadata.asin = Data.MBRelease.releaseASIN;
        }

        mSignalMetadata.emit (metadata);
      }
      else
      {
          ustring const& title = (*iter)[cr_audio_cd.title];

          TrackMetadata metadata;
          metadata.artist    = ustring (_(unknown_artist));
          metadata.album     = ustring (_(unknown_album));
          metadata.title     = title;
          metadata.duration  = int ((*iter)[cr_audio_cd.duration]/1000);

          mSignalMetadata.emit (metadata);
      }
    }

    void
    CDDA::assign_current_iter (TreeIter const& iter)
    {
      if( Data.CurrentIter )
      {
        TreeIter iter = Data.CurrentIter.get();
        TreeModel::Path path (iter);
        Data.CurrentIter.reset ();
        Widgets.Display.View->get_model()->row_changed (path, iter);
      }

      Data.CurrentIter = iter;
      TreeModel::Path path1, path2, path3 (Data.CurrentIter.get());
      if( Widgets.Display.View->get_visible_range (path1, path2) )
      {
        if( (path3 < path1) || (path3 > path2) )
        {
          Widgets.Display.View->scroll_to_row (path3, 0.5);
        }
      }
      Widgets.Display.View->queue_draw ();
    }

    // Bmp::PlaybackSource
    void
    CDDA::cell_data_func_playing (CellRenderer* cell_, TreeIter const& iter)
    {
      CellRendererPixbuf *cell = dynamic_cast<CellRendererPixbuf *>(cell_);
      if( Data.CurrentIter && (Data.CurrentIter.get() == iter) )
      {
        cell->property_pixbuf() = Widgets.Display.View->render_icon (Gtk::StockID (BMP_STOCK_PLAY), Gtk::ICON_SIZE_MENU);
      }
      else
      {
        cell->property_pixbuf() = RefPtr<Gdk::Pixbuf>(0);
      }
    }

    ustring
    CDDA::get_uri ()
    {
      return ustring ((cdda_uri_f % (TreePath (Data.CurrentIter.get()).get_indices().data()[0] + 1)).str());
    }

    bool
    CDDA::go_next ()
    {
      TreeModel::Path path (Data.CurrentIter.get());
      TreeModel::Path path_old (path);
      TreeIter iter_old = Widgets.Display.View->get_model()->get_iter (path);

      TreePath::value_type size = Widgets.Display.View->get_model()->children().size()-1;
      TreePath::value_type position = path.get_indices().data()[0];

      go_next_loop:

      if( (position + 1) > size )
      {
        if( mcs->key_get <bool> ("bmp","repeat") )
        {
          position = -1;
        }
        else
        {
          return false;
        }
      }

      path = TreeModel::Path (TreePath::size_type (1), TreePath::value_type (position+1));
      TreeIter const& iter = Widgets.Display.View->get_model()->get_iter (path);
      if( !(*iter)[cr_audio_cd.audio] )
        {
          position++;
          goto go_next_loop;
        }

      assign_current_iter (iter);

      Widgets.Display.View->get_model()->row_changed (path_old, iter_old);
      Widgets.Display.View->get_model()->row_changed (path, iter);

      if( (position < size) || mcs->key_get <bool> ("bmp", "repeat") )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_NEXT);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);

      if( position > 0 )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_PREV);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_PREV);

      mSignalCaps.emit (m_caps);
      send_title ();
      return true;
    }

    bool
    CDDA::go_prev ()
    {
      TreeModel::Path path (Data.CurrentIter.get());

      TreeModel::Path path_old (path);
      TreeIter iter_old = Widgets.Display.View->get_model()->get_iter (path);

      unsigned int size = Widgets.Display.View->get_model()->children().size()-1;
      unsigned int position = path.get_indices().data()[0];

      go_prev_loop:

      if ((position - 1) < 0) return false;

      path = TreeModel::Path (1, position-1);
      TreeIter const& iter = Widgets.Display.View->get_model()->get_iter (path);
      if( !(*iter)[cr_audio_cd.audio] )
        {
          position--;
          goto go_prev_loop;
        }

      assign_current_iter (iter);

      Widgets.Display.View->get_model()->row_changed (path_old, iter_old);
      Widgets.Display.View->get_model()->row_changed (path, iter);

      if( (position < size) || mcs->key_get <bool> ("bmp", "repeat") )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_NEXT);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);

      if( position > 0 )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_PREV);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_PREV);

      mSignalCaps.emit (m_caps);
      send_title ();
      return true;
    }

    void
    CDDA::stop ()
    {
      if( Data.CurrentIter )
      {
        TreeIter iter = Data.CurrentIter.get();
        TreeModel::Path path (iter);
        Data.CurrentIter.reset ();
        Widgets.Display.View->get_model()->row_changed (path, iter);
      }

      if ( !Widgets.Display.View->get_selection()->count_selected_rows())
      {
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_PLAY);
      }

      m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_PREV);
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PAUSE);
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PROVIDE_METADATA);

      mSignalCaps.emit (m_caps);

      Widgets.Buttons.Rip->set_sensitive (true);
      Widgets.CBox.Format->set_sensitive (true);
      Widgets.CBox.Quality->set_sensitive (true);
    }

    bool
    CDDA::play ()
    {
      if( !Data.CurrentIter )
      {
        if( Widgets.Display.View->get_selection()->count_selected_rows() )
        {
          assign_current_iter (Widgets.Display.View->get_selection()->get_selected());
          return true;
        }
        else
        {
          assign_current_iter (Data.Store->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (0))));
          return true;
        }
      }

 
      TreeModel::Path path (Data.CurrentIter.get());

      Widgets.Buttons.Rip->set_sensitive (false);
      Widgets.CBox.Format->set_sensitive (false);
      Widgets.CBox.Quality->set_sensitive (false);

      Widgets.Display.View->get_column (5)->set_visible (false);
      Widgets.Display.View->get_column (6)->set_visible (false);

      unsigned int position = path.get_indices().data()[0];
      unsigned int size     = Widgets.Display.View->get_model()->children().size()-1;

      if( (position < size) || mcs->key_get <bool> ("bmp", "repeat") )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_NEXT);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);

      if( position > 0 )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_PREV);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_PREV);

      m_caps = Caps (m_caps | PlaybackSource::CAN_PAUSE);

      if (!m_anonymous_cd) m_caps = Caps (m_caps | PlaybackSource::CAN_PROVIDE_METADATA);

      send_title ();

      return true;
    }

    void
    CDDA::play_post ()
    {
      TreeModel::Path path (Data.CurrentIter.get());
      Widgets.Buttons.Rip->set_sensitive (false);
      Widgets.CBox.Format->set_sensitive (false);
      Widgets.CBox.Quality->set_sensitive (false);

      Widgets.Display.View->get_column (5)->set_visible (false);
      Widgets.Display.View->get_column (6)->set_visible (false);

      unsigned int position = path.get_indices().data()[0];
      unsigned int size     = Widgets.Display.View->get_model()->children().size()-1;

      if( (position < size) || mcs->key_get <bool> ("bmp", "repeat") )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_NEXT);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);

      if( position > 0 )
        m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_PREV);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_PREV);


      if( !m_anonymous_cd )
      {
        m_caps = Caps (m_caps | PlaybackSource::CAN_PROVIDE_METADATA);
      }
      m_caps = Caps (m_caps | PlaybackSource::CAN_PAUSE);

      send_title ();
    }

    void
    CDDA::restore_context ()
    {
    }

    void
    CDDA::selection_changed ()
    {
      if( Widgets.Display.View->get_selection ()->count_selected_rows() == 1 )
      {
        TreeIter const& iter = Widgets.Display.View->get_selection()->get_selected ();
        if( (*iter)[cr_audio_cd.audio] )
        {
          m_caps = Caps (m_caps |  PlaybackSource::CAN_PLAY);
          mSignalCaps.emit (m_caps);
          return;
        }
      }

      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PLAY);
      mSignalCaps.emit (m_caps);
    }

    bool
    CDDA::get_processing_cd ()
    {
      m_check_lock_1.lock ();
      bool processing = m_processing_cd;
      m_check_lock_1.unlock ();
      return processing;
    }

    void
    CDDA::set_processing_cd (bool set)
    {
      m_check_lock_1.lock ();
      m_processing_cd = set;
      m_check_lock_1.unlock ();
    }

    bool
    CDDA::get_eos ()
    {
      m_check_lock_2.lock ();
      bool eos = m_stream_eos;
      m_check_lock_2.unlock ();
      return eos;
    }

    void
    CDDA::set_eos (bool set)
    {
      m_check_lock_2.lock ();
      m_stream_eos = set;
      m_check_lock_2.unlock ();
    }

  } // namespace UiPart
} // namespace Bmp
