/*
 *
 *    Sonik digital audio editor
 *    Copyright (C) 2003-2005  Robert Walker <rob@tenfoot.org.uk>
 *
 *    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "sonik.h"
#include "startdlgimpl.h"
#include "appsettings.h"
#include "appconfigpage.h"

#include "part.h"

#include <dcopclient.h>
#include <klibloader.h>
#include <kapplication.h>
#include <kstdaction.h>
#include <kmessagebox.h>
#include <kconfig.h>
#include <klistbox.h>
#include <kkeydialog.h>
#include <kedittoolbar.h>
#include <kfiledialog.h>
#include <klocale.h>
#include <kstatusbar.h>
#include <kconfigdialog.h>

#include <kdebug.h>

#include <qcheckbox.h>

using Sonik::SonikApp;

SonikApp::SonikApp()
  : DCOPObject("SonikApp"),
    KParts::MainWindow(0L, "SonikApp")
{
  // set the shell's ui resource file
  setXMLFile("sonikui.rc");

  // then, setup our actions
  setupActions();

  // read this here as closing dialogs later will casue a write
  readConfig(kapp->config());

  // this routine will find and load our Part.  it finds the Part by
  // name which is a bad idea usually.. but it's alright in this
  // case since our Part is made for this Shell
  KLibFactory *factory = KLibLoader::self()->factory("libsonikpart");
  if (factory)
  {
    // now that the Part is loaded, we cast it to a Part to get
    // our hands on it
    mPart = static_cast<Sonik::Part*>(factory->create(this,
                                                      "sonikpart",
                                                      "Sonik::Part"));
    if (mPart)
    {
      // tell the KParts::MainWindow that this is indeed the main widget
      setCentralWidget(mPart->widget());

      // and integrate the part's GUI with the shell's
      createGUI(mPart);
    }
    else
    {
      KMessageBox::error(this, i18n("Error loading SonikPart"));
      exit(-1);
      return;
    }
  }
  else
  {
    // if we couldn't find our Part, we exit since the Shell by
    // itself can't do anything useful
    KMessageBox::error(this, i18n("Could not find SonikPart"));
    exit(-1);
    return;
  }

  //
  // connect to part
  //
  connect(mPart, SIGNAL(channelsChanged(uint8_t)),
          this, SLOT(statusChannelsChanged(uint8_t)));
  connect(mPart, SIGNAL(lengthChanged(size_t)),
          this, SLOT(statusLengthChanged(size_t)));
  connect(mPart, SIGNAL(sampleRateChanged(uint32_t)),
          this, SLOT(statusSampleRateChanged(uint32_t)));
  connect(mPart, SIGNAL(bitsChanged(uint8_t)),
          this, SLOT(statusBitsChanged(uint8_t)));
  connect(mPart, SIGNAL(cursorPosChanged(off_t)),
          this, SLOT(statusCursorPosChanged(off_t)));
  connect(mPart, SIGNAL(displaySelectionChanged(off_t, size_t)),
          this, SLOT(statusSelectionChanged(off_t, size_t)));
  connect(mPart, SIGNAL(playing()),
          this, SLOT(playing()));
  connect(mPart, SIGNAL(stopped()),
          this, SLOT(stopped()));
  connect(mPart, SIGNAL(timeFormatChanged(Sonik::TimeFormat)),
          this, SLOT(statusTimeFormatChanged(Sonik::TimeFormat)));
  connect(mPart, SIGNAL(urlChanged(KURL&)),
          this, SLOT(urlChanged(KURL&)));

  // status bar
  setupStatusbar();
  statusBar()->show();

  setAutoSaveSettings();  // window sizes etc
}

SonikApp::~SonikApp()
{
}

void SonikApp::load(const KURL& url)
{
  mPart->openURL(url);
  mFileOpenRecent->addURL(url);
}

void SonikApp::setupActions()
{
  // File menu
  mFileNew = KStdAction::openNew(this, SLOT(fileNew()), actionCollection());
  mFileOpen = KStdAction::open(this, SLOT(fileOpen()), actionCollection());
  mFileOpenRecent = KStdAction::openRecent(this,
                                            SLOT(fileOpenRecent(const KURL&)),
                                            actionCollection());
  mFileClose = KStdAction::close(this, SLOT(close()), actionCollection());
  mFileQuit = KStdAction::quit(kapp, SLOT(closeAllWindows()), actionCollection());


  mFileNew->setToolTip(i18n("Creates a new document"));
  mFileOpenRecent->setToolTip(i18n("Opens a recently used file"));
  mFileClose->setToolTip(i18n("Closes the actual document"));
  mFileQuit->setToolTip(i18n("Quits the application"));

  // Settings menu
  mSettingsToolbarAction =
    KStdAction::showToolbar(this, SLOT(optionsShowToolbar()),
                            actionCollection());
  mSettingsStatusbarAction =
    KStdAction::showStatusbar(this, SLOT(optionsShowStatusbar()),
                              actionCollection());
  KStdAction::preferences(this, SLOT(optionsConfigure()),
                          actionCollection());
  KStdAction::keyBindings(this, SLOT(optionsConfigureKeys()),
                          actionCollection());
  KStdAction::configureToolbars(this, SLOT(optionsConfigureToolbars()),
                                actionCollection());
  mSettingsToolbarAction->setToolTip(i18n("Enables/disables the toolbar"));
  mSettingsStatusbarAction->setToolTip(i18n("Enables/disables the statusbar"));
}

void SonikApp::setupStatusbar()
{
  statusBar()->insertItem(i18n("Ready."), kStatusMessage);
  statusBar()->setItemAlignment(kStatusMessage, AlignLeft);

  statusBar()->insertFixedItem("", kStateCursorPos, true);
  statusBar()->insertFixedItem("", kStateSelection, true);
  statusBar()->insertFixedItem("", kStateSampleRate, true);
  statusBar()->insertFixedItem("", kStateBits, true);

  statusBar()->setItemAlignment(kStateCursorPos, AlignLeft);
  statusBar()->setItemAlignment(kStateSelection, AlignLeft);
  statusBar()->setItemAlignment(kStateLength, AlignLeft);
  statusBar()->setItemAlignment(kStateSampleRate, AlignLeft);
  statusBar()->setItemAlignment(kStateBits, AlignLeft);

  // default to largest size (+ a bit more)
  QFontMetrics fm = statusBar()->fontMetrics();
  statusBar()->setItemFixed(kStateCursorPos, fm.boundingRect("_88:88:88.888").width());
  statusBar()->setItemFixed(kStateSelection, fm.boundingRect("_88:88:88.888 - 88:88:88.888 (88:88:88.888)").width());
  statusBar()->setItemFixed(kStateSampleRate, fm.boundingRect("_888888Hz").width());
  statusBar()->setItemFixed(kStateBits, fm.boundingRect("_88 bit").width());
}

void SonikApp::statusChannelsChanged(uint8_t)
{
}

void SonikApp::statusLengthChanged(size_t length)
{
  QString lenStr = Sonik::toString(length,
                                   mPart->data().sampleRate(),
                                   mPart->timeFormat());
  statusBar()->changeItem(lenStr, kStateLength);
}

void SonikApp::statusSampleRateChanged(uint32_t sampleRate)
{
  statusBar()->changeItem(QString("%1Hz").arg(sampleRate), kStateSampleRate);
}

void SonikApp::statusBitsChanged(uint8_t bits)
{
  statusBar()->changeItem(QString("%1 bit").arg(bits), kStateBits);
}

void SonikApp::statusCursorPosChanged(off_t pos)
{
  QString posStr = Sonik::toString(pos,
                                   mPart->data().sampleRate(),
                                   mPart->timeFormat());
  statusBar()->changeItem(posStr, kStateCursorPos);
}

void SonikApp::statusSelectionChanged(off_t start, size_t length)
{
  if (length > 0)
  {
    QString startStr = Sonik::toString(start,
                                       mPart->data().sampleRate(),
                                       mPart->timeFormat());
    QString endStr = Sonik::toString(start + length - 1,
                                     mPart->data().sampleRate(),
                                     mPart->timeFormat());
    QString lenStr = Sonik::toString(length,
                                     mPart->data().sampleRate(),
                                     mPart->timeFormat());
    statusBar()->changeItem(startStr + " - " + endStr + " (" + lenStr + ")", kStateSelection);
  }
  else
  {
    QString posStr = Sonik::toString(start,
                                     mPart->data().sampleRate(),
                                     mPart->timeFormat());
    statusBar()->changeItem(posStr, kStateSelection);
  }
}

void SonikApp::statusTimeFormatChanged(Sonik::TimeFormat)
{
  statusLengthChanged(mPart->data().length());
  statusCursorPosChanged(mPart->cursorPos());
  statusSelectionChanged(mPart->selectionStart(), mPart->selectionLength());
}

void SonikApp::playing()
{
}

void SonikApp::stopped()
{
}

void SonikApp::saveProperties(KConfig* /*config*/)
{
  ////  kdDebug(60606) << "SonikApp: saveProperties" << "\n";
  // the 'config' object points to the session managed
  // config file.  anything you write here will be available
  // later when this app is restored
}

