/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2000  Andreas Mueller <mueller@daneb.ping.de>
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <assert.h>
#include <gtkmm.h>
#include <libgnome/gnome-i18n.h>

#include "Toc.h"
#include "SoundIF.h"
#include "AudioCDProject.h"
#include "AudioCDView.h"
#include "TocEdit.h"
#include "TocEditView.h"
#include "TocInfoDialog.h"
#include "CdTextDialog.h"
#include "guiUpdate.h"
#include "util.h"
#include "gcdmaster.h"
#include "xcdrdao.h"
#include "RecordTocDialog.h"
#include "Icons.h"
#include "MessageBox.h"

AudioCDProject::AudioCDProject(int number, const char *name, TocEdit *tocEdit,
                               Gtk::Window *parent) : Project(parent)
{
  tocInfoDialog_ = NULL;
  cdTextDialog_ = NULL;
  soundInterface_ = NULL;
  buttonPlay_ = NULL;
  buttonStop_ = NULL;
  buttonPause_ = NULL;
  audioCDView_ = NULL;

  parent_ = parent;
  pack_start(hbox_);

  projectNumber_ = number;

  playStatus_ = STOPPED;
  playBurst_ = 588 * 10;
  playBuffer_ = new Sample[playBurst_];

  if (tocEdit == NULL)
    tocEdit_ = new TocEdit(NULL, NULL);
  else
    tocEdit_ = tocEdit;
  
  // Connect TocEdit signals to us.
  tocEdit_->signalStatusMessage.
    connect(sigc::mem_fun(*this, &AudioCDProject::status));
  tocEdit_->signalProgressFraction.
    connect(sigc::mem_fun(*this, &AudioCDProject::progress));
  tocEdit_->signalFullView.
    connect(sigc::mem_fun(*this, &AudioCDProject::fullView));
  tocEdit_->signalSampleSelection.
    connect(sigc::mem_fun(*this, &AudioCDProject::sampleSelect));
  tocEdit_->signalCancelEnable.
    connect(sigc::mem_fun(*this, &AudioCDProject::cancelEnable));
  tocEdit_->signalError.
    connect(sigc::mem_fun(*this, &AudioCDProject::errorDialog));

  if (tocEdit_->isQueueActive())
    cancelEnable(true);
                                                     
  if (!name || strlen(name) == 0) {
    char buf[20];
    sprintf(buf, "unnamed-%i.toc", projectNumber_);
    tocEdit_->filename(buf);
    new_ = true;
  } else {
    new_ = false; // The project file already exists
  }
  
  audioCDView_ = new AudioCDView(this);
  hbox_.pack_start(*audioCDView_, TRUE, TRUE);
  audioCDView_->tocEditView()->sampleViewFull();

  updateWindowTitle();

  guiUpdate(UPD_ALL);
  show_all();
}

