/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2002  Andreas Mueller <andreas@daneb.de>
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <limits.h>
#include <math.h>
#include <assert.h>

#include <gtkmm.h>
#include <gtk/gtk.h>

#include "SampleManager.h"

#include "TocEdit.h"
#include "guiUpdate.h"
#include "Toc.h"
#include "util.h"

#include "TrackDataScrap.h"

class SampleManagerImpl : public sigc::trackable {
public:
  SampleManagerImpl(unsigned long);
  ~SampleManagerImpl();

  unsigned long blocking_;
  TocEdit *tocEdit_;
  TocReader tocReader_;

  short *leftNegSamples_;
  short *leftPosSamples_;
  short *rightNegSamples_;
  short *rightPosSamples_;
  long samplesSize_;
  long blocks_;
  unsigned long slength_;
  long chunk_;

  Sample *block_;
  long actBlock_;
  long endBlock_;
  long burstBlock_;
  const char* curFilename_;
  unsigned long length_;
  gfloat percent_;
  gfloat percentStep_;

  void getPeak(unsigned long start, unsigned long end,
	       short *leftNeg, short *leftPos,
	       short *rightNeg, short *rightPos);
  int scanToc(unsigned long start, unsigned long end, bool blocking);

  int  readSamples();
  void reallocSamples(unsigned long maxSample);
  void removeSamples(unsigned long start, unsigned long end, TrackDataScrap *);
  void insertSamples(unsigned long pos, unsigned long len,
		     const TrackDataScrap *);
};

SampleManager::SampleManager(unsigned long blocking)
{
  impl_ = new SampleManagerImpl(blocking);
}

SampleManager::~SampleManager()
{
  delete impl_;
  impl_ = NULL;
}

unsigned long SampleManager::blocking() const
{
  return impl_->blocking_;
}

void SampleManager::setTocEdit(TocEdit *t)
{
  impl_->tocEdit_ = t;
  impl_->tocReader_.init(t->toc());

  impl_->blocks_ = 0;
}


int SampleManager::scanToc(unsigned long start, unsigned long end,
                           bool blocking)
{
  return impl_->scanToc(start, end, blocking);
}

int SampleManager::readSamples()
{
  return impl_->readSamples();
}

void SampleManager::getPeak(unsigned long start, unsigned long end,
			    short *leftNeg, short *leftPos,
			    short *rightNeg, short *rightPos)
{
  impl_->getPeak(start, end, leftNeg, leftPos, rightNeg, rightPos);
}

void SampleManager::removeSamples(unsigned long start, unsigned long end,
				  TrackDataScrap *scrap)
{
  impl_->removeSamples(start, end, scrap);
}

void SampleManager::insertSamples(unsigned long pos, unsigned long len,
				  const TrackDataScrap *scrap)
{
  impl_->insertSamples(pos, len, scrap);
}

SampleManagerImpl::SampleManagerImpl(unsigned long blocking) : tocReader_(NULL)
{
  blocking_ = blocking;
  tocEdit_ = NULL;

  leftNegSamples_ = leftPosSamples_ = NULL;
  rightNegSamples_ = rightPosSamples_ = NULL;
  samplesSize_ = 0;
  blocks_ = 0;
  slength_ = 0;

  block_ = new Sample[blocking_];
  actBlock_ = endBlock_ = burstBlock_ = 0;
  length_ = 0;

  // allocate space in chunks of 40 minutes
  chunk_ = 40 * 60 * 75 * 588 / blocking;
}

SampleManagerImpl::~SampleManagerImpl()
{
  delete[] block_;

  delete[] leftNegSamples_;
  delete[] leftPosSamples_;
  delete[] rightNegSamples_;
  delete[] rightPosSamples_;

  tocEdit_ = NULL;
}

