/*
 *
 *    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 "data.h"
#include "actionmanager.h"
#include "fileio.h"
#include "sonik_util.h"

#include <kdebug.h>
#include <klocale.h>
#include <qvaluevector.h>
#include <assert.h>

using Sonik::Data;
using Sonik::FileIO;

using Sonik::SampleBuffer;
using Sonik::Sample8Buffer;
using Sonik::Sample16Buffer;
using Sonik::Sample24Buffer;

namespace Sonik
{
  struct Data::DataPrivate
  {
    // allocate 256k samples at a time
    static const size_t kChunkSize = 0x40000;

    struct Chunk
    {
      Chunk() : start(0), length(0) { }

      off_t start;
      size_t length;
      QValueVector<Sample*> data;
    };
    typedef QValueVector<Chunk> ChunkVector;

    DataPrivate(const uint8_t& channels, const size_t& length);
    virtual ~DataPrivate();

    inline ChunkVector::iterator findChunk(off_t pos);
    inline ChunkVector::const_iterator findChunk(off_t pos) const;

    void  clearChunks();

    void dumpChunks() const;

    template <typename S>
    inline void dataInternal(uint8_t channel, off_t start, size_t length,
                             S* data) const;

    template <typename S>
    inline void setDataInternal(uint8_t channel, off_t start, size_t length,
                                const S* data);

    template <typename S>
    inline void fillDataInternal(uint8_t channel, off_t start, size_t length,
                                 const S& val);

    /**
     * Retrieves the data for all channels, converted to the appropriate
     * format.
     */
    template <typename S>
    inline void data(off_t start, size_t length, auto_buffer_2d<S>& data) const;

    /**
     * Retrieves the data from the given channel, converted to the appropriate
     * format.
     */
    template <typename S>
    inline void data(uint8_t channel, off_t start, size_t length,
                     auto_buffer<S>& data) const;

    /**
     * Sets the data in all channel.
     * <i>data</i> is converted to the correct format if necessary.
     * <i>mData</i> is extended if necessary.
     */
    template <typename S>
    inline void setData(off_t start,
                        const auto_buffer_2d<S>& data);

    /**
     * Sets the data in a channel.
     * <i>data</i> is converted to the correct format if necessary.
     * <i>mData</i> is extended if necessary.
     */
    template <typename S>
    inline void setData(uint8_t channel, off_t start,
                        const auto_buffer<S>& data);

    template <typename S>
    inline void fillData(off_t start, size_t length,
                         const S& val);

    template <typename S>
    inline void fillData(uint8_t channel, off_t start, size_t length,
                         const S& val);

    static Chunk allocateChunk(off_t start, size_t length, uint8_t channels);
    static void  freeChunk(Chunk& ck);

    ChunkVector    mData;
    const uint8_t& mChannels;
    const size_t&  mLength;
  };

  class ChannelsRemoveAction : public Action
  {
  public:
    ChannelsRemoveAction(Data& data, uint8_t start, uint8_t num,
                         const QString& name)
      : Action(name), mData(data), mStart(start), mNum(num) { }
    virtual void apply() { mData.removeChannels(mStart, mNum); }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Channels remove: %1 %2").arg(mStart).arg(mNum);
    }

  private:
    Data&   mData;
    uint8_t mStart;
    uint8_t mNum;
  };

  class ChannelsAddAction : public Action
  {
  public:
    ChannelsAddAction(Data& data, uint8_t start, SampleSegment& samples,
                      const QString& name)
      : Action(name), mData(data), mStart(start), mSamples(samples) { }
    virtual void apply()
    {
      assert(mData.length() == mSamples.size());

      mData.addChannels(mStart, mSamples.rows());
      for (size_t i = 0; i < mSamples.rows(); ++i)
      {
        SampleBuffer buf(mSamples.data(i), mSamples.size());
        mData.setData(mStart + i, 0, buf);
        buf.release();
      }
    }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Channels add: %1 %2").arg(mStart).arg(mSamples.rows());
    }

  private:
    Data&         mData;
    uint8_t       mStart;
    SampleSegment mSamples;
  };

  class DataRemoveAction : public Action
  {
  public:
    DataRemoveAction(Data& data, off_t start, size_t length,
                     const QString& name)
      : Action(name), mData(data), mStart(start), mLength(length) { }
    virtual void apply() { mData.remove(mStart, mLength); }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Data remove %1 %2").arg(mStart).arg(mLength);
    }

  private:
    Data&  mData;
    off_t  mStart;
    size_t mLength;
  };

  class DataInsertAction : public Action
  {
  public:
    DataInsertAction(Data& data, off_t start, SampleSegment& samples,
                     const QString& name)
      : Action(name), mData(data), mStart(start), mSamples(samples) { }
    virtual void apply()
    {
      assert(mData.channels() == mSamples.rows());
      mData.insert(mStart, mSamples.size());
      mData.setData(mStart, mSamples);
    }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Data insert %1 %2").arg(mStart).arg(mSamples.rows());
    }

  private:
    Data&         mData;
    off_t         mStart;
    SampleSegment mSamples;
  };

  class DataSetAction : public Action
  {
  public:
    DataSetAction(Data& data, uint8_t channel, off_t start, SampleBuffer& samples,
                  const QString& name)
      : Action(name),
        mData(data), mChannel(channel), mStart(start),
        mSamples(samples) { }
    virtual void apply()
    {
      mData.setData(mChannel, mStart, mSamples);
    }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Data set %1 %2 %3").arg(mChannel).arg(mStart).arg(mSamples.size());
    }

  private:
    Data&        mData;
    uint8_t      mChannel;
    off_t        mStart;
    SampleBuffer mSamples;
  };

  class SetSampleRateAction : public Action
  {
  public:
    SetSampleRateAction(Data& data, uint32_t sampleRate, const QString& name)
      : Action(name), mData(data), mSampleRate(sampleRate) { }
    virtual void apply() { mData.setSampleRate(mSampleRate); }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Sample rate set %1").arg(mSampleRate);
    }

  private:
    Data&    mData;
    uint32_t mSampleRate;
  };

  class SetBitsAction : public Action
  {
  public:
    SetBitsAction(Data& data, uint8_t bits, const QString& name)
      : Action(name), mData(data), mBits(bits) { }
    virtual void apply() { mData.setBits(mBits); }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Bits set %1").arg(mBits);
    }

  private:
    Data&   mData;
    uint8_t mBits;
  };

}

