/*
 *
 *    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 "audioiomanager.h"
#include "data.h"
#include "audioio.h"

#include "sonik_util.h"
#include "sonik_sigproc.h"

#include <kdebug.h>

#include <qtextstream.h>
#include <qtimer.h>

//#define DUMP_AUDIO

#ifdef DUMP_AUDIO
#include <qdatetime.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int dumpFd = 0;
#endif

namespace Sonik
{
  struct AudioIOManager::Private
  {
    Private(Data& data)
      : mData(data),
        mDevice(0),
        mLooping(false),
        mRangeStart(0),
        mRangeLength(mData.length()),
        mPos(0),
        mState(kIdle)
    {
    }

    Data&              mData;
    AudioIO*           mDevice;

    ChannelSeq         mChannels;
    bool               mLooping;
    off_t              mRangeStart;
    size_t             mRangeLength;
    off_t              mPos;
    AudioIOManager::State mState;

    SampleBuffer       mInterleaveBuf;

    template <typename S>
    void pull(size_t length, auto_buffer<S>& data);

    template <typename S>
    void push(auto_buffer<S>& data);
  };
}

using Sonik::AudioIOManager;
using Sonik::Data;
using Sonik::AudioIO;

template <typename S>
void AudioIOManager::Private::pull(std::size_t length,
                                   auto_buffer<S>& data)
{
  assert(data.capacity() >= length * mChannels.size());
  data.resize(length * mChannels.size());

  if (mState == kPlaying)
  {
    auto_buffer<S> tmp;
    S* dest = data.data();

    while (length > 0)
    {
      size_t len = length;
      if (mPos + len > mRangeStart + mRangeLength)
        len = mRangeStart + mRangeLength - mPos;

      tmp.reset(dest, len * mChannels.size());

      ChannelSeq::const_iterator it = mChannels.begin();
      ChannelSeq::const_iterator  e = mChannels.end();
      for ( ; it != e; ++it)
      {
        mData.data(*it, mPos, len, mInterleaveBuf);
        Sonik::interleave(mInterleaveBuf, tmp, mChannels.size(), *it);
      }

      tmp.release();

      dest += len * mChannels.size();
      mPos += len;
      length -= len;

      if (mPos == (off_t)(mRangeStart + mRangeLength))
      {
        if (mLooping)
          mPos = mRangeStart;
        else
          break;
      }
    }

    if (length > 0)
    {
      // Pad with zeroes & stop
      tmp.reset(dest, length * mChannels.size());
      Sonik::fill(tmp, Sonik::to<S>(0));
      tmp.release();

      mState = kIdle;
    }
  }
  else
  {
    Sonik::fill(data, Sonik::to<S>(0));
  }

#ifdef DUMP_AUDIO
  if (dumpFd)
    write(dumpFd, data.data(), data.size() * sizeof(S));
#endif
}


template <typename S>
void AudioIOManager::Private::push(auto_buffer<S>& data)
{
  if (mState == kRecording)
  {
    if (data.size() == 0)
      return;

    size_t frames = data.size()/mChannels.size();

    mData.insert(mPos, frames);

    ChannelSeq::const_iterator it = mChannels.begin();
    ChannelSeq::const_iterator  e = mChannels.end();
    for ( ; it != e; ++it)
    {
      Sonik::deinterleave(data, mInterleaveBuf, mChannels.size(), *it);
      mData.setData(*it, mPos, mInterleaveBuf);
    }

    mPos += frames;
  }
}

AudioIOManager::AudioIOManager(Data& data)
  : d(new AudioIOManager::Private(data))
{
}

AudioIOManager::~AudioIOManager()
{
  delete d;
}

Sonik::IOResult AudioIOManager::open(AudioIO* device)
{
  if (!device)
    return kDeviceError;

  if (d->mDevice)
  {
    d->mDevice->close();
    d->mDevice = 0;
  }

  // Find the best match between the data and the device's caps
  AudioIO::ChannelCaps channelCaps = device->channelCaps();
  AudioIO::ChannelCaps::const_iterator it = channelCaps.begin();
  AudioIO::ChannelCaps::const_iterator  e = channelCaps.end();
  for ( ; it != e; ++it)
    if (*it >= d->mData.channels())
      break;
  if (it == channelCaps.end())
    return kDeviceFormat;
  // TODO: warning about channels not matching
  // TODO: data ch < device ch
  uint8_t channels = *it;
  // TODO: channel mappings - for now 1:1
  d->mChannels.resize(channels);
  for (uint8_t i = 0; i < channels; ++i)
    d->mChannels[i] = i;

  // TODO: match sample rate & bits
  uint32_t sampleRate = d->mData.sampleRate();
  uint8_t  bits = d->mData.bits();

  d->mDevice = device;

  IOResult res = d->mDevice->open(this, channels, sampleRate, bits);
  if (res != kSuccess)
    return res;

  d->mInterleaveBuf.reset(d->mDevice->blockSize() / channels);

  return res;
}

void AudioIOManager::close()
{
  d->mInterleaveBuf.reset();

  if (!d->mDevice)
    return;

  d->mDevice->close();
  d->mDevice = 0;
}

void AudioIOManager::setRange(off_t start, size_t length)
{
  assert(start >= 0);
  assert(start + length <= d->mData.length());

  d->mRangeStart  = start;
  d->mRangeLength = length;
  seek(d->mPos);
}

off_t AudioIOManager::rangeStart() const
{
  return d->mRangeStart;
}

size_t AudioIOManager::rangeLength() const
{
  return d->mRangeLength;
}

AudioIOManager::State AudioIOManager::state() const
{
  return d->mState;
}

void AudioIOManager::play(bool looping)
{
  if (!d->mDevice)
    return;

  if (d->mRangeLength == 0)
    return;

#ifdef DUMP_AUDIO
  QString fn = "/tmp/sonik-" + QDateTime::currentDateTime().toString("yyMMdd-hhmmss") + ".raw";
  dumpFd = ::open(fn.latin1(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  kdDebug(60606) << "AudioIOManager::play: "
                 << dumpFd << ", " << errno
                 << "\n";
#endif

  d->mLooping = looping;
  d->mDevice->play();
  d->mState = kPlaying;

  emit playing();
}

void AudioIOManager::record()
{
  if (!d->mDevice)
    return;

  d->mDevice->record();
  d->mState = kRecording;

  emit recording();
}

void AudioIOManager::pause()
{
  if (!d->mDevice)
    return;

  if (d->mState == kPaused)
  {
    d->mDevice->play();
    d->mState = kPlaying;
    emit playing();
  }
  else if (d->mState == kPlaying)
  {
    d->mDevice->stop();
    d->mState = kPaused;
    emit paused();
  }
}

void AudioIOManager::stop()
{
  if (!d->mDevice)
    return;

  d->mDevice->stop();
  emit position(d->mPos);

  d->mLooping = false;
  d->mPos = d->mRangeStart;
  d->mState = kIdle;

#ifdef DUMP_AUDIO
  ::close(dumpFd);
  dumpFd = 0;
#endif

  emit stopped();
}

off_t AudioIOManager::position() const
{
  return d->mPos;
}

void AudioIOManager::seek(off_t pos)
{
  // bound position to range (including end)
  d->mPos = Sonik::bound(pos, d->mRangeStart, (off_t)d->mRangeLength+1);

  emit position(d->mPos);
}

void AudioIOManager::skip(off_t delta)
{
  seek(d->mPos + delta);
}

void AudioIOManager::pull(size_t length, SampleBuffer& data)
{
  d->pull<Sample>(length, data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::pull(size_t length, Sample8Buffer& data)
{
  d->pull<Sample8>(length, data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::pull(size_t length, Sample16Buffer& data)
{
  d->pull<Sample16>(length, data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::pull(size_t length, Sample24Buffer& data)
{
  d->pull<Sample24>(length, data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::push(SampleBuffer& data)
{
  d->push<Sample>(data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::push(Sample8Buffer& data)
{
  d->push<Sample8>(data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::push(Sample16Buffer& data)
{
  d->push<Sample16>(data);
  QTimer::singleShot(0, this, SLOT(update()));
}

void AudioIOManager::push(Sample24Buffer& data)
{
  d->push<Sample24>(data);
  QTimer::singleShot(0, this, SLOT(update()));
}

Data& AudioIOManager::data()
{
  return d->mData;
}

void AudioIOManager::update()
{
  if (d->mState == kIdle)
  {
    stop();
    close();
  }
  else
    emit position(d->mPos);
}
