/*
 *
 *    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 "basicgenerator.h"

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

#include "editapplydialog.h"
#include "controls/slider.h"
#include "insertpositonwidget.h"

#include "waveoptions.h"

#include "sonik_util.h"

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

#include <qvbox.h>
#include <qcombobox.h>

#include <math.h>

using Sonik::BasicGeneratorPlugin;

namespace Sonik
{
  class SimpleGenerator : public Edit::Processor
  {
  public:
    enum Wave { Sine, Square, Saw };

    SimpleGenerator(Wave wave, float frequency, float dutyCycle, float amplitude)
      : mWave(wave),
        mFrequency(frequency),
        mDutyCycle(dutyCycle),
        mAmplitude(amplitude)
    {
    }

    virtual bool prepare(uint8_t, std::size_t /*len*/, uint32_t sampleRate, std::size_t)
    {
      // TODO: user choosable phase
      mT = 0;
      mW = mFrequency / sampleRate;

      return true;
    }

    virtual void apply(SampleSegment& seg)
    {
      for (uint8_t channel = 0; channel < seg.rows(); ++channel)
      {
        switch (mWave)
        {
          case Sine:   generateSine(seg, channel);               break;
          case Square: generateSquare(seg, channel, mDutyCycle); break;
          case Saw:    generateSaw(seg, channel, mDutyCycle);    break;
          default:                                               break;
        }
      }
      mT += seg.size();
    }

  private:
    void generateSine(SampleSegment& seg, uint8_t channel)
    {
      Sample *p = seg.data(channel), *e = seg.end(channel);
      for (off_t t = mT; p != e; ++p, ++t)
      {
        // TODO: sine can be done as 2 adds (matched filter)
        *p = mAmplitude * sin(2.0 * M_PI * mW * t);
      }
    }

    void generateSquare(SampleSegment& seg, uint8_t channel, float dutyCycle)
    {
      Sample *p = seg.data(channel), *e = seg.end(channel);
      for (off_t t = mT; p != e; ++p, ++t)
      {
        *p = fmod(mW * t, 1.0f) < dutyCycle ? mAmplitude : -mAmplitude;
      }
    }

    void generateSaw(SampleSegment& seg, uint8_t channel, float dutyCycle)
    {
      Sample *p = seg.data(channel), *e = seg.end(channel);
      for (off_t t = mT; p != e; ++p, ++t)
      {
        float x = fmod(mW * t, 1.0f);
        float y = x < dutyCycle ? (x / dutyCycle) : (1.0f - (x - dutyCycle) / (1.0f - dutyCycle));
        *p = (2 * y - 1.0f) * mAmplitude;
      }
    }

    Wave  mWave;
    float mFrequency;
    float mDutyCycle;
    float mAmplitude;

    off_t mT;
    float mW;
  };
}

BasicGeneratorPlugin::BasicGeneratorPlugin(QObject* parent, const char* name,
                             const QStringList& args)
  : Sonik::Edit("basicGenerator", i18n("Basic Generator"), parent, name, args)
{
  new KAction(i18n("Silence"), 0,
              this, SLOT(uiGenerateSilence()),
              actionCollection(),
              "generate_silence");
  new KAction(i18n("Simple"), 0,
              this, SLOT(uiGenerateSimple()),
              actionCollection(),
              "generate_simple");
}

BasicGeneratorPlugin::~BasicGeneratorPlugin()
{
}

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

void BasicGeneratorPlugin::applyConfigPage()
{
}

void BasicGeneratorPlugin::uiGenerateSilence()
{
  PluginContext* context = new PluginContext;
  QVBox* layout = new QVBox;

  InsertPositonWidget* pos = new InsertPositonWidget(layout, "position");
  pos->setDefaults(mData->sampleRate(), mWidget->selectionLength());
  context->controls.push_back(new UiControl("pos", i18n("Position"), pos, "position"));
  context->controls.push_back(new UiControl("length", i18n("Length"), pos, "length"));
  connect(mWidget, SIGNAL(selectionChanged(off_t, size_t)),
          pos, SLOT(selectionChanged(off_t, size_t)));

  pluginDialog("generate_silence", layout, i18n("Insert silence"),
               SLOT(applyGenerateSilence(Sonik::Edit::PluginContext*)),
               context);
}

void BasicGeneratorPlugin::applyGenerateSilence(Sonik::Edit::PluginContext* context)
{
  InsertPositonWidget* pos =
    static_cast<InsertPositonWidget*>(context->controls.front()->widget());

  Sonik::Edit::Processor p;
  mActionManager->beginCompoundAction(i18n("Insert silence"));
  generate(p, pos->position(), pos->length());
  mActionManager->endCompoundAction();
}

void BasicGeneratorPlugin::uiGenerateSimple()
{
  PluginContext* context = new PluginContext;
  QVBox* layout = new QVBox;

  InsertPositonWidget* pos = new InsertPositonWidget(layout, "position");
  pos->setDefaults(mData->sampleRate(), mWidget->selectionLength());
  context->controls.push_back(new UiControl("pos", i18n("Position"), pos, "position"));
  context->controls.push_back(new UiControl("length", i18n("Length"), pos, "length"));
  connect(mWidget, SIGNAL(selectionChanged(off_t, size_t)),
          pos, SLOT(selectionChanged(off_t, size_t)));

  WaveOptions* waveOpts = new WaveOptions(mData->sampleRate() / 2.0,
                                          layout, "wave_opts");
  context->controls.push_back(new UiControl("freq", i18n("Frequency"), waveOpts, "freq"));
  context->controls.push_back(new UiControl("dutyCycle", i18n("Duty Cycle"), waveOpts, "dutyCycle"));
  context->controls.push_back(new UiControl("amplitude", i18n("Amplitude"), waveOpts, "amplitude"));
  context->controls.push_back(new UiControl("wave", i18n("Wave"), waveOpts, "wave"));

  pluginDialog("generate_simple", layout, i18n("Generate simple waveform"),
               SLOT(applyGenerateSimple(Sonik::Edit::PluginContext*)),
               context);

  // TODO: update position widget with selection changes
}

void BasicGeneratorPlugin::applyGenerateSimple(Sonik::Edit::PluginContext*context)
{
  InsertPositonWidget* pos =
    static_cast<InsertPositonWidget*>(context->controls.front()->widget());
  WaveOptions* waveOpts =
    static_cast<WaveOptions*>(context->controls[2]->widget());

  SimpleGenerator p(static_cast<SimpleGenerator::Wave>(
                      waveOpts->wave()
                      ),
                    waveOpts->freq(),
                    waveOpts->dutyCycle(),
                    waveOpts->amplitude());
  mActionManager->beginCompoundAction(i18n("Insert simple waveform"));
  generate(p, pos->position(), pos->length());
  mActionManager->endCompoundAction();
}

//
// Factory definition
//
K_EXPORT_COMPONENT_FACTORY(libsonik_editbasicgenerator,
                           KGenericFactory<BasicGeneratorPlugin>(
                             "sonikpart-edit-basicgenerator")
                           );