/*
 * SonikCore::DataPrivate methods
 */
Data::DataPrivate::DataPrivate(const uint8_t& channels, const size_t& length)
  : mChannels(channels), mLength(length)
{
}

Data::DataPrivate::~DataPrivate()
{
  clearChunks();
}

Data::DataPrivate::ChunkVector::iterator
Data::DataPrivate::findChunk(off_t pos)
{
  // TODO: binary search
  ChunkVector::iterator it = mData.begin();
  while (it != mData.end() &&
         pos >= (off_t)(((*it).start) + ((*it).length)))
    ++it;

  return it;
}

Data::DataPrivate::ChunkVector::const_iterator
Data::DataPrivate::findChunk(off_t pos) const
{
  ChunkVector::const_iterator it = mData.begin();
  while (it != mData.end() &&
         pos >= (off_t)(((*it).start) + ((*it).length)))
    ++it;

  return it;
}

void Data::DataPrivate::clearChunks()
{
  ChunkVector::iterator itC = mData.begin(), eC = mData.end();
  for ( ; itC != eC; ++itC)
  {
    freeChunk(*itC);
  }
  mData.clear();
}

void Data::DataPrivate::dumpChunks() const
{
  ChunkVector::const_iterator it = mData.begin(), e = mData.end();
  for ( ; it != e; ++it)
  {
    kdDebug(60606) << "Data::DataPrivate::dumpChunks: "
                   << (*it).start << " + " << (*it).length
                   << "\n";
  }
}

