/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  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 "format.h"

#include "dialogs.h"
#include "formatdialog.h"

#include "partwidget.h"
#include "data.h"
#include "actionmanager.h"

#include "sonik_sigproc.h"

#include <kaction.h>
#include <klocale.h>
#include <knuminput.h>
#include <kgenericfactory.h>
#include <kdebug.h>

using Sonik::FormatPlugin;

namespace
{
  class ChannelProcessor : public Sonik::Edit::Processor
  {
  public:
    ChannelProcessor(const Sonik::GainMatrix& gain)
      : mGain(gain)
    {
    }

    virtual bool prepare(uint8_t channels, std::size_t /*len*/,
                         uint32_t /*sampleRate*/, std::size_t blocksize)
    {
      mWorkSeg.reset(channels, blocksize);

      return true;
    }

    virtual void apply(Sonik::SampleSegment& seg)
    {
      assert(seg.rows() == mWorkSeg.rows());
      assert(seg.size() <= mWorkSeg.capacity());

      mWorkSeg.resize(seg.size());

      for (uint8_t co = 0; co < mGain.outChannels(); ++co)
      {
        Sonik::fill(mWorkSeg.data(co), mWorkSeg.size());

        for (uint8_t ci = 0; ci < mGain.inChannels(); ++ci)
        {
          for (uint32_t i = 0; i < seg.size(); ++i)
          {
            mWorkSeg[co][i] += mGain[ci][co] * seg[ci][i];
          }
        }
      }

      Sonik::SampleSegment::swap(seg, mWorkSeg);
    }

    virtual void cleanup()
    {
    }

  private:
    const Sonik::GainMatrix& mGain;
    Sonik::SampleSegment     mWorkSeg;
  };
}

FormatPlugin::FormatPlugin(QObject* parent, const char* name,
                             const QStringList& args)
  : Sonik::Edit("format", i18n("Format"), parent, name, args)
{
  new KAction(i18n("Format"), 0,
              this, SLOT(uiEditFormat()),
              actionCollection(),
              "format_format");

  mEditChannelsMono   = new KAction(i18n("Convert to Mono"), 0,
                                    this, SLOT(uiEditChannelsMono()),
                                    actionCollection(),
                                    "format_channels_mono");
  mEditChannelsStereo = new KAction(i18n("Convert to Stereo"), 0,
                                    this, SLOT(uiEditChannelsStereo()),
                                    actionCollection(),
                                    "format_channels_stereo");
  new KAction(i18n("Insert"), 0,
              this, SLOT(uiEditChannelsInsert()),
              actionCollection(),
              "format_channels_insert");

  new KAction(i18n("Remove"), 0,
              this, SLOT(uiEditChannelsRemove()),
              actionCollection(),
              "format_channels_remove");
}

FormatPlugin::~FormatPlugin()
{
}

void FormatPlugin::init(Data* data,
                        PartWidget* widget,
                        ActionManager* actionManager)
{
  Sonik::Edit::init(data, widget, actionManager);

  connect(data, SIGNAL(channelsChanged(uint8_t)),
          this, SLOT(dataChannelsChanged(uint8_t)));
}

QWidget* FormatPlugin::makeConfigPage(QWidget*)
{
  return 0;
}

void FormatPlugin::applyConfigPage()
{
}