void AudioCDProject::add_menus(Glib::RefPtr<Gtk::UIManager> m_refUIManager)
{
  m_refActionGroup = Gtk::ActionGroup::create("AudioCDProject");

  m_refActionGroup->add( Gtk::Action::create("Save", Gtk::Stock::SAVE),
                         sigc::mem_fun(*this, &Project::saveProject) );

  m_refActionGroup->add( Gtk::Action::create("SaveAs", Gtk::Stock::SAVE_AS),
                         sigc::mem_fun(*this, &Project::saveAsProject) );

  m_refActionGroup->add( Gtk::Action::create("ProjectInfo", Gtk::Stock::PROPERTIES,
                         _("Project Info..."),
                         _("Edit global project data")),
                         sigc::mem_fun(*this, &AudioCDProject::projectInfo) );

  m_refActionGroup->add( Gtk::Action::create("CDTEXT", Gtk::Stock::PROPERTIES,
                         _("CD-TEXT..."),
                         _("Edit CD-TEXT data")),
                         sigc::mem_fun(*this, &AudioCDProject::cdTextDialog) );

  m_refActionGroup->add( Gtk::Action::create("Record", Icons::RECORD,
                         _("_Record"),
                         _("Record")),
                         sigc::mem_fun(*this, &AudioCDProject::recordToc2CD) );

  m_refActionGroup->add( Gtk::Action::create("Play", Icons::PLAY,
                         _("Play"),
                         _("Play")),
                         sigc::mem_fun(*this, &AudioCDProject::on_play_clicked) );

  m_refActionGroup->add( Gtk::Action::create("Stop", Icons::STOP,
                         _("Stop"),
                         _("Stop")),
                         sigc::mem_fun(*this, &AudioCDProject::on_stop_clicked) );

  m_refActionGroup->add( Gtk::Action::create("Pause", Icons::PAUSE,
                         _("Pause"),
                         _("Pause")),
                         sigc::mem_fun(*this, &AudioCDProject::on_pause_clicked) );

  //Add Toggle Actions:
  Gtk::RadioAction::Group group_colors;
  m_refActionGroup->add( Gtk::RadioAction::create(group_colors, "Select",
                         Gtk::Stock::JUMP_TO,
                         _("Select"),
                         _("Select Mode")),
                         sigc::mem_fun(*this, &AudioCDProject::on_select_clicked));
  m_refActionGroup->add( Gtk::RadioAction::create(group_colors, "Zoom",
                         Gtk::Stock::ZOOM_FIT,
                         _("Zoom"),
                         _("Zoom Mode")),
                         sigc::mem_fun(*this, &AudioCDProject::on_zoom_clicked));

  m_refActionGroup->add( Gtk::Action::create("ZoomIn", Gtk::Stock::ZOOM_IN,
                         _("Zoom In"),
                         _("Zoom In")),
                         sigc::mem_fun(*this, &AudioCDProject::on_zoom_in_clicked) );

  m_refActionGroup->add( Gtk::Action::create("ZoomOut", Gtk::Stock::ZOOM_OUT,
                         _("Zoom Out"),
                         _("Zoom Out")),
                         sigc::mem_fun(*this, &AudioCDProject::on_zoom_out_clicked) );

  m_refActionGroup->add( Gtk::Action::create("ZoomFit", Gtk::Stock::ZOOM_FIT,
                         _("Zoom Fit"),
                         _("Zoom Fit")),
                         sigc::mem_fun(*this, &AudioCDProject::on_zoom_fit_clicked) );

  m_refUIManager->insert_action_group(m_refActionGroup);

  // Merge menuitems
  try
  {
    Glib::ustring ui_info =
        "<ui>"
        "  <menubar name='MenuBar'>"
        "    <menu action='FileMenu'>"
        "      <placeholder name='FileSaveHolder'>"
        "      <menuitem action='Save'/>"
        "      <menuitem action='SaveAs'/>"
        "      </placeholder>"
        "    </menu>"
        "    <menu action='EditMenu'>"
        "      <menuitem action='ProjectInfo'/>"
        "      <menuitem action='CDTEXT'/>"
        "    </menu>"
        "    <menu action='ActionsMenu'>"
        "      <placeholder name='ActionsRecordHolder'>"
        "        <menuitem action='Record'/>"
        "      </placeholder>"
        "      <menuitem action='Play'/>"
        "      <menuitem action='Stop'/>"
        "      <menuitem action='Pause'/>"
        "      <separator/>"
        "      <menuitem action='Select'/>"
        "      <menuitem action='Zoom'/>"
        "      <separator/>"
        "      <menuitem action='ZoomIn'/>"
        "      <menuitem action='ZoomOut'/>"
        "      <menuitem action='ZoomFit'/>"
        "    </menu>"
        "  </menubar>"
        "  <toolbar name='ToolBar'>"
        "    <toolitem action='Save'/>"
        "    <toolitem action='Record'/>"
        "    <separator/>"
        "    <toolitem action='Play'/>"
        "    <toolitem action='Stop'/>"
        "    <toolitem action='Pause'/>"
        "    <separator/>"
        "    <toolitem action='Select'/>"
        "    <toolitem action='Zoom'/>"
        "    <separator/>"
        "    <toolitem action='ZoomIn'/>"
        "    <toolitem action='ZoomOut'/>"
        "    <toolitem action='ZoomFit'/>"
        "    <separator/>"
        "  </toolbar>"
        "</ui>";

    m_refUIManager->add_ui_from_string(ui_info);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << "merging menus failed: " <<  ex.what();
  }

  Glib::RefPtr<Gtk::Action> action;
  action = m_refActionGroup->get_action ("Play");
  action->set_sensitive(true);
  action = m_refActionGroup->get_action ("Pause");
  action->set_sensitive(false);
  action = m_refActionGroup->get_action ("Stop");
  action->set_sensitive(false);

  audioCDView_->add_menus (m_refUIManager);
  audioCDView_->signal_tocModified.
    connect(sigc::mem_fun(*this, &AudioCDProject::update));
}