/*
 * SonikCore::DataPrivate templated methods
 */

template <typename S>
void Data::DataPrivate::dataInternal(uint8_t channel, off_t start, size_t length,
                                     S* buf) const
{
  // Entire region is undefined
  if (start >= (off_t)mLength || (off_t)(start + length) < 0)
  {
    memset(buf, 0, length*sizeof(S));
    return;
  }

  // Zero samples for t < 0
  if (start < 0)
  {
    size_t z = -start;
    memset(buf, 0, z*sizeof(S));
    buf += z;
    length -= z;
    start = 0;
  }

  // Zero samples for t >= L
  if (start + length > mLength)
  {
    off_t s = mLength - start;
    size_t z = start + length - mLength;
    memset(buf + s, 0, z*sizeof(S));
    length = s;
  }

  // Copy samples for 0 <= t < L
  if (length == 0)
    return;

  ChunkVector::const_iterator chunk = findChunk(start);
  if (chunk != mData.end())
  {
    off_t offset = start - (*chunk).start;
    while (length)
    {
      const size_t sz = QMIN(length, (*chunk).length - offset);
      const Sample* src = (*chunk).data[channel] + offset;
      Sonik::to<S>(buf, src, sz);
      buf += sz;
      length -= sz;
      offset = 0;
      ++chunk;
    }
  }
}

template <typename S>
void Data::DataPrivate::setDataInternal(uint8_t channel, off_t start, size_t length,
                                        const S* buf)
{
  ChunkVector::const_iterator chunk = findChunk(start);
  if (chunk != mData.end())
  {
    off_t offset = start - (*chunk).start;
    while (length)
    {
      const size_t sz = QMIN(length, (*chunk).length - offset);
      Sample* dst = (*chunk).data[channel] + offset;
      Sonik::from<S>(dst, buf, sz);
      buf += sz;
      length -= sz;
      offset = 0;
      ++chunk;
    }
  }
}


template <typename S>
void Data::DataPrivate::fillDataInternal(uint8_t channel, off_t start, size_t length,
                                         const S& val)
{
  ChunkVector::const_iterator chunk = findChunk(start);
  if (chunk != mData.end())
  {
    off_t offset = start - (*chunk).start;
    while (length)
    {
      size_t cnt = QMIN(length, (*chunk).length - offset);
      Sample* dst = (*chunk).data[channel] + offset;

      length -= cnt;
      while (cnt--)
      {
        *dst++ = val;
      }
      offset = 0;
      ++chunk;
    }
  }
}

template <typename S>
void Data::DataPrivate::data(off_t start, size_t length,
                             auto_buffer_2d<S>& buf) const
{
  assert(buf.rows() >= mChannels);
  assert(buf.capacity() >= length);

  buf.resize(length);

  for (uint8_t channel = 0; channel < mChannels; ++channel)
  {
    dataInternal(channel, start, length, buf.data(channel));
  }
}

template <typename S>
void Data::DataPrivate::data(uint8_t channel, off_t start, size_t length,
                             auto_buffer<S>& buf) const
{
  assert(channel < mChannels);
  assert(buf.capacity() >= length);

  buf.resize(length);

  dataInternal(channel, start, length, buf.data());
}

template <class S>
void Data::DataPrivate::setData(off_t start,
                                const auto_buffer_2d<S>& buf)
{
  assert(start >= 0);
  assert(buf.rows() >= mChannels);
  assert(start+buf.size() <= mLength);

  for (uint8_t channel = 0; channel < mChannels; ++channel)
  {
    setDataInternal(channel, start, buf.size(), buf.data(channel));
  }
}

template <class S>
void Data::DataPrivate::setData(uint8_t channel, off_t start,
                                const auto_buffer<S>& buf)
{
  assert(channel < mChannels);
  assert(start >= 0);
  assert(start+buf.size() <= mLength);

  setDataInternal(channel, start, buf.size(), buf.data());
}

