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

#include "audioiomanager.h"
#include "data.h"

#include <arts/stdsynthmodule.h>
#include <klocale.h>
#include <kgenericfactory.h>
#include <kdebug.h>

#include <qlabel.h>

#include <math.h>

using Sonik::ArtsPlugin;
using Sonik::ArtsPluginOptionsPage;

namespace Sonik
{
  const int kPacketSize = 4096;

  class ArtsPluginProducer_impl : virtual public ArtsPluginProducer_skel,
                                  virtual public Arts::StdSynthModule
  {
  public:
    ArtsPluginProducer_impl(AudioIOManager& manager,
                            uint8_t  channels,
                            uint32_t sampleRate,
                            int packetCount,
                            int packetCapacity);
    ~ArtsPluginProducer_impl();

    Arts::AutoSuspendState autoSuspend() { return Arts::asSuspendStop; }

    long        channels()     { return mChannels; }
    long        samplingRate() { return mSampleRate; }
    long        bits()         { return ArtsPlugin::kBits; }
    std::string title()        { return "soniK"; }

    void streamStart() { outdata.setPull(mPacketCount, mPacketCapacity); }
    void streamEnd()   { outdata.endPull(); }
    void request_outdata(Arts::DataPacket<Arts::mcopbyte>* packet);

  private:
    AudioIOManager& mManager;

    long int mChannels;
    long int mSampleRate;

    int  mPacketCount;
    int  mPacketCapacity;
    size_t mFramesPerBlock;

    Sample16Buffer mBuf;
  };

  class ArtsPluginReceiver_impl : virtual public ArtsPluginReceiver_skel,
                                  virtual public Arts::StdSynthModule
  {
  public:
    ArtsPluginReceiver_impl(AudioIOManager& manager,
                            uint8_t  channels,
                            uint32_t sampleRate,
                            size_t   blockSize);
    ~ArtsPluginReceiver_impl();

    Arts::AutoSuspendState autoSuspend() { return Arts::asSuspendStop; }

    long        channels()     { return mChannels; }
    long        samplingRate() { return mSampleRate; }
    long        bits()         { return ArtsPlugin::kBits; }
    std::string title()        { return "soniK"; }

    void process_indata(Arts::DataPacket<Arts::mcopbyte> *packet);

  private:
    AudioIOManager& mManager;

    long int mChannels;
    long int mSampleRate;

    Sample16Buffer mBuf;
  };
}

using Sonik::ArtsPluginProducer_impl;
using Sonik::ArtsPluginReceiver_impl;

ArtsPluginProducer_impl::ArtsPluginProducer_impl(AudioIOManager& manager,
                                                 uint8_t  channels,
                                                 uint32_t sampleRate,
                                                 int packetCount,
                                                 int packetCapacity)
  : mManager(manager),
    mChannels(channels), mSampleRate(sampleRate),
    mPacketCount(packetCount), mPacketCapacity(packetCapacity),
    mFramesPerBlock(mPacketCapacity / ((ArtsPlugin::kBits>>3)*mChannels))
{
  mBuf.reset(mFramesPerBlock * mChannels);
}

ArtsPluginProducer_impl::~ArtsPluginProducer_impl()
{
}

void ArtsPluginProducer_impl::request_outdata(
  Arts::DataPacket<Arts::mcopbyte>* packet
)
{
  mManager.pull(mFramesPerBlock, mBuf);

  // put the samples in the packet
  Arts::mcopbyte *to = &packet->contents[0];
  const Sample16 *p = mBuf.data(), *e = mBuf.end();
  for ( ; p != e; ++p)
  {
    *to++ = *p & 0xff;
    *to++ = (*p >> 8) & 0xff;
  }

  packet->send();
}

ArtsPluginReceiver_impl::ArtsPluginReceiver_impl(AudioIOManager& manager,
                                                 uint8_t  channels,
                                                 uint32_t sampleRate,
                                                 size_t   blockSize)
  : mManager(manager),
    mChannels(channels), mSampleRate(sampleRate)
{
  mBuf.reset(blockSize);
}

ArtsPluginReceiver_impl::~ArtsPluginReceiver_impl()
{
}

void ArtsPluginReceiver_impl::process_indata(
  Arts::DataPacket<Arts::mcopbyte>* packet
)
{
  size_t packetSamples = packet->size / (ArtsPlugin::kBits>>3);

  // get the samples from the packet
  Arts::mcopbyte *src = &packet->contents[0];

  while (packetSamples)
  {
    size_t bufSz = QMIN(packetSamples, mBuf.capacity());

    mBuf.resize(bufSz);

    Sample16 *p = mBuf.data(), *e = mBuf.end();
    for ( ; p != e; ++p)
    {
      *p  = *src++;
      *p |= (*src++ << 8);
    }

    mManager.push(mBuf);

    packetSamples -= bufSz;
  }

  packet->processed();
}