void AudioCDProject::configureAppBar(Gnome::UI::AppBar *s, Gtk::ProgressBar* p,
                                     Gtk::Button *b)
{
  statusbar_ = s;
  progressbar_ = p;
  progressButton_ = b;

  if (tocEdit_) {
    tocEdit_->signalProgressPulse.
      connect(sigc::mem_fun(*progressbar_, &Gtk::ProgressBar::pulse));
    signalCancelClicked.connect(sigc::mem_fun(*tocEdit_,
                                              &TocEdit::queueAbort));
    if (tocEdit_->isQueueActive())
      progressButton_->set_sensitive(true);
  }

  progressButton_->signal_clicked().
      connect(sigc::mem_fun(*this, &AudioCDProject::on_cancel_clicked));
  progressbar_->set_pulse_step(0.01);
};

void AudioCDProject::status(const char* msg)
{
  statusMessage(msg);
}

void AudioCDProject::errorDialog(const char* msg)
{
  Gtk::MessageDialog md(*(getParentWindow()), msg, false, Gtk::MESSAGE_ERROR);
  md.run();
}

void AudioCDProject::progress(double val)
{
  progressbar_->set_fraction(val);
}

void AudioCDProject::fullView()
{
  if (audioCDView_)
    audioCDView_->fullView();
}

void AudioCDProject::sampleSelect(unsigned long start, unsigned long len)
{
  audioCDView_->tocEditView()->sampleSelect(start, len);
}

void AudioCDProject::cancelEnable(bool enable)
{
  if (progressButton_)
    progressButton_->set_sensitive(enable);
}

bool AudioCDProject::closeProject()
{
  if (tocEdit_->tocDirty()) {

    // Project window might be iconified and user might have forgotten
    // about it (Quit can be called on another project window).
    getParentWindow()->present();

    Glib::ustring message = "Project ";
    message += tocEdit_->filename();
    message += " not saved. Are you sure you want to close it ?";

    Gtk::MessageDialog d(*getParentWindow(), message, false,
                         Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true);

    int ret = d.run();
    d.hide();

    if (ret == Gtk::RESPONSE_CANCEL || ret == Gtk::RESPONSE_DELETE_EVENT)
      return false;
  }

  if (tocEdit_ && tocEdit_->isQueueActive()) {
    tocEdit_->queueAbort();
  }

  if (playStatus_ == PLAYING || playStatus_ == PAUSED) {
    playStop();
  }

  if (audioCDView_) {
    delete audioCDView_;
    audioCDView_ = NULL;
  }

  if (tocInfoDialog_)   delete tocInfoDialog_;
  if (cdTextDialog_)    delete cdTextDialog_;
  if (recordTocDialog_) delete recordTocDialog_;

  return true;
}

void AudioCDProject::recordToc2CD()
{
  if (recordTocDialog_ == NULL)
    recordTocDialog_ = new RecordTocDialog(tocEdit_);

  recordTocDialog_->start (parent_);
}

void AudioCDProject::projectInfo()
{
  if (!tocInfoDialog_)
    tocInfoDialog_ = new TocInfoDialog(parent_);

  tocInfoDialog_->start(tocEdit_);
}

void AudioCDProject::cdTextDialog()
{
  if (cdTextDialog_ == 0)
    cdTextDialog_ = new CdTextDialog();

  cdTextDialog_->start(tocEdit_);
}