template <class S>
void Data::DataPrivate::fillData(off_t start, size_t length,
                                 const S& val)
{
  assert(start >= 0);
  assert(start+length <= mLength);

  for (uint8_t channel = 0; channel < mChannels; ++channel)
  {
    fillDataInternal(channel, start, length, Sonik::from<S>(val));
  }
}

template <class S>
void Data::DataPrivate::fillData(uint8_t channel, off_t start, size_t length,
                                 const S& val)
{
  assert(channel < mChannels);
  assert(start >= 0);
  assert(start+length <= mLength);

  fillDataInternal(channel, start, length, Sonik::from<S>(val));
}

Data::DataPrivate::Chunk Data::DataPrivate::allocateChunk(off_t start,
                                                          size_t length,
                                                          uint8_t channels)
{
  assert(length <= kChunkSize);

  Chunk ck;

  ck.start = start;
  ck.length = length;
  ck.data.resize(channels);
  for (uint8_t channel = 0; channel < channels; ++channel)
  {
    ck.data[channel] = new Sample[kChunkSize];
    memset(ck.data[channel], 0, kChunkSize*sizeof(Sample));
  }

  return ck;
}

void Data::DataPrivate::freeChunk(Chunk& ck)
{
  QValueVector<Sample*>::iterator it = ck.data.begin();
  QValueVector<Sample*>::iterator e  = ck.data.end();
  for ( ; it != e; ++it)
    delete[] (*it);
  ck.data.clear();
}

/*
 * Sonik::Data methods
 */

Data::Data(ActionManager& actionManager)
  : mActionManager(actionManager),
    mChannels(0),
    mLength(0),
    mSampleRate(48000),
    mBits(16),
    d(new Data::DataPrivate(mChannels, mLength))
{
  addChannels(0, 1);
}

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

/**
 * Adds n channels before channel
 */
void Data::addChannels(uint8_t channel, uint8_t n)
{
  assert(channel <= mChannels);

  DataPrivate::ChunkVector::iterator ck = d->mData.begin();
  for ( ; ck != d->mData.end(); ++ck)
  {
    (*ck).data.resize(mChannels+n);

    // move channels channel..end forward n
    for (uint8_t i = mChannels; i > channel; --i)
      (*ck).data[i] = (*ck).data[i-n];

    // allocate n channels
    for (uint8_t i = 0; i < n; ++i)
    {
      (*ck).data[channel+i] = new Sample[mLength];
      memset((*ck).data[channel+i], 0, mLength*sizeof(Sample));
    }
  }

  mChannels += n;

  mActionManager.recordAction(
    new ChannelsRemoveAction(*this, channel, n, i18n("Add Channels"))
    );

  emit channelsChanged(mChannels);
  for (uint8_t i = channel; i < mChannels; ++i)
    emit dataChanged(i, (off_t)0, mLength);
}

void Data::removeChannels(uint8_t channel, uint8_t n)
{
  assert(channel < mChannels);
  assert(channel+n <= mChannels);

  // TODO: relink chunks to avoid data copying
  SampleSegment undoData(n, mLength);
  for (uint8_t i = channel; i < channel+n; ++i)
  {
    SampleBuffer buf(undoData.data(i-channel), mLength);
    data(i, 0, mLength, buf);
    buf.release();
  }

  DataPrivate::ChunkVector::iterator ck = d->mData.begin();
  for ( ; ck != d->mData.end(); ++ck)
  {
    // delete n channels
    for (uint8_t i = channel; i < channel+n; ++i)
      delete[] (*ck).data[i];

    // move channel+n..end channels back n places
    for (uint8_t i = channel; i < mChannels-n; ++i)
      (*ck).data[i] = (*ck).data[i+n];

    (*ck).data.resize(mChannels-n);
  }

  mChannels -= n;

  mActionManager.recordAction(
    new ChannelsAddAction(*this, channel, undoData, i18n("Remove Channels"))
    );

  emit channelsChanged(mChannels);
  for (uint8_t i = channel; i < mChannels; ++i)
    emit dataChanged(i, (off_t)0, mLength);
}