void SonikApp::readProperties(KConfig* /*config*/)
{
  ////  kdDebug(60606) << "SonikApp: readProperties" << "\n";
  // the 'config' object points to the session managed
  // config file.  this function is automatically called whenever
  // the app is being restored.  read in here whatever you wrote
  // in 'saveProperties'
}

void SonikApp::saveConfig(KConfig* /*config*/)
{
  kdDebug(60606) << "SonikApp: saveConfig" << "\n";

  mFileOpenRecent->saveEntries(AppSettings::self()->config());
  AppSettings::writeConfig();
}

void SonikApp::readConfig(KConfig* /*config*/)
{
  kdDebug(60606) << "SonikApp: readConfig" << "\n";

  mFileOpenRecent->loadEntries(AppSettings::self()->config());
}

int SonikApp::chooseStartupAction(QString& file)
{
  int action = -1;
  StartDlgImpl dlg(this, "StartDlg", true);

  dlg.recentList->insertStringList(mFileOpenRecent->items());
  dlg.recentList->setCurrentItem(0);

  StartDlgImpl::Action dlgAction;
  switch (AppSettings::lastStartAction())
  {
    case AppSettings::EnumLastStartAction::RecordNew:
      dlgAction = StartDlgImpl::RecordNew;
      break;
    case AppSettings::EnumLastStartAction::EditNew:
      dlgAction = StartDlgImpl::EditNew;
      break;
    case AppSettings::EnumLastStartAction::OpenFile:
      dlgAction = StartDlgImpl::OpenFile;
      break;
    case AppSettings::EnumLastStartAction::OpenRecent:
      dlgAction = StartDlgImpl::OpenRecent;
      break;
  }
  dlg.setAction(dlgAction);

  if (dlg.exec())
  {
    switch (dlg.action())
    {
      case StartDlgImpl::RecordNew:
        action = AppSettings::EnumLastStartAction::RecordNew;
        break;

      case StartDlgImpl::EditNew:
        action = AppSettings::EnumLastStartAction::EditNew;
        break;

      case StartDlgImpl::OpenFile:
        action = AppSettings::EnumLastStartAction::OpenFile;
        break;

      case StartDlgImpl::OpenRecent:
        action = AppSettings::EnumLastStartAction::OpenRecent;
        file = dlg.recentList->currentText();
        break;
    }
    AppSettings::setLastStartAction(action);

    if (dlg.dontShow->isChecked())
      AppSettings::setShowStartAction(false);
  }

  return action;
}