void FormatPlugin::uiEditFormat()
{
  Sonik::FormatDialog formatDlg(mData->format(), mWidget, "format_dialog");

  if (formatDlg.exec() == QDialog::Accepted)
  {
    enum { None, Format, Channels } updateAction = None;
    Sonik::Format newFormat = formatDlg.format();
    Sonik::GainMatrix channelMap;

    if (newFormat.channels < mData->channels())
    {
      if (mData->channels() == 2 && newFormat.channels == 1)
      {
        StereoToMonoDialog stereoToMonoDlg(mWidget, "channel_dlg");
        if (stereoToMonoDlg.exec() == QDialog::Accepted)
        {
          updateAction = Channels;
          channelMap = stereoToMonoDlg.gain();
        }
      }
      else
      {
        RemoveChannelsDialog removeDlg(mData->channels(), mWidget, "channel_dlg");
        removeDlg.channelCount->setValue(mData->channels() - newFormat.channels);
        if (removeDlg.exec() == QDialog::Accepted)
        {
          updateAction = Channels;
          channelMap = removeDlg.gain();
        }
      }
    }
    else if (newFormat.channels > mData->channels())
    {
      if (mData->channels() == 1 && newFormat.channels == 2)
      {
        MonoToStereoDialog monoToStereoDlg(mWidget, "channel_dlg");
        if (monoToStereoDlg.exec() == QDialog::Accepted)
        {
          updateAction = Channels;
          channelMap = monoToStereoDlg.gain();
        }
      }
      else
      {
        InsertChannelsDialog insertDlg(mData->channels(), mWidget, "channel_dlg");
        insertDlg.channelCount->setValue(newFormat.channels - mData->channels());
        if (insertDlg.exec() == QDialog::Accepted)
        {
          updateAction = Channels;
          channelMap = insertDlg.gain();
        }
      }
    }
    else
      updateAction = Format;

    // TODO: Only record action if something changed
    mActionManager->beginCompoundAction(i18n("Set Format"));

    if (updateAction == Channels)
    {
      changeChannels(channelMap, QString::null);
      newFormat.channels = channelMap.outChannels();
    }

    if (updateAction != None)
      mData->setFormat(newFormat);

    mActionManager->endCompoundAction();
  }
}

void FormatPlugin::uiEditChannelsMono()
{
  if (mData->channels() == 2)
  {
    StereoToMonoDialog dlg(mWidget, "channel_dlg");

    if (dlg.exec() == QDialog::Accepted)
    {
      changeChannels(dlg.gain(), i18n("Convert to Mono"));
    }
  }
}

void FormatPlugin::uiEditChannelsStereo()
{
  if (mData->channels() == 1)
  {
    MonoToStereoDialog dlg(mWidget, "channel_dlg");

    if (dlg.exec() == QDialog::Accepted)
    {
      changeChannels(dlg.gain(), i18n("Convert to Stereo"));
    }
  }
}

void FormatPlugin::uiEditChannelsInsert()
{
  InsertChannelsDialog dlg(mData->channels(), mWidget, "channel_dlg");

  if (dlg.exec() == QDialog::Accepted)
  {
    changeChannels(dlg.gain(), i18n("Insert Channels"));
  }
}

void FormatPlugin::uiEditChannelsRemove()
{
  RemoveChannelsDialog dlg(mData->channels(), mWidget, "channel_dlg");

  if (dlg.exec() == QDialog::Accepted)
  {
    changeChannels(dlg.gain(), i18n("Remove Channels"));
  }
}

void FormatPlugin::dataChannelsChanged(uint8_t channels)
{
  if (channels == 1)
    stateChanged("mono");
  else if (channels == 2)
    stateChanged("stereo");
  else
    stateChanged("other");
}

void FormatPlugin::changeChannels(const GainMatrix& gain, const QString &label)
{
  mActionManager->beginCompoundAction(label);

  // increase channels
  if (gain.outChannels() > mData->channels())
  {
    mData->addChannels(mData->channels(), gain.outChannels() - mData->channels());
  }

  ChannelProcessor p(gain);
  apply(p, false);

  // decrease channels
  if (gain.outChannels() < mData->channels())
  {
    mData->removeChannels(gain.outChannels(), mData->channels() - gain.outChannels());
  }

  mActionManager->endCompoundAction();
}

//
// Factory definition
//
K_EXPORT_COMPONENT_FACTORY(libsonik_editformat,
                           KGenericFactory<FormatPlugin>(
                             "sonikpart-edit-format")
                           );