void Data::setSampleRate(uint32_t sampleRate)
{
  mActionManager.recordAction(
    new SetSampleRateAction(*this, mSampleRate, i18n("Set Sample Rate"))
    );

  mSampleRate = sampleRate;
  emit sampleRateChanged(mSampleRate);
}

void Data::setBits(uint8_t bits)
{
  mActionManager.recordAction(
    new SetBitsAction(*this, mBits, i18n("Set Bits"))
    );

  mBits = bits;
  emit bitsChanged(mBits);
}

Sonik::Format Data::format() const
{
  return Format(mChannels, mSampleRate, mBits);
}

void Data::setFormat(const Format& format)
{
  mActionManager.beginCompoundAction(i18n("Set Format"));

  if (format.channels < mChannels)
  {
    removeChannels(format.channels, mChannels - format.channels);
  }
  else if (format.channels > mChannels)
  {
    addChannels(mChannels, format.channels - mChannels);
  }

  setSampleRate(format.sampleRate);
  setBits(format.bits);

  mActionManager.endCompoundAction();
}

void Data::insert(off_t start, size_t length)
{
  assert(start >= 0);
  assert(start <= (off_t)mLength);

  // find chunk containing start
  DataPrivate::ChunkVector::iterator ck = d->findChunk(start);

  DataPrivate::ChunkVector::iterator ckFill = d->mData.end();
  DataPrivate::ChunkVector::iterator ckInsert = d->mData.end();
  DataPrivate::ChunkVector::iterator ckUpdate = d->mData.end();

  if (ck != d->mData.end())
  {
    size_t chunkOffset = start - (*ck).start;

    assert(chunkOffset < (*ck).length);

    if (chunkOffset == 0)
    {
      // insert before current chunk

      if (ck != d->mData.begin())
      {
        // use any space at end of previous chunk
        DataPrivate::ChunkVector::iterator ckPrev = ck;
        ckFill = --ckPrev;
      }

      // allocate chunks between ck-1 and ck
      ckInsert = ck;

      // update pos for ck..end
      ckUpdate = ck;
    }
    else
    {
      // split current chunk
      size_t remainder = (*ck).length - chunkOffset;
      DataPrivate::Chunk ck2 =
        DataPrivate::allocateChunk((*ck).start + chunkOffset,
                                   remainder,
                                   mChannels);
      for (uint8_t channel = 0; channel < mChannels; ++channel)
      {
        memcpy(ck2.data[channel], (*ck).data[channel] + chunkOffset,
               sizeof(Sample)*ck2.length);
      }

      (*ck).length = chunkOffset;

      DataPrivate::ChunkVector::iterator ckIns = d->mData.insert(ck + 1, ck2);

      // use space at end of ck
      ckFill = ckIns - 1;

      // allocate chunks between ck and ck2
      ckInsert = ckIns;

      // update pos for ck2..end
      ckUpdate = ckIns;
    }
  }
  else if (d->mData.size() != 0)
  {
    // fill at end of last chunk
    ckFill = d->mData.begin() + d->mData.size() - 1;
  }


  off_t  pos = start;
  size_t sz  = length;

  // use space at end of ckFill
  if (ckFill != d->mData.end() &&
      (*ckFill).length < DataPrivate::kChunkSize)
  {
    size_t szFill = QMIN((DataPrivate::kChunkSize - (*ckFill).length), sz);

    for (uint8_t channel = 0; channel < mChannels; ++channel)
      memset((*ckFill).data[channel] + (*ckFill).length, 0, sizeof(Sample)*szFill);

    (*ckFill).length += szFill;

    sz  -= szFill;
    pos += szFill;
  }

  // update chunks after insertion
  for ( ; ckUpdate != d->mData.end(); ++ckUpdate)
  {
    (*ckUpdate).start += length;
  }

  // insert new chunks after ckInsert
  // (do it last as insert invalidates iterators)
  while (sz)
  {
    size_t szAdd = QMIN(sz, DataPrivate::kChunkSize);
    DataPrivate::Chunk ckAdd = DataPrivate::allocateChunk(pos, szAdd, mChannels);
    ckInsert = d->mData.insert(ckInsert, ckAdd) + 1;
    pos += szAdd;
    sz  -= szAdd;
  }

  mLength += length;

  mActionManager.recordAction(
    new DataRemoveAction(*this, start, length, i18n("Insert Data"))
    );

  emit lengthChanged(mLength);
  for (uint8_t channel = 0; channel < mChannels; ++channel)
    emit dataChanged(channel, start, mLength-start);
}