void SonikApp::startupAction()
{
  // default to edit new
  int action = AppSettings::EnumLastStartAction::EditNew;
  QString file;

  if (AppSettings::showStartAction())
    action = chooseStartupAction(file);

  switch (action)
  {
    case AppSettings::EnumLastStartAction::RecordNew:
      if (initNewFile())
        mPart->uiPlayRecord();
      break;

    case AppSettings::EnumLastStartAction::OpenFile:
      fileOpen();
      break;

    case AppSettings::EnumLastStartAction::OpenRecent:
      load(file);
      break;

    case AppSettings::EnumLastStartAction::EditNew:
    default:
      initNewFile();
      break;
  }
}

void SonikApp::loadSettings()
{
}

void SonikApp::fileNew()
{
  // create a new window
  SonikApp *sonik = new SonikApp;
  sonik->show();
  if (!sonik->initNewFile())
    // close if cancelled
    sonik->close();
}

void SonikApp::fileOpen()
{
  KURL url = KFileDialog::getOpenURL(QString::null, mPart->fileFilter(false),
                                     this, i18n("Open File..."));

  if (!url.isEmpty())
  {
    if (!mPart->url().isEmpty() || mPart->isModified())
    {
      SonikApp *sonik = new SonikApp;
      sonik->show();
      sonik->load(url);
      mFileOpenRecent->addURL(url);
    }
    else
      load(url);
  }
}