void SampleManagerImpl::getPeak(unsigned long start, unsigned long end,
				short *leftNeg, short *leftPos,
				short *rightNeg, short *rightPos)
{
  *leftNeg = *leftPos = 0;
  *rightNeg = *rightPos = 0;

  long startBlock = start / blocking_;
  long endBlock = end / blocking_;
  long i;

  if (startBlock >= blocks_ || endBlock >= blocks_)
    return;

  for (i = startBlock; i <= endBlock; i++) {
    assert(leftNegSamples_[i] <= 0);
    assert(rightNegSamples_[i] <= 0);
    assert(leftPosSamples_[i] >= 0);
    assert(rightPosSamples_[i] >= 0);
	
    if (leftNegSamples_[i] < *leftNeg)
      *leftNeg = leftNegSamples_[i];
	
    if (leftPosSamples_[i] > *leftPos)
      *leftPos = leftPosSamples_[i];
	
    if (rightNegSamples_[i] < *rightNeg)
      *rightNeg = rightNegSamples_[i];
	
    if (rightPosSamples_[i] > *rightPos)
      *rightPos = rightPosSamples_[i];
  }
}

// Return values:
//   0 : ok
//   1 : incorrect parameters
//   2 : unable to read from file

int SampleManagerImpl::scanToc(unsigned long start, unsigned long end,
                               bool blocking)
{
  long i;
  const Toc *toc;

  actBlock_ = start / blocking_;
  endBlock_ = end / blocking_;
  curFilename_ = NULL;

  if (tocEdit_ == NULL || endBlock_ < actBlock_)
    return 1;

  toc = tocEdit_->toc();

  length_ = toc->length().samples();

  if (end >= length_)
    return 1;

  length_ -= actBlock_ * blocking_;

  reallocSamples(end);

  for (i = actBlock_; i <= endBlock_; i++) {
    leftNegSamples_[i] = rightNegSamples_[i] = -16000;
    leftPosSamples_[i] = rightPosSamples_[i] = 16000;
  }

  if (tocReader_.openData() != 0)
    return 2;

  if (tocReader_.seekSample(actBlock_ * blocking_) != 0) {
    tocReader_.closeData();
    return 2;
  }

  long len = endBlock_ - actBlock_ + 1;

  if (len < 2000) {
    burstBlock_ = len;
    percentStep_ = 1.0;
    // withGui_ = false;
  }
  else if (len < 10000) {
    burstBlock_ = len / 100;
    percentStep_ = 0.01;
    // withGui_ = true;
  }
  else {
    burstBlock_ = 75;
    percentStep_ = gfloat(burstBlock_) / gfloat(len);
    // withGui_ = true;
  }

  if (burstBlock_ == 0) 
    burstBlock_ = 1;

  percent_ = 0;

  if (blocking) {
    while (readSamples() == 0);
    return 0;
  }

  return 0;
}

// Returns:
//   0 : in progress
//   1 : done
//  -1 : read error

int SampleManagerImpl::readSamples()
{
  int j;
  long n;
  short lpossum, rpossum, lnegsum, rnegsum;
  int ret;
  long burstEnd = actBlock_ + burstBlock_;

  const char* cf = tocReader_.curFilename();
  if (cf && cf != curFilename_) {
    std::string msg = "Scanning audio data \"";
    msg += cf;
    msg += "\"";
    tocEdit_->signalStatusMessage(msg.c_str());
    curFilename_ = cf;
    guiUpdate(UPD_SAMPLES);
  }

  for (; actBlock_ <= endBlock_ && actBlock_ < burstEnd && length_ > 0;
       actBlock_++) {
    n = length_ > blocking_ ? blocking_ : length_;
    if ((ret = tocReader_.readSamples(block_, n)) == n) {
      lpossum = lnegsum = rpossum = rnegsum = 0;
      for (j = 0; j < n; j++) {
	short d = block_[j].left();
	if (d > lpossum)
	  lpossum = d;
	if (d < lnegsum)
	  lnegsum = d;

	d = block_[j].right();
	if (d > rpossum)
	  rpossum = d;
	if (d < rnegsum)
	  rnegsum = d;
      }
      leftNegSamples_[actBlock_] = lnegsum;
      leftPosSamples_[actBlock_] = lpossum;
      rightNegSamples_[actBlock_] = rnegsum;
      rightPosSamples_[actBlock_] = rpossum;
    }
    else {
      message(-2, "Cannot read audio data: %ld - %ld.", n, ret);
      tocReader_.closeData();
      tocEdit_->signalProgressFraction(0.0);
      return -1;
    }
    length_ -= n;
  }

  
  if (actBlock_ >= endBlock_ && actBlock_ < burstEnd) {
    tocReader_.closeData();
    tocEdit_->signalProgressFraction(0.0);
    return 1;
  }

  percent_ += percentStep_;
  if (percent_ > 1.0) 
    percent_ = 1.0;

  tocEdit_->signalProgressFraction(percent_);

  return 0;
}