void Data::remove(off_t start, size_t length)
{
  // ignore data outside valid range
  if (start < 0)
  {
    if (length <= (size_t)(-start))
      // nothing to remove
      return;

    length -= -start;
    start = 0;
  }

  if ((size_t)start >= mLength)
    return;

  if (start + length > mLength)
    length = mLength - start;

  SampleSegment undoData(mChannels, length);
  data(start, length, undoData);

  // find start chunk
  DataPrivate::ChunkVector::iterator ck = d->findChunk(start);
  assert(ck != d->mData.end());

  if (start >= (*ck).start &&
      (start + length) < ((*ck).start + (*ck).length))
  {
    // deletion from one chunk only
    off_t offset = start - (*ck).start;

    // move end of chunk backwards
    for (uint8_t channel = 0; channel < mChannels; ++channel)
    {
      memmove((*ck).data[channel] + offset,
              (*ck).data[channel] + offset + length,
              sizeof(Sample) * ((*ck).length - offset - length));
    }

    (*ck).length -= length;
    ++ck;
  }
  else
  {
    size_t sz = length;

    // preserve start of chunk
    if (start > (*ck).start)
    {
      off_t offset = start - (*ck).start;
      sz -= ((*ck).length - offset);
      (*ck).length = offset;
      ++ck;
    }

    // remove completely empty chunks
    while (ck != d->mData.end() &&
           sz >= (*ck).length)
    {
      sz -= (*ck).length;
      DataPrivate::freeChunk(*ck);
      ck = d->mData.erase(ck);
    }

    // memmove partial data in last chunk to beginning of chunk
    if (sz && ck != d->mData.end())
    {
      for (uint8_t channel = 0; channel < mChannels; ++channel)
      {
        memmove((*ck).data[channel],
                (*ck).data[channel] + sz,
                sizeof(Sample) * ((*ck).length - sz));
      }
      (*ck).start -= length - sz;
      (*ck).length -= sz;
      ++ck;
    }
  }

  // update start,end of susbsequent
  for ( ; ck != d->mData.end(); ++ck)
  {
    (*ck).start -= length;
  }

  mLength -= length;

  mActionManager.recordAction(
    new DataInsertAction(*this, start, undoData, i18n("Remove Data"))
    );

  emit lengthChanged(mLength);
  for (uint8_t channel = 0; channel < mChannels; ++channel)
    emit dataChanged(channel, start, mLength-start);
}

void Data::data(off_t start, size_t length,
                SampleSegment& data) const
{
  d->data<Sample>(start, length, data);
}

void Data::data(uint8_t channel, off_t start, size_t length,
                SampleBuffer& data) const
{
  d->data<Sample>(channel, start, length, data);
}

void Data::data(uint8_t channel, off_t start, size_t length,
                Sample8Buffer& data) const
{
  d->data<Sample8>(channel, start, length, data);
}

void Data::data(uint8_t channel, off_t start, size_t length,
                Sample16Buffer& data) const
{
  d->data<Sample16>(channel, start, length, data);
}

void Data::data(uint8_t channel, off_t start, size_t length,
                Sample24Buffer& data) const
{
  d->data<Sample24>(channel, start, length, data);
}

void Data::setData(off_t start,
                   const SampleSegment& data)
{
  for (uint8_t channel = 0; channel < mChannels; ++channel)
    recordSetDataUndoAction(channel, start, data.size());

  d->setData<Sample>(start, data);

  for (uint8_t channel = 0; channel < mChannels; ++channel)
    emit dataChanged(channel, start, data.size());
}