void SonikApp::fileOpenRecent(const KURL& url)
{
  if (!url.isEmpty())
  {
    if (!mPart->url().isEmpty() || mPart->isModified())
    {
      SonikApp *sonik = new SonikApp;
      sonik->show();
      sonik->load(url);
    }
    else
      load(url);
  }
}

void SonikApp::optionsShowToolbar()
{
  if (mSettingsToolbarAction->isChecked())
    toolBar()->show();
  else
    toolBar()->hide();
}

void SonikApp::optionsShowStatusbar()
{
  if (mSettingsStatusbarAction->isChecked())
    statusBar()->show();
  else
    statusBar()->hide();
}

void SonikApp::optionsConfigure()
{
   if(KConfigDialog::showDialog("sonik_settings"))
     return;
   KConfigDialog *dialog = new KConfigDialog(this, "sonik_settings", AppSettings::self(), KDialogBase::IconList);
   dialog->addPage(new AppConfigPage(0, "Sonik"), i18n("Sonik"), "configure");
   connect(dialog, SIGNAL(settingsChanged()), this, SLOT(loadSettings()));
   dialog->show();
}

void SonikApp::optionsConfigureKeys()
{
  KKeyDialog dlg;
  dlg.insert(actionCollection());
  if( mPart )
    dlg.insert(mPart->actionCollection());
  dlg.configure();
}

void SonikApp::optionsConfigureToolbars()
{
  saveMainWindowSettings(KGlobal::config(), "MainWindow");

  // use the standard toolbar editor
  KEditToolbar dlg(factory());
  connect(&dlg, SIGNAL(newToolbarConfig()),
          this, SLOT(applyNewToolbarConfig()));
  dlg.exec();
}

void SonikApp::applyNewToolbarConfig()
{
  applyMainWindowSettings(KGlobal::config(), "MainWindow");
}

void SonikApp::urlChanged(KURL& url)
{
  mFileOpenRecent->addURL(url);
}

bool SonikApp::queryClose()
{
  return mPart->closeURL();
}

bool SonikApp::queryExit()
{
  saveConfig(kapp->config());
  return true;
}

QSize SonikApp::sizeHint() const
{
  return QSize(800, 600);
}

// DCOP functions

void SonikApp::openURL(const QString& /*name*/)
{
}

void SonikApp::newFile()
{
}

bool SonikApp::initNewFile()
{
  bool formatAccepted = mPart->formatDialog(this);

  // set initial status.
  statusLengthChanged(mPart->data().length());
  statusBitsChanged(mPart->data().bits());
  statusSampleRateChanged(mPart->data().sampleRate());

  if (formatAccepted)
    mPart->initialized();

  return formatAccepted;
}