ArtsPlugin::ArtsPlugin(QObject* parent, const char* name,
                       const QStringList& args)
  : Sonik::AudioIO("arts", i18n("Arts"), parent, name, args),
    mOut(ArtsPluginProducer::null()),
    mIn(ArtsPluginReceiver::null()),
    mState(kStopped)
{
}

ArtsPlugin::~ArtsPlugin()
{
}

QWidget* ArtsPlugin::makeConfigPage(QWidget* /*parent*/)
{
  return 0;
}

void ArtsPlugin::applyConfigPage()
{
}

const ArtsPlugin::ChannelCaps ArtsPlugin::channelCaps() const
{
  ArtsPlugin::ChannelCaps c;
  c.append(1);
  c.append(2);
  return c;
}

const ArtsPlugin::SampleRateCaps ArtsPlugin::sampleRateCaps() const
{
  ArtsPlugin::SampleRateCaps c;
  c.append(ArtsPlugin::kAnySampleRate);
  return c;
}

const ArtsPlugin::BitsCaps ArtsPlugin::bitsCaps() const
{
  ArtsPlugin::BitsCaps c;
  c.append(16);
  return c;
}

size_t ArtsPlugin::blockSize() const
{
  return kPacketSize / (kBits>>3);
}

Sonik::IOResult ArtsPlugin::open(AudioIOManager* manager,
                                 uint8_t  channels,
                                 uint32_t sampleRate,
                                 uint8_t  /*bits*/)
{
  close();

  mManager = manager;

  // calc num packets needed to provice minStreamBufferTime ms of data
  // T (s) = packets * packetSize / (sampleRate * channels * 2)
  int calcPackets = 1 + (int)ceil(((mServer.server().minStreamBufferTime() / 1000.0) *
                                   channels *
                                   sampleRate *
                                   2 /* bytes per sample */) /
                                  kPacketSize);
  int packets = QMAX(8, calcPackets);


  mOut = ArtsPluginProducer::_from_base(
    new ArtsPluginProducer_impl(*mManager,
                                channels, sampleRate,
                                packets, kPacketSize)
  );

  mIn = ArtsPluginReceiver::_from_base(
    new ArtsPluginReceiver_impl(*mManager,
                                channels, sampleRate,
                                blockSize())
  );

  if (mOut.isNull() || mIn.isNull())
    return Sonik::kDeviceError;


  return Sonik::kSuccess;
}

void ArtsPlugin::close()
{
  stop();

  if (!mOut.isNull())
  {
    mOut = ArtsPluginProducer::null();
  }

  if (!mIn.isNull())
  {
    mIn = ArtsPluginReceiver::null();
  }

  mManager = 0;
}

void ArtsPlugin::play()
{
  if (!mOut.isNull() && mState == kStopped)
  {
    mServer.server().attach(mOut);
    mOut.start();
    mState = kPlaying;
  }
}

void ArtsPlugin::record()
{
  if (!mIn.isNull() && mState == kStopped)
  {
    mServer.server().attachRecorder(mIn);
    mIn.start();
    mState = kRecording;
  }
}

void ArtsPlugin::stop()
{
  if (!mOut.isNull() && mState == kPlaying)
  {
    mOut.stop();
    mServer.server().detach(mOut);
  }

  if (!mIn.isNull() && mState == kRecording)
  {
    mIn.stop();
    mServer.server().detachRecorder(mIn);
  }

  mState = kStopped;
}

QWidget* ArtsPlugin::makeOptionsPage(QWidget* parent, const char *name)
{
  return new ArtsPluginOptionsPage(*this, parent, name);
}

ArtsPluginOptionsPage::ArtsPluginOptionsPage(Sonik::ArtsPlugin& p,
                                             QWidget* parent, const char *name)
  : QWidget(parent, name),
    mPlugin(p)
{
}

ArtsPluginOptionsPage::~ArtsPluginOptionsPage()
{
}

void ArtsPluginOptionsPage::okClicked()
{
}

//
// Factory definition
//
K_EXPORT_COMPONENT_FACTORY(libsonik_artsaudio,
                           KGenericFactory<ArtsPlugin>(
                             "sonikpart-audio-arts")
                           );