void Data::setData(uint8_t channel, off_t start,
                   const SampleBuffer& data)
{
  recordSetDataUndoAction(channel, start, data.size());
  d->setData<Sample>(channel, start, data);
  emit dataChanged(channel, start, data.size());
}

void Data::setData(uint8_t channel, off_t start,
                   const Sample8Buffer& data)
{
  recordSetDataUndoAction(channel, start, data.size());
  d->setData<Sample8>(channel, start, data);
  emit dataChanged(channel, start, data.size());
}

void Data::setData(uint8_t channel, off_t start,
                   const Sample16Buffer& data)
{
  recordSetDataUndoAction(channel, start, data.size());
  d->setData<Sample16>(channel, start, data);
  emit dataChanged(channel, start, data.size());
}

void Data::setData(uint8_t channel, off_t start,
                   const Sample24Buffer& data)
{
  recordSetDataUndoAction(channel, start, data.size());
  d->setData<Sample24>(channel, start, data);
  emit dataChanged(channel, start, data.size());
}

void Data::fillData(off_t start, size_t length,
                    const Sample& val)
{
  for (uint8_t channel = 0; channel < mChannels; ++channel)
    recordSetDataUndoAction(channel, start, length);

  d->fillData<Sample>(start, length, val);

  for (uint8_t channel = 0; channel < mChannels; ++channel)
    emit dataChanged(channel, start, length);
}

void Data::fillData(uint8_t channel, off_t start, size_t length,
                    const Sample& val)
{
  recordSetDataUndoAction(channel, start, length);
  d->fillData<Sample>(channel, start, length, val);
  emit dataChanged(channel, start, length);
}

void Data::fillData(uint8_t channel, off_t start, size_t length,
                    const Sample8& val)
{
  recordSetDataUndoAction(channel, start, length);
  d->fillData<Sample8>(channel, start, length, val);
  emit dataChanged(channel, start, length);
}

void Data::fillData(uint8_t channel, off_t start, size_t length,
                    const Sample16& val)
{
  recordSetDataUndoAction(channel, start, length);
  d->fillData<Sample16>(channel, start, length, val);
  emit dataChanged(channel, start, length);
}

void Data::fillData(uint8_t channel, off_t start, size_t length,
                    const Sample24& val)
{
  recordSetDataUndoAction(channel, start, length);
  d->fillData<Sample24>(channel, start, length, val);
  emit dataChanged(channel, start, length);
}

void Data::compact()
{
  if (d->mData.size() < 2)
    return;

  if (mLength >= (d->mData.size()-1) * DataPrivate::kChunkSize)
    return;

  DataPrivate::ChunkVector::iterator cur = d->mData.begin(), next = cur + 1;

  while (next != d->mData.end())
  {
    if ((*cur).length + (*next).length <= DataPrivate::kChunkSize)
    {
      for (uint8_t channel = 0; channel < mChannels; ++channel)
      {
        memcpy((*cur).data[channel] + (*cur).length,
               (*next).data[channel],
               sizeof(Sample) * (*next).length);
      }

      (*cur).length += (*next).length;

      DataPrivate::freeChunk(*next);
      next = d->mData.erase(next);
    }
    else
    {
      ++cur;
      ++next;
    }
  }

  // TODO: compact over >2 chunks (eg 3 into 2)
}

void Data::reset(uint8_t channels, size_t length)
{
  // free exisiting data
  d->clearChunks();

  mActionManager.clear();

  mChannels = channels;
  mLength = length;

  // allocate in blocks of kChunkSize
  size_t pos = 0;
  while (pos < length)
  {
    size_t sz = QMIN(length, DataPrivate::kChunkSize);
    DataPrivate::Chunk ck = DataPrivate::allocateChunk(pos, sz, mChannels);

    d->mData.push_back(ck);
    pos += sz;
  }

  emit channelsChanged(mChannels);
  emit lengthChanged(mLength);
}