void AudioCDProject::update(unsigned long level)
{
  //FIXME: Here we should update the menus and the icons
  //       this is, enabled/disabled.

  level |= tocEdit_->updateLevel();

  if (level & (UPD_TOC_DIRTY | UPD_TOC_DATA))
    updateWindowTitle();

  audioCDView_->update(level);

  if (tocInfoDialog_)
    tocInfoDialog_->update(level, tocEdit_);

  if (cdTextDialog_ != 0)
    cdTextDialog_->update(level, tocEdit_);
  if (recordTocDialog_ != 0)
    recordTocDialog_->update(level);

  if (level & UPD_PLAY_STATUS) {
    bool sensitivity[3][3] = {
      //PLAY  PAUSE STOP 
      { false, true, true }, // Playing
      { true, true, true },  // Paused
      { true, false, false } // Stopped
    };

    Glib::RefPtr<Gtk::Action> action;
    action = m_refActionGroup->get_action ("Play");
    action->set_sensitive(sensitivity[playStatus_][0]);
    action = m_refActionGroup->get_action ("Pause");
    action->set_sensitive(sensitivity[playStatus_][1]);
    action = m_refActionGroup->get_action ("Stop");
    action->set_sensitive(sensitivity[playStatus_][2]);
  }

  if (level & UPD_EDITABLE_STATE) {
    bool editable = tocEdit_->editable();
    Glib::RefPtr<Gtk::Action> action;
    action = m_refActionGroup->get_action ("Play");
    action->set_sensitive(editable);
  }
}

void AudioCDProject::playStart()
{
  unsigned long start, end;
 
  // If we're in paused mode, resume playing.
  if (playStatus_ == PAUSED) {
    playStatus_ = PLAYING;
    Glib::signal_idle().connect(sigc::mem_fun(*this,
                                              &AudioCDProject::playCallback));
    return;
  } else if (playStatus_ == PLAYING) {
    return;
  }

  if (audioCDView_ && audioCDView_->tocEditView()) {
    if (!audioCDView_->tocEditView()->sampleSelection(&start, &end))
      audioCDView_->tocEditView()->sampleView(&start, &end);

    playStart(start, end);
  }
}

void AudioCDProject::playStart(unsigned long start, unsigned long end)
{
  unsigned long level = 0;

  if (playStatus_ == PLAYING)
    return;

  if (tocEdit_->lengthSample() == 0) {
    guiUpdate(UPD_PLAY_STATUS);
    return;
  }

  if (soundInterface_ == NULL) {
    soundInterface_ = new SoundIF;
    if (soundInterface_->init() != 0) {
      delete soundInterface_;
      soundInterface_ = NULL;
      guiUpdate(UPD_PLAY_STATUS);
      statusMessage(_("WARNING: Cannot open \"/dev/dsp\""));
      return;
    }
  }

  if (soundInterface_->start() != 0) {
    statusMessage(_("WARNING: Cannot open sound device"));
    guiUpdate(UPD_PLAY_STATUS);
    return;
  }

  tocReader.init(tocEdit_->toc());
  if (tocReader.openData() != 0) {
    tocReader.init(NULL);
    soundInterface_->end();
    guiUpdate(UPD_PLAY_STATUS);
    return;
  }

  if (tocReader.seekSample(start) != 0) {
    tocReader.init(NULL);
    soundInterface_->end();
    guiUpdate(UPD_PLAY_STATUS);
    return;
  }

  playLength_ = end - start + 1;
  playPosition_ = start;
  playStatus_ = PLAYING;
  playAbort_ = false;

  level |= UPD_PLAY_STATUS;

  //FIXME: Selection / Zooming does not depend
  //       on the Child, but the View.
  //       we should have different blocks!
  tocEdit_->blockEdit();

  guiUpdate(level);

  Glib::signal_idle().connect(sigc::mem_fun(*this,
                                            &AudioCDProject::playCallback));
}

void AudioCDProject::playPause()
{
  if (playStatus_ == PAUSED) {
    playStatus_ = PLAYING;
    Glib::signal_idle().connect(sigc::mem_fun(*this,
                                              &AudioCDProject::playCallback));
  } else if (playStatus_ == PLAYING) {
    playStatus_ = PAUSED;
  }
}

void AudioCDProject::playStop()
{
  if (playStatus() == PAUSED) {
    soundInterface_->end();
    tocReader.init(NULL);
    playStatus_ = STOPPED;
    tocEdit_->unblockEdit();
    playStatus_ = STOPPED;
    guiUpdate(UPD_PLAY_STATUS|UPD_EDITABLE_STATE);
  } else {
    playAbort_ = true;
  }
}