void SampleManagerImpl::reallocSamples(unsigned long maxSample)
{
  long i;
  long maxBlock = (maxSample / blocking_) + 1;

  if (maxSample >= slength_)
    slength_ = maxSample + 1;

  if (maxBlock > blocks_)
    blocks_ = maxBlock;

  if (blocks_ > samplesSize_) {
    long newSize = samplesSize_ + chunk_;
    while (newSize < blocks_)
      newSize += chunk_;

    short *newLeftNeg = new short[newSize];
    short *newLeftPos = new short[newSize];
    short *newRightNeg = new short[newSize];
    short *newRightPos = new short[newSize];

    for (i = 0; i < samplesSize_; i++) {
      newLeftNeg[i] = leftNegSamples_[i];
      newLeftPos[i] = leftPosSamples_[i];
      newRightNeg[i] = rightNegSamples_[i];
      newRightPos[i] = rightPosSamples_[i];
    }
    samplesSize_ = newSize;

    delete[] leftNegSamples_;
    delete[] leftPosSamples_;
    delete[] rightNegSamples_;
    delete[] rightPosSamples_;
    leftNegSamples_ = newLeftNeg;
    leftPosSamples_ = newLeftPos;
    rightNegSamples_ = newRightNeg;
    rightPosSamples_ = newRightPos;
  }
}

void SampleManagerImpl::removeSamples(unsigned long start, unsigned long end,
				      TrackDataScrap *scrap)
{
  long i;
  long bstart;
  long oldBlocks = blocks_;
  long blen;
  unsigned long slen;

  if (start > end || end >= slength_)
    return;

  slen = end - start + 1;
  
  slength_ -= slen;

  if (slength_ == 0) {
    blocks_ = 0;
    return;
  }

  blocks_ = ((slength_ - 1) / blocking_) + 1;
  
  blen = oldBlocks - blocks_;
  
  bstart = start / blocking_;

  if (scrap != NULL)
    scrap->setPeaks(blen, &(leftNegSamples_[bstart]),
		    &(leftPosSamples_[bstart]), &(rightNegSamples_[bstart]),
		    &(rightPosSamples_[bstart]));

  if (blen > 0) {
    for (i = bstart; i < blocks_; i++) {
      leftNegSamples_[i] = leftNegSamples_[i + blen];
      leftPosSamples_[i] = leftPosSamples_[i + blen];
      rightNegSamples_[i] = rightNegSamples_[i + blen];
      rightPosSamples_[i] = rightPosSamples_[i + blen] ;
    }
  }
}

void SampleManagerImpl::insertSamples(unsigned long pos, unsigned long len,
				      const TrackDataScrap *scrap)
{
  long blen;
  long bpos;
  long oldBlocks;
  long i;

  if (len == 0)
    return;

  bpos = pos / blocking_;

  if (bpos > blocks_)
    bpos = blocks_;

  oldBlocks = blocks_;

  reallocSamples(slength_ + (len - 1));

  blen = blocks_ - oldBlocks;

  if (blen > 0) {
    for (i = blocks_ - 1; i >= bpos + blen; i--) {
      leftNegSamples_[i] = leftNegSamples_[i - blen];
      leftPosSamples_[i] = leftPosSamples_[i - blen];
      rightNegSamples_[i] = rightNegSamples_[i - blen];
      rightPosSamples_[i] = rightPosSamples_[i - blen] ;
    }

    // initialize the new region
    for (i = bpos; i < bpos + blen; i++) {
      leftNegSamples_[i] = -16000;
      leftPosSamples_[i] = 16000;
      rightNegSamples_[i] = -16000;
      rightPosSamples_[i] = 16000;
    }

    if (scrap != NULL)
      scrap->getPeaks(blen, &(leftNegSamples_[bpos]), &(leftPosSamples_[bpos]),
		      &(rightNegSamples_[bpos]), &(rightPosSamples_[bpos]));
  }
}