Sonik::IOResult Data::open(FileIO::Reader& reader)
{
  kdDebug(60606) << "Data::open: init reader" << "\n";
  IOResult res = reader.open();

  if (res != kSuccess)
    return res;

  reset(reader.channels(), reader.length());

  mActionManager.setEnabled(false);

  setSampleRate(reader.sampleRate());
  setBits(reader.bits());

  size_t framesLeft = mLength;
  off_t pos = 0;
  const size_t kMaxBlock = 0x10000;
  size_t blockFrames = kMaxBlock / mChannels;
  SampleBuffer channelBuf(blockFrames);
  SampleBuffer interleaveBuf(kMaxBlock);
  while (framesLeft && res == kSuccess)
  {
    size_t frames = (framesLeft > blockFrames) ? blockFrames : framesLeft;
    size_t bufSize = mChannels * frames;

    channelBuf.resize(frames);
    interleaveBuf.resize(bufSize);

    size_t count = reader.read(interleaveBuf);
    if (count != frames)
    {
      kdDebug(60606) << "Data::open: " << "read error ("
                     << count << " / " << frames << ")" << "\n";
      reader.close();
      mActionManager.setEnabled(true);
      return kReadError;
    }

    // deinterleave and set
    for (uint8_t channel = 0; channel < mChannels; ++channel)
    {
      Sonik::deinterleave(interleaveBuf, channelBuf, mChannels, channel);
      setData(channel, pos, channelBuf);
    }

    framesLeft -= frames;
    pos += frames;
  }

  kdDebug(60606) << "Data::open: closing" << "\n";
  reader.close();
  mActionManager.setEnabled(true);

  return kSuccess;
}

Sonik::IOResult Data::save(FileIO::Writer& writer) const
{
  IOResult res = writer.open();

  if (res != kSuccess)
    return res;

  size_t framesLeft = mLength;
  off_t pos = 0;
  const size_t kMaxBlock = 0x10000;
  size_t blockFrames = kMaxBlock / mChannels;
  SampleBuffer channelBuf(blockFrames);
  SampleBuffer interleaveBuf(kMaxBlock);
  while (framesLeft && res == kSuccess)
  {
    size_t frames = (framesLeft > blockFrames) ? blockFrames : framesLeft;
    size_t bufSize = mChannels * frames;

    channelBuf.resize(frames);
    interleaveBuf.resize(bufSize);

    // interleave
    for (uint8_t channel = 0; channel < mChannels; ++channel)
    {
      data(channel, pos, frames, channelBuf);
      Sonik::interleave(channelBuf, interleaveBuf, mChannels, channel);
    }

    size_t count = writer.write(interleaveBuf);
    if (count != frames)
    {
      writer.close();
      return kWriteError;
    }

    framesLeft -= frames;
    pos += frames;
  }

  writer.close();

  return res;
}

void Data::suspendSignals()
{
  blockSignals(true);
}

void Data::resumeSignals()
{
  blockSignals(false);

  // broadcast current state
  emit sampleRateChanged(mSampleRate);
  emit bitsChanged(mBits);
  emit channelsChanged(mChannels);
  emit lengthChanged(mLength);
  for (uint8_t channel = 0; channel < mChannels; ++channel)
    emit dataChanged(channel, 0, mLength);
}

size_t Data::chunkSize() const
{
  return DataPrivate::kChunkSize;
}

uint32_t Data::chunkCount() const
{
  return d->mData.size();
}

void Data::chunkInfo(uint32_t chunk, off_t& start, size_t& length) const
{
  assert(chunk < d->mData.size());

  start = d->mData[chunk].start;
  length = d->mData[chunk].length;
}

void Data::recordSetDataUndoAction(uint8_t channel,
                                   off_t start, size_t length)
{
  SampleBuffer undoData(length);
  data(channel, start, length, undoData);
  mActionManager.recordAction(
    new DataSetAction(*this, channel, start, undoData, i18n("Set Data"))
    );
}