bool AudioCDProject::playCallback()
{
  unsigned long level = 0;

  long len = playLength_ > playBurst_ ? playBurst_ : playLength_;

  if (playStatus_ == PAUSED)
    {
      level |= UPD_PLAY_STATUS;
      guiUpdate(level);
      return false; // remove idle handler
    }

  if (tocReader.readSamples(playBuffer_, len) != len ||
      soundInterface_->play(playBuffer_, len) != 0) {
    soundInterface_->end();
    tocReader.init(NULL);
    playStatus_ = STOPPED;
    level |= UPD_PLAY_STATUS;
    tocEdit_->unblockEdit();
    guiUpdate(level);
    return false; // remove idle handler
  }

  playLength_ -= len;
  playPosition_ += len;

  unsigned long delay = soundInterface_->getDelay();

  if (delay <= playPosition_)
    level |= UPD_PLAY_STATUS;

  if (len == 0 || playAbort_) {
    soundInterface_->end();
    tocReader.init(NULL);
    playStatus_ = STOPPED;
    level |= UPD_PLAY_STATUS | UPD_EDITABLE_STATE;
    tocEdit_->unblockEdit();
    guiUpdate(level);
    return false; // remove idle handler
  }
  else {
    guiUpdate(level);
    return true; // keep idle handler
  }
}

unsigned long AudioCDProject::playPosition()
{
  return playPosition_;
}

unsigned long AudioCDProject::getDelay()
{
  return soundInterface_->getDelay();
}

void AudioCDProject::on_play_clicked()
{
  playStart();
}

void AudioCDProject::on_stop_clicked()
{
  playStop();
}

void AudioCDProject::on_pause_clicked()
{
  playPause();
}

void AudioCDProject::on_zoom_in_clicked()
{
  if (audioCDView_)
    audioCDView_->zoomx2();
}

void AudioCDProject::on_zoom_out_clicked()
{
  if (audioCDView_)
    audioCDView_->zoomOut();
}

void AudioCDProject::on_zoom_fit_clicked()
{
  if (audioCDView_)
    audioCDView_->fullView();
}

void AudioCDProject::on_zoom_clicked()
{
  if (audioCDView_)
    audioCDView_->setMode(AudioCDView::ZOOM);
}

void AudioCDProject::on_select_clicked()
{
  if (audioCDView_)
    audioCDView_->setMode(AudioCDView::SELECT);
}

void AudioCDProject::on_cancel_clicked()
{
    signalCancelClicked();
}

bool AudioCDProject::appendTrack(const char* file)
{
  FileExtension type = fileExtension(file);

  switch (type) {

  case FE_M3U: {
    std::list<std::string> list;
    if (parseM3u(file, list)) {
      std::list<std::string>::iterator i = list.begin();
      for (; i != list.end(); i++) {
        tocEdit()->queueAppendTrack((*i).c_str());
      }
    } else {
      std::string msg = "Could not read M3U file \"";
      msg += file;
      msg += "\"";
      errorDialog(msg.c_str());
      return false;
    }
    break;
  }

  default:
    tocEdit()->queueAppendTrack(file);
  }

  return true;
}

bool AudioCDProject::appendTracks(std::list<std::string>& files)
{
  std::list<std::string>::iterator i = files.begin();
  for (; i != files.end(); i++) {
    tocEdit()->queueAppendTrack((*i).c_str());
  }
  return true;
}

bool AudioCDProject::appendFiles(std::list<std::string>& files)
{
  std::list<std::string>::iterator i = files.begin();
  for (; i != files.end(); i++) {
    tocEdit()->queueAppendFile((*i).c_str());
  }
  return true;
}

bool AudioCDProject::insertFiles(std::list<std::string>& files)
{
  unsigned long pos;

  TocEditView* view = audioCDView_->tocEditView();
  if (!view) return false;
  if (!view->sampleMarker(&pos)) pos = 0;

  std::list<std::string>::iterator i = files.end();
  do {
    i--;
    tocEdit()->queueInsertFile((*i).c_str(), pos);
  } while (i != files.begin());

  return true;
}
