/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2001  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 <config.h>

#include <stddef.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>

#include "Track.h"
#include "util.h"
#include "TrackDataList.h"
#include "CdTextItem.h"

#include "edc_ecc/ecc.h"

Track::Track(TrackData::Mode t) : length_(0), start_(0), end_(0)
{
  type_ = t;

  nofSubTracks_ = 0;
  subTracks_ = lastSubTrack_ = NULL;

  nofIndices_ = 0;
  index_ = new Msf[98](0);

  isrcValid_ = 0;

  flags_.copy = 0;         // digital copy not permitted
  flags_.preEmphasis = 0;  // no pre-emphasis
  flags_.audioType = 0;    // two channel audio
}

Track::Track(const Track &obj)
  : length_(obj.length_), start_(obj.start_), end_(obj.end_),
    cdtext_(obj.cdtext_)
{
  int i;
  SubTrack *run;

  type_ = obj.type_;

  nofSubTracks_ = obj.nofSubTracks_;
  subTracks_ = lastSubTrack_ = NULL;
  for (run = obj.subTracks_; run != NULL; run = run->next_) {
    if (subTracks_ == NULL) {
      subTracks_ = lastSubTrack_ = new SubTrack(*run);
    }
    else {
      lastSubTrack_->next_ = new SubTrack(*run);
      lastSubTrack_->next_->pred_ = lastSubTrack_;
      lastSubTrack_ = lastSubTrack_->next_;
    }
  }

  nofIndices_ = obj.nofIndices_;
  index_ = new Msf[98](0);
  for (i = 0; i < nofIndices_; i++) {
    index_[i] = obj.index_[i];
  }
  
  isrcValid_ = obj.isrcValid_;
  memcpy(isrcCountry_, obj.isrcCountry_, 2);
  memcpy(isrcOwner_, obj.isrcOwner_, 3);
  memcpy(isrcYear_, obj.isrcYear_, 2);
  memcpy(isrcSerial_, obj.isrcSerial_, 5);

  flags_ = obj.flags_;
}


Track::~Track()
{
  SubTrack *run = subTracks_;
  SubTrack *next = NULL;

  while (run != NULL) {
    next = run->next_;
    delete run;
    run = next;
  }

  delete[] index_;
}

// Returns first sub-track or 'NULL' if no sub-tracks are defined
const SubTrack *Track::firstSubTrack() const
{
  return subTracks_ != NULL ? subTracks_ : NULL;
}

// Returns last sub-track or 'NULL' if no sub-tracks are defined
const SubTrack *Track::lastSubTrack() const
{
  return lastSubTrack_ != NULL ? lastSubTrack_ : NULL;
}


// Appends given sub-track to list of sub-tracks.
// return: 0: OK
//         1: tried to append PAD sub-track
int Track::append(const SubTrack &strack)
{
  if (strack.type() == SubTrack::PAD) {
    return 1;
  }

  if (lastSubTrack_ != NULL && lastSubTrack_->type() == SubTrack::PAD &&
      lastSubTrack_->mode() == strack.mode()) {
    // remove padding sub track
    delete removeSubTrack(lastSubTrack_);
  }

  // append sub track
  insertSubTrackAfter(lastSubTrack_, new SubTrack(strack));

  update();

  return 0;
}


// Traverses all sub-tracks to update summary data of track.
void Track::update()
{
  long lenLba = 0;           // accumulates total length of track in blocks
  unsigned long slength;     // length of track in samples
  unsigned long padLen;      // padding length
  unsigned long blen;        // block length for current data mode
  SubTrack *run, *next;
  SubTrack *pad;
  TrackData::Mode dataMode;

  // remove all padding sub-tracks
  run = subTracks_;
  while (run != NULL) {
    next = run->next_;

    if (run->type() == SubTrack::PAD) {
      delete removeSubTrack(run);
    }

    run = next;
  }

  if ((run = subTracks_) != NULL) {
    do {
      dataMode = run->mode();
      if (run->mode() == TrackData::AUDIO) {
	blen = SAMPLES_PER_BLOCK;
      }
      else {
	blen = TrackData::dataBlockSize(run->mode());
      }

      slength = 0;

      // find continues range of sub tracks with same data mode
      do {
	slength += run->length();
	run = run->next_;
      } while (run != NULL && run->mode() == dataMode);
      
      if ((padLen = slength % blen) != 0) {
	padLen = blen - padLen;
	pad = new SubTrack(SubTrack::PAD, 0, TrackData(dataMode, padLen));
	
	if (run != NULL) {
	  insertSubTrackAfter(run->pred_, pad);
	}
	else {
	  insertSubTrackAfter(lastSubTrack_, pad);
	}
	slength += padLen;
      }

      // at this point 'slength' should be a multiple of 'blen'
      assert(slength % blen == 0);

      lenLba += slength / blen;
    
    } while (run != NULL);
  }

  length_ = Msf(lenLba);

  slength = 0;
  for (run = subTracks_; run != NULL; run = run->next_) {
    run->start(slength); // set start position of sub-track
    slength += run->length();
  }


  // reset 'start_' if necessary
  if (start_.lba() >= length_.lba()) {
    start_ = Msf(0);
  }
}

// Sets logical start of track, everthing before start (if != 0) is taken
// as pre-gap. FIXME: Already set index marks are not updated.
// return: 0: OK
//         1: given start behind track end
int Track::start(Msf s)
{
  if (s.lba() >= length_.lba()) {
    return 1;
  }

  start_ = s;

  return 0;
}

// Sets end of user area of track which is the start of the post-gap. 
// An index entry is created at this point. The pre-gap length must be
// set with 'start()' before this function can be called.
// return: 0: OK
//         1: start of post-gap is behind or at track end
//         2: post-gap within pre-gap
//         3: cannot create index mark, 98 index marks are already defined
int Track::end(Msf e)
{
  if (e.lba() >= length_.lba()) {
    return 1;
  }

  if (e.lba() != 0 && e.lba() <= start_.lba()) {
    return 2;
  }

  if (e.lba() != 0) {
    // add index mark for post-gap
    if (addIndex(Msf(e.lba() - start_.lba())) == 1) {
      // already 98 index increments defined
      return 3;
    }
  }

  end_ = e;

  return 0;
}


// Appends given index to index increment list.
// return: 0: OK
//         1: > 98 index increments 
//         2: index at or beyond track end
//         3: index at start
int Track::appendIndex(const Msf &index)
{
  if (nofIndices_ == 98) {
    return 1;
  }

  if (index.lba() >= (length_ - start_).lba()) {
    return 2;
  }

  if (index.lba() == 0) {
    return 3;
  }

  index_[nofIndices_] = index;
  nofIndices_ += 1;

  return 0;
}

// Adds index at given position
// return: 0: OK
//         1: > 98 index increments 
//         2: cannot add index at specified position
int Track::addIndex(const Msf &index)
{
  int i;
  if (nofIndices_ == 98) {
    return 1;
  }

  if (index.lba() >= (length_ - start_).lba()) {
    return 2;
  }

  if (index.lba() == 0) {
    return 2;
  }

  for (i = 0; i < nofIndices_; i++) {
    if (index.lba() == index_[i].lba())
      return 2;

    if (index.lba() < index_[i].lba())
      break;
  }

  if (i == nofIndices_) {
    // append index
    index_[nofIndices_] = index;
    nofIndices_ += 1; 
  }
  else {
    int pos = i;
    for (i = nofIndices_; i > pos; i--)
      index_[i] = index_[i - 1];

    index_[pos] = index;
    nofIndices_ += 1; 
  }
  
  return 0;
}

// Removes specified index.
// return 0: OK
//        1: index not found
int Track::removeIndex(int index)
{
  int i;

  if (index < 0 || index >= nofIndices_)
    return 1;

  
  for (i = index; i < nofIndices_ - 1; i++)
    index_[i] = index_[i + 1];

  nofIndices_ -= 1;

  return 0;
}

// returns index increment
Msf Track::getIndex(int i) const
{
  if (i >= nofIndices_ || i < 0) {
    return Msf(0);
  }
  else {
    return index_[i];
  }
}

int Track::check(int trackNr) const
{
  SubTrack *st;
  int ret = 0;

  if (length().lba() - start().lba() < Msf(0, 4, 0).lba()) {
    message(-1, "Track %d: Length is shorter than 4 seconds.", trackNr);
    ret = 1;
  }

  for (st = subTracks_; st != NULL; st = st->next_) {
    ret |= st->check(trackNr);
  }

  return ret;
}

// Sets ISRC code. Expected string: "CCOOOYYSSSSS"
//                 C: country code (ASCII)
//                 O: owner code (ASCII)
//                 Y: year ('0'-'9')
//                 S: serial number ('0'-'9')
// return: 0: OK
//         1: ilegal ISRC string
int Track::isrc(const char *isrc)
{
  int i;

  if (isrc == NULL) {
    isrcValid_ = 0;
    return 0;
  }

  if (strlen(isrc) != 12) {
    return 1;
  }

  for (i=0; i < 5; i++) {
    if (!(isdigit(isrc[i]) || isupper(isrc[i]))) {
      return 1;
    }
  }

  for (i = 5; i < 12; i++) {
    if (!isdigit(isrc[i])) {
      return 1;
    }
  }

  isrcCountry_[0] = isrc[0];
  isrcCountry_[1] = isrc[1];

  isrcOwner_[0] = isrc[2];
  isrcOwner_[1] = isrc[3];
  isrcOwner_[2] = isrc[4];


  // store BCD
  isrcYear_[0] = isrc[5] - '0';
  isrcYear_[1] = isrc[6] - '0';
  
  isrcSerial_[0] = isrc[7] - '0';
  isrcSerial_[1] = isrc[8] - '0';
  isrcSerial_[2] = isrc[9] - '0';
  isrcSerial_[3] = isrc[10] - '0';
  isrcSerial_[4] = isrc[11] - '0';
  
  isrcValid_ = 1;

  return 0;
}

const char *Track::isrc() const
{
  static char buf[13];
  
  if (!isrcValid_)
    return NULL;

  buf[0] = isrcCountry_[0];
  buf[1] = isrcCountry_[1];
  buf[2] = isrcOwner_[0];
  buf[3] = isrcOwner_[1];
  buf[4] = isrcOwner_[2];
  buf[5] = isrcYear_[0] + '0';
  buf[6] = isrcYear_[1] + '0';
  buf[7] = isrcSerial_[0] + '0';
  buf[8] = isrcSerial_[1] + '0';
  buf[9] = isrcSerial_[2] + '0';
  buf[10] = isrcSerial_[3] + '0';
  buf[11] = isrcSerial_[4] + '0';
  buf[12] = 0;

  return buf;
}

int Track::isPadded() const
{
  SubTrack *run;

  for (run = subTracks_; run != NULL; run = run->next_) {
    if (run->type() == SubTrack::PAD)
      return 1;
  }

  return 0;
}

// writes out track data in TOC file syntax
void Track::print(ostream &out) const
{
  SubTrack *st;
  int i;

  out << "TRACK " << TrackData::mode2String(type());

  out << endl;

  if (!copyPermitted())
    out << "NO ";
  out << "COPY" << endl;

  if (type() == TrackData::AUDIO) {
    if (!preEmphasis())
      out << "NO ";
    out << "PRE_EMPHASIS" << endl;

    if (audioType() == 0)
      out << "TWO_CHANNEL_AUDIO" << endl;
    else
      out << "FOUR_CHANNEL_AUDIO" << endl;
  
    if (isrcValid()) {
      out << "ISRC \"" << isrcCountry(0) << isrcCountry(1)
	  << isrcOwner(0) << isrcOwner(1) << isrcOwner(2)
	  << (char)(isrcYear(0) + '0') << (char)(isrcYear(1) + '0')
	  << (char)(isrcSerial(0) + '0') << (char)(isrcSerial(1) + '0')
	  << (char)(isrcSerial(2) + '0') << (char)(isrcSerial(3) + '0')
	  << (char)(isrcSerial(4) + '0') << "\"" << endl;
    }

    cdtext_.print(1, out);
  }


  for (st = subTracks_; st != NULL; st = st->next_) {
    st->print(out);
  }

  if (start_.lba() != 0) {
    out << "START " << start_.str() << endl;
  }

  for (i = 0; i < nofIndices_; i++) {
    out << "INDEX " << index_[i].str() << endl;
  }
}

// Locates 'SubTrack' that contains specified sample.
// return: found 'SubTrack' or 'NULL' if given sample is out of range
SubTrack *Track::findSubTrack(unsigned long sample) const
{
  SubTrack *run;

  if (sample >= length_.samples()) 
    return NULL;

  for (run = subTracks_;
       run != NULL && run->next_ != NULL;
       run = run->next_) {
    if (sample < run->next_->start())
      return run;
  }

  return run;
}


void Track::countSubTracks()
{
  SubTrack *run;

  nofSubTracks_ = 0;
  for (run = subTracks_; run != NULL; run = run->next_)
    nofSubTracks_++;
}

// move track start or index increment within track range
// return: 0: OK
//         1: track length would be shorter than 4 seconds
//         2: cannot cross index boundary
int Track::moveIndex(int index, long lba)
{
  int i;
  long rangeMin;
  long rangeMax;

  assert(index > 0 && index - 2 < nofIndices_);
  assert(lba >= 0 && index < length_.lba());

  if (index == 1) {
    if (nofIndices_ > 0 && lba >= start_.lba() + getIndex(0).lba())
      return 2;

    if (lba > length_.lba() - 4 * 75)
      return 1;

    // adjust index increments to new track start
    for (i = 0; i < nofIndices_; i++) {
      index_[i] = Msf(index_[i].lba() + start_.lba() - lba);
    }

    start_ = Msf(lba);

    return 0;
  }

  // adjust 'index' for index array access
  index -= 2;

  if (lba <= start_.lba())
    return 2;

  lba -= start_.lba();

  rangeMin = (index == 0 ? 0 : index_[index - 1].lba());
  rangeMax =
    (index == nofIndices_ - 1 ? length_.lba() - start_.lba() :
                                index_[index + 1].lba());

  if (lba > rangeMin && lba < rangeMax) {
    index_[index] = Msf(lba);
    return 0;
  }

  return 2;
}

TrackDataList *Track::removeToEnd(unsigned long sample)
{
  SubTrack *strun;
  SubTrack *store;
  int i;

  assert(sample > 0 && sample < length_.samples());

  strun = findSubTrack(sample);

  assert(strun != NULL);

  TrackDataList *list = new TrackDataList;

  if (sample == strun->start()) {
    // we don't have to split the TrackData object
    list->append(new TrackData(*strun));

    // cannot be the first SubTrack because 'sample' > 0
    strun->pred_->next_ = NULL;
    lastSubTrack_ = strun->pred_;
    
    store = strun;
    strun = strun->next_;
    delete store;
  }
  else {
    // split audio data object
    TrackData *part1, *part2;

    strun->split(sample - strun->start(), &part1, &part2);

    list->append(part2);

    store = new SubTrack(strun->type(), *part1);
    delete part1;

    if (strun->pred_ == NULL) {
      subTracks_ = store;
    }
    else {
      strun->pred_->next_ = store;
      store->pred_ = strun->pred_;
    }
    lastSubTrack_ = store;
    
    store = strun;
    strun = strun->next_;
    delete store;
  }

  
  while (strun != NULL) {
    list->append(new TrackData(*strun));

    store = strun;
    strun = strun->next_;
    delete store;
  }

  countSubTracks();
  update();

  checkConsistency();

  // adjust index increments
  for (i = 0; i < nofIndices_; i++) {
    if (index_[i].lba() + start_.lba() >= length_.lba()) {
      nofIndices_ = i;
      break;
    }
  }

  return list;
}

TrackDataList *Track::removeFromStart(unsigned long sample)
{
  SubTrack *strun;
  SubTrack *store, *start;
  int i;

  assert(sample > 0 && sample < length_.samples());

  TrackDataList *list = new TrackDataList;

  for (strun = subTracks_;
       strun != NULL && strun->next_ != NULL;
       strun = strun->next_) {
    if (sample < strun->next_->start())
      break;
    else
      list->append(new TrackData(*strun));
  }

  assert(strun != NULL);

  start = subTracks_;

  if (sample == strun->start()) {
    // we don't have to split the TrackData object
    // cannot be the first SubTrack because 'sample' > 0

    strun->pred_->next_ = NULL;
    subTracks_ = strun;
    subTracks_->pred_ = NULL;
  }
  else {
    // split actual sub track

    TrackData *part1, *part2;

    strun->split(sample - strun->start(), &part1, &part2);
    
    list->append(part1);

    store = new SubTrack(strun->type(), *part2);
    delete part2;

    store->next_ = strun->next_;
    if (store->next_ != NULL)
      store->next_->pred_ = store;

    strun->next_ = NULL;
    subTracks_ = store;

    if (strun == lastSubTrack_)
      lastSubTrack_ = store;
  }

  // remove unlinked sub tracks
  while (start != NULL) {
    store = start;
    start = start->next_;

    delete store;
  }
  
  countSubTracks();
  update();

  checkConsistency();

  // adjust index increments
  for (i = 0; i < nofIndices_; i++) {
    if (index_[i].lba() + start_.lba() >= length_.lba()) {
      nofIndices_ = i;
      break;
    }
  }

  return list;
}
  

void Track::prependTrackData(const TrackDataList *list)
{
  SubTrack *start = NULL;
  SubTrack *last = NULL;
  SubTrack *ent;
  const TrackData *run;

  if (list->count() == 0)
    return;

  for (run = list->first(); run != NULL; run = list->next()) {
    ent = new SubTrack(SubTrack::DATA, *run);
    if (last == NULL) {
      start = ent;
    }
    else {
      last->next_ = ent;
      ent->pred_ = last;
    }
    last = ent;
  }

  if (subTracks_ == NULL) {
    subTracks_ = start;
    lastSubTrack_ = last;
  }
  else {
    last->next_ = subTracks_;
    subTracks_->pred_ = last;
    subTracks_ = start;
  }

  mergeSubTracks(); // this will also update the sub track counter
  update();

  checkConsistency();
}

void Track::appendTrackData(const TrackDataList *list)
{
  SubTrack *start = NULL;
  SubTrack *last = NULL;
  SubTrack *ent;
  const TrackData *run;

  if (list->count() == 0)
    return;

  for (run = list->first(); run != NULL; run = list->next()) {
    ent = new SubTrack(SubTrack::DATA, *run);
    if (last == NULL) {
      start = ent;
    }
    else {
      last->next_ = ent;
      ent->pred_ = last;
    }
    last = ent;
  }

  if (lastSubTrack_ != NULL) {
    if (lastSubTrack_->type() == SubTrack::PAD)
      lastSubTrack_->type_ = SubTrack::DATA;

    lastSubTrack_->next_ = start;
    start->pred_ = lastSubTrack_;
    lastSubTrack_ = last;
  }
  else {
    subTracks_ = start;
    lastSubTrack_ = last;
  }

  mergeSubTracks(); // this will also update the sub track counter
  update();

  checkConsistency();
}  

void Track::appendTrackData(const Track *track)
{
  SubTrack *run, *ent;

  for (run = track->subTracks_; run != NULL; run = run->next_) {
    ent = new SubTrack(*run);

    if (lastSubTrack_ == NULL) {
      subTracks_ = ent;
    }
    else {
      lastSubTrack_->next_ = ent;
      ent->pred_ = lastSubTrack_;
    }
    lastSubTrack_ = ent;
  }

  mergeSubTracks(); // this will also update the sub track counter
  update();

  checkConsistency();
}

TrackDataList *Track::removeTrackData(unsigned long start, unsigned long end)
{
  SubTrack *run;
  TrackData *part1, *part2, *part3;
  unsigned long plen;

  if (start > end || end >= length_.samples())
    return NULL;

  SubTrack *startSt = findSubTrack(start);
  SubTrack *endSt = findSubTrack(end);

  assert(startSt != NULL);
  assert(endSt != NULL);

  TrackDataList *list = new TrackDataList;

  if (startSt == endSt) {
    if (start == startSt->start() &&
	end == startSt->start() + startSt->length() - 1) {
      // remove complete sub track
      list->append(new TrackData(*startSt));
    }
    else if (start == startSt->start()) {
      // remove first part of sub track
      startSt->split(end + 1 - startSt->start(), &part1, &part2);
      list->append(part1);
      insertSubTrackAfter(startSt, new SubTrack(startSt->type(), *part2));
      delete part2;
    }
    else if (end == startSt->start() + startSt->length() - 1) {
      // remove last part of sub track
      startSt->split(start - startSt->start(), &part1, &part2);
      list->append(part2);
      insertSubTrackAfter(startSt, new SubTrack(startSt->type(), *part1));
      delete part1;
    }
    else {
      // remove middle part of sub track
      startSt->split(start - startSt->start(), &part1, &part2);

      insertSubTrackAfter(startSt->pred_,
			  new SubTrack(startSt->type(), *part1));
      plen = part1->length();
      delete part1;

      part2->split(end + 1 - startSt->start() - plen, &part1, &part3);
      list->append(part1);

      insertSubTrackAfter(startSt, new SubTrack(startSt->type(), *part3));
      delete part3;
      delete part2;
    }

    delete removeSubTrack(startSt);
  }
  else {
    if (start == startSt->start()) {
      // remove complete sub track
      list->append(new TrackData(*startSt));
    }
    else {
      startSt->split(start - startSt->start(), &part1, &part2);
      list->append(part2);
      insertSubTrackAfter(startSt->pred_,
			  new SubTrack(startSt->type(), *part1));
      delete part1;
    }

    for (run = startSt->next_; run != endSt; run = run->next_)
      list->append(new TrackData(*(startSt->next_)));

    if (end == endSt->start() + endSt->length() - 1) {
      // remove complete sub track
      list->append(new TrackData(*endSt));
    }
    else {
      endSt->split(end + 1 - endSt->start(), &part1, &part2);
      list->append(part1);
      insertSubTrackAfter(endSt, new SubTrack(endSt->type(), *part2));
      delete part2;
    }

    while (startSt->next_ != endSt)
      delete removeSubTrack(startSt->next_);

    delete removeSubTrack(startSt);
    delete removeSubTrack(endSt);
  }


  // adjust index marks
  unsigned long len;
  long preGapAdj = 0;
  long slba = 0;
  long elba = 0;
  long indexMove = 0;
  long indexAdj = 0;
  int i;

  if (start < start_.samples()) {
    if (end < start_.samples()) {
      len = end - start + 1;
      slba = -1;
      elba = -1;
    }
    else {
      len = start_.samples() - start;
      elba = (end - start_.samples()) / SAMPLES_PER_BLOCK;
      slba = 0;
      indexMove = (end - start_.samples() + 1) / SAMPLES_PER_BLOCK;
      if (((end - start_.samples() + 1) % SAMPLES_PER_BLOCK) != 0) 
	indexMove += 1;
    }

    preGapAdj = len / SAMPLES_PER_BLOCK;

    if ((len % SAMPLES_PER_BLOCK) != 0)
      indexAdj = 1;
  }
  else {
    slba = (start - start_.samples()) / SAMPLES_PER_BLOCK;
    if (((start - start_.samples()) % SAMPLES_PER_BLOCK) != 0)
      slba += 1;

    elba = (end - start_.samples()) / SAMPLES_PER_BLOCK;

    indexMove = (end - start + 1) / SAMPLES_PER_BLOCK;
    if (((end - start + 1) % SAMPLES_PER_BLOCK) != 0)
      indexMove += 1;
  }

  
  i = 0;

  while (i < nofIndices_) {
    if (index_[i].lba() >= slba && index_[i].lba() <= elba) {
      removeIndex(i);
      continue;
    }
    else if (index_[i].lba() > elba) {
      if (index_[i].lba() - indexMove <= 0) {
	removeIndex(i);
	continue;
      }
      else {
	index_[i] = Msf(index_[i].lba() - indexMove);
      }

      if (i > 0 && index_[i - 1].lba() == index_[i].lba()) {
	removeIndex(i);
	continue;
      }
    }

    if (index_[i].lba() - indexAdj <= 0) {
      removeIndex(i);
      continue;
    }
    else {
      index_[i] = Msf(index_[i].lba() - indexAdj);
    }

    i++;
  }

  // adjust pre-gap length
  start_ = Msf(start_.lba() - preGapAdj);
  

  mergeSubTracks(); // this will also update the sub track counter
  update();

  checkConsistency();

  return list;
}


void Track::insertTrackData(unsigned long pos, const TrackDataList *list)
{
  const TrackData *run;
  TrackData *part1, *part2;
  SubTrack *st;
  unsigned long len;
  long blen;
  int i;

  if (list == NULL || list->first() == NULL || list->length() == 0)
    return;


  if (pos >= length_.samples()) {
    // append data
    for (run = list->first(); run != NULL; run = list->next()) {
      insertSubTrackAfter(lastSubTrack_, new SubTrack(SubTrack::DATA, *run));
    }
  }
  else {
    st = findSubTrack(pos);

    assert(st != NULL);

    len = list->length();

    if (pos == st->start()) {
      for (run = list->first(); run != NULL; run = list->next()) {
	insertSubTrackAfter(st->pred_, new SubTrack(SubTrack::DATA, *run));
      }
    }
    else {
      st->split(pos - st->start(), &part1, &part2);
      
      insertSubTrackAfter(st->pred_, new SubTrack(SubTrack::DATA, *part1));
      insertSubTrackAfter(st, new SubTrack(st->type(), *part2));
      delete part1;
      delete part2;
      
      for (run = list->first(); run != NULL; run = list->next()) {
	insertSubTrackAfter(st->pred_, new SubTrack(SubTrack::DATA, *run));
      }
      
      delete removeSubTrack(st);
    }

    blen = len / SAMPLES_PER_BLOCK;

    if (pos <= start_.samples()) {
      start_ = Msf(start_.lba() + blen);
    }
    else {
      for (i = 0; i < nofIndices_; i++) {
	if (index_[i].samples() >= pos)
	  index_[i] = Msf(index_[i].lba() + blen);
      }
    }
  }

  mergeSubTracks(); // this will also update the sub track counter
  update();

  checkConsistency();

  return;
}
    
void Track::mergeSubTracks()
{
  SubTrack *run = subTracks_;
  SubTrack *newSubTrack;
  TrackData *mergedData;


  while (run != NULL && run->next_ != NULL) {
    if (run->type() == run->next_->type() &&
	(mergedData = run->merge(run->next_)) != NULL) {
      newSubTrack = new SubTrack(run->type(), *mergedData);
      delete mergedData;

      newSubTrack->next_ =  run->next_->next_;
      if (newSubTrack->next_ != NULL)
	newSubTrack->next_->pred_ = newSubTrack;

      if (run->pred_ == NULL) {
	subTracks_ = newSubTrack;
      }
      else {
	run->pred_->next_ = newSubTrack;
	newSubTrack->pred_ = run->pred_;
      }

      if (run->next_ == lastSubTrack_)
	lastSubTrack_ = newSubTrack;

      delete run->next_;
      delete run;
      run = newSubTrack;
    }
    else {
      run = run->next_;
    }
  }

  countSubTracks();
}


void Track::checkConsistency()
{
  SubTrack *run, *last = NULL;
  long cnt = 0;

  for (run = subTracks_; run != NULL; last = run, run = run->next_) {
    cnt++;
    if (run->pred_ != last) 
      message(-3, "Track::checkConsistency: wrong pred pointer.");
  }

  if (last != lastSubTrack_)
    message(-3, "Track::checkConsistency: wrong last pointer.");

  if (cnt != nofSubTracks_)
    message(-3, "Track::checkConsistency: wrong sub track counter.");
}

// Inserts 'newSubTrack' after existing sub track 'subTrack'. If 'subTrack'
// is NULL it will be prepended.
void Track::insertSubTrackAfter(SubTrack *subTrack, SubTrack *newSubTrack)
{
  if (subTrack == NULL) {
    if (subTracks_ == NULL) {
      subTracks_ = lastSubTrack_ = newSubTrack;
      newSubTrack->next_ = newSubTrack->pred_ = NULL;
    }
    else {
      newSubTrack->next_ = subTracks_;
      subTracks_->pred_ = newSubTrack;

      newSubTrack->pred_ = NULL;
      subTracks_ = newSubTrack;
    }
  }
  else {
    newSubTrack->next_ = subTrack->next_;
    if (newSubTrack->next_ != NULL) {
      newSubTrack->next_->pred_ = newSubTrack;
    }
    else {
      lastSubTrack_ = newSubTrack;
    }

    subTrack->next_ = newSubTrack;
    newSubTrack->pred_ = subTrack;
  }

  nofSubTracks_ += 1;

  checkConsistency();
}

// Removes given sub track from list. Returns the removed sub track.
SubTrack *Track::removeSubTrack(SubTrack *subTrack)
{
  if (subTrack->pred_ == NULL) {
    assert(subTrack == subTracks_);

    subTracks_ = subTrack->next_;

    if (subTracks_ != NULL) {
      subTracks_->pred_ = NULL;
    }
    else {
      lastSubTrack_ = NULL;
    }
  }
  else {
    subTrack->pred_->next_ = subTrack->next_;

    if (subTrack->next_ != NULL) {
      subTrack->next_->pred_ = subTrack->pred_;
    }
    else {
      lastSubTrack_ = subTrack->pred_;
    }
  }

  nofSubTracks_ -= 1;

  subTrack->next_ = subTrack->pred_ = NULL;

  checkConsistency();

  return subTrack;
}


// Fills provided buffer with an audio block that contains zero data
// encoded with given mode.
// encMode: encoding mode, see 'Track::readBlock()'
// mode: sector mode
// lba : absolute address of sector
// buf : caller provided buffer that is filled with 2352 bytes

void Track::encodeZeroData(int encMode, TrackData::Mode mode, long lba,
			   char *buf)
{
  if (encMode == 0) {
    memset(buf, 0, AUDIO_BLOCK_LEN);

    switch (mode) {
    case TrackData::AUDIO:
      break;
    case TrackData::MODE0:
      do_encode_L2((unsigned char *)buf, MODE_0, lba);
      scramble_L2((unsigned char *)buf);
      break;
    case TrackData::MODE1:
    case TrackData::MODE1_RAW:
      do_encode_L2((unsigned char *)buf, MODE_1, lba);
      scramble_L2((unsigned char *)buf);
      break;
    case TrackData::MODE2:
    case TrackData::MODE2_RAW:
      do_encode_L2((unsigned char *)buf, MODE_2, lba);
      scramble_L2((unsigned char *)buf);
      break;
    case TrackData::MODE2_FORM1:
    case TrackData::MODE2_FORM_MIX: // encode as form 1
      do_encode_L2((unsigned char *)buf, MODE_2_FORM_1, lba);
      scramble_L2((unsigned char *)buf);
      break;
    case TrackData::MODE2_FORM2:
      // setup sub header
      buf[16+2] = 0x20;
      buf[16+6] = 0x20;
      do_encode_L2((unsigned char *)buf, MODE_2_FORM_2, lba);
      scramble_L2((unsigned char *)buf);
      break;
    }
  }
  else if (encMode == 1) {
    switch (mode) {
    case TrackData::AUDIO:
      memset(buf, 0, AUDIO_BLOCK_LEN);
      break;
    case TrackData::MODE0:
      memset(buf, 0, MODE0_BLOCK_LEN);
      break;
    case TrackData::MODE1:
    case TrackData::MODE1_RAW:
      memset(buf, 0, MODE1_BLOCK_LEN);
      break;
    case TrackData::MODE2:
    case TrackData::MODE2_RAW:
    case TrackData::MODE2_FORM_MIX:
      memset(buf, 0, MODE2_BLOCK_LEN);
      break;
    case TrackData::MODE2_FORM1:
      memset(buf, 0, MODE2_BLOCK_LEN);
      break;
    case TrackData::MODE2_FORM2:
      memset(buf, 0, MODE2_BLOCK_LEN);
      // setup sub header
      buf[2] = 0x20;
      buf[6] = 0x20;
      break;
    }
  }
  else {
    message(-3, "Illegal encoding mode in 'Track::encodeZeroData()'.");
  }
}

void Track::addCdTextItem(CdTextItem *item)
{
  assert(CdTextItem::isTrackPack(item->packType()));

  cdtext_.add(item);
}

void Track::removeCdTextItem(CdTextItem::PackType type, int blockNr)
{
  cdtext_.remove(type, blockNr);
}

TrackReader::TrackReader(const Track *t) : reader(NULL)
{
  track_ = t;

  readPos_ = 0;
  readPosSample_ = 0;
  readSubTrack_ = NULL;
  open_ = 0;
}

TrackReader::~TrackReader()
{
  if (open_) {
    closeData();
  }

  track_ = NULL;
  readSubTrack_ = NULL;
}

void TrackReader::init(const Track *t)
{
  if (open_) {
    closeData();
  }

  track_ = t;
  readSubTrack_ = NULL;
}  

// initiates reading track
// return: 0: OK
//         1: data file could not be opened
//         2: could not seek to start position
int TrackReader::openData()
{
  assert(open_ == 0);
  assert(track_ != NULL);

  int ret = 0;

  open_ = 1;
  readPos_ = 0;
  readPosSample_ = 0;

  readSubTrack_ = track_->subTracks_;

  reader.init(readSubTrack_);

  if (readSubTrack_ != NULL) {
    ret = reader.openData();
  }

  return ret;
}

void TrackReader::closeData()
{
  if (open_) {
    open_ = 0;
    readPos_ = 0;
    readPosSample_ = 0;
    readSubTrack_ = NULL;
  
    reader.closeData();
  }
}


long TrackReader::readData(int encodingMode, long lba, char *buf, long len)
{
  long err = 0;
  long b;
  long offset; 

  assert(open_ != 0);

  if (readPos_ + len > track_->length_.lba()) {
    if ((len = track_->length_.lba() - readPos_) <= 0) {
      return 0;
    }
  }

  for (b = 0; b < len; b++) {
    if ((offset = readBlock(encodingMode, lba, (Sample*)buf)) == 0) {
      err = 1;
      break;
    }
    buf += offset;
    lba++;
  }

  readPos_ += b;

  return err == 0 ? b : -1;
}

// Reads one block from sub-tracks and performs the data encoding for the
// block.
// encodingMode: conrols how the data is encoded
//               0: returned data is always an audio block (2352 bytes),
//                  data blocks are completely encoded
//               1: data is returned mostly unencoded, the block size
//                  depends on the actual sub-track mode, MODE2_FORM1 and
//                  MODE2_FORM2 blocks are extended by the sub header and
//                  filled with 0 bytes to match the MODE2 block size
//                  (2336 bytes).
// lba: Logical block address that must be encoded into header of data blocks
// Return: 0 if error occured, else length of block that has been filled
//         
int TrackReader::readBlock(int encodingMode, long lba, Sample *buf)
{
  TrackData::Mode dataMode; // actual data mode of sub-track
  long actLen;
  long count; // amount of data the must be read from sub track
  long nread = 0;
  long offset = 0; // block length of returned data

  while (reader.readLeft() == 0) {
    // skip to next sub track with available data
    readSubTrack_ = readSubTrack_->next_;
    // next sub-track must exist since requested length matches available data
    assert(readSubTrack_ != NULL); 

    reader.init(readSubTrack_);
    if (reader.openData() != 0) {
      return 0;
    }
  }

  dataMode = readSubTrack_->mode(); // actual data mode

  if (dataMode == TrackData::AUDIO)
    count = SAMPLES_PER_BLOCK;
  else
    count = TrackData::dataBlockSize(dataMode);


  char *dataBuf = (char *)buf;

  if (encodingMode == 0) {
    offset = AUDIO_BLOCK_LEN;

    switch (dataMode) {
    case TrackData::AUDIO:
    case TrackData::MODE1_RAW:
    case TrackData::MODE2_RAW:
      break;
    case TrackData::MODE0:
    case TrackData::MODE1:
    case TrackData::MODE2:
    case TrackData::MODE2_FORM_MIX:
      dataBuf += 16;
      break;
    case TrackData::MODE2_FORM1:
      memset(dataBuf + 16, 0, 8); // clear sub-header
      dataBuf += 24;
      break;
    case TrackData::MODE2_FORM2:
      memset(dataBuf + 16, 0, 8); // clear sub-header
      dataBuf[16+2] = 0x20;
      dataBuf[16+6] = 0x20;
      dataBuf += 24;
      break;
    }
  }
  else if (encodingMode == 1) {
    switch (dataMode) {
    case TrackData::AUDIO:
      offset = AUDIO_BLOCK_LEN;
      break;
    case TrackData::MODE0:
      offset = MODE0_BLOCK_LEN;
      break;
    case TrackData::MODE1:
      offset = MODE1_BLOCK_LEN;
      break;
    case TrackData::MODE1_RAW:
      offset = MODE1_BLOCK_LEN;
      break;
    case TrackData::MODE2:
    case TrackData::MODE2_FORM_MIX:
      offset = MODE2_BLOCK_LEN;
      break;
    case TrackData::MODE2_FORM1:
    case TrackData::MODE2_FORM2:
      offset = MODE2_BLOCK_LEN;
      memset(dataBuf, 0, MODE2_BLOCK_LEN);
      dataBuf += 8; // reserve space for sub-header
      break;
    case TrackData::MODE2_RAW:
      offset = MODE2_BLOCK_LEN;
      break;
    }
  }

  // gather one block of data, block length depends on 'dataMode'
  while (count > 0) {
    if (dataMode == TrackData::AUDIO)
      actLen = reader.readData(buf + nread, count);
    else 
      actLen = reader.readData((Sample *)(dataBuf + nread), count);

    if (actLen < 0) // indicates read error
      return 0;

    if (actLen != count) {
      // end of data in sub-track reached, skip to next sub-track
      readSubTrack_ = readSubTrack_->next_;

      // next sub-track must exist since requested length match available data
      assert(readSubTrack_ != NULL); 

      // mode of next sub-track must match 'dataMode', this is ensured
      // in 'update()'.
      assert(readSubTrack_->mode() == dataMode);

      reader.init(readSubTrack_);
      if (reader.openData() != 0) {
	return 0;
      }
    }

    count -= actLen;
    nread += actLen;
  }

  unsigned char *encBuf = (unsigned char *)buf;

  if (encodingMode == 0) {
    // encode data block according to 'dataMode'

    switch (dataMode) {
    case TrackData::AUDIO:
      break;
    case TrackData::MODE0:
      do_encode_L2(encBuf, MODE_0, lba);
      scramble_L2(encBuf);
      break;
    case TrackData::MODE1:
      do_encode_L2(encBuf, MODE_1, lba);
      scramble_L2(encBuf);
      break;
    case TrackData::MODE1_RAW:
      {
	Msf m(lba);

	if (int2bcd(m.min()) != encBuf[12] ||
	    int2bcd(m.sec()) != encBuf[13] ||
	    int2bcd(m.frac()) != encBuf[14]) {
	  // sector address mismatch -> rebuild L-EC since it covers the header
	  do_encode_L2(encBuf, MODE_1, lba);
	}

	scramble_L2(encBuf);
      }
      break;
    case TrackData::MODE2:
      do_encode_L2(encBuf, MODE_2, lba);
      scramble_L2(encBuf);
      break;
    case TrackData::MODE2_FORM1:
      do_encode_L2(encBuf, MODE_2_FORM_1, lba);
      scramble_L2(encBuf);
      break;
    case TrackData::MODE2_FORM2:
      do_encode_L2(encBuf, MODE_2_FORM_2, lba);
      scramble_L2(encBuf);
      break;
    case TrackData::MODE2_FORM_MIX:
      if ((encBuf[16+2] & 0x20) != 0)
	do_encode_L2(encBuf, MODE_2_FORM_2, lba);
      else
	do_encode_L2(encBuf, MODE_2_FORM_1, lba);

      scramble_L2(encBuf);
      break;
    case TrackData::MODE2_RAW:
      {
	Msf m(lba);

	// L-EC does not cover sector header so it is relocatable
	// just update the sector address in the header
	encBuf[12] = int2bcd(m.min());
	encBuf[13] = int2bcd(m.sec());
	encBuf[14] = int2bcd(m.frac());

	scramble_L2(encBuf);
      }
      break;
    }
  }
  else if (encodingMode == 1) {
    switch (dataMode) {
    case TrackData::MODE2_FORM2:
      // add sub header for data form 2 sectors
      encBuf[2] = 0x20;
      encBuf[6] = 0x20;
      break;
    case TrackData::MODE1_RAW:
      // strip off sync and header
      memmove(encBuf, encBuf + 16, MODE1_BLOCK_LEN);
      break;
    case TrackData::MODE2_RAW:
      // strip off sync and header
      memmove(encBuf, encBuf + 16, MODE2_BLOCK_LEN);
      break;

    default:
      break;
    }
  }
 
  return offset;
}

long TrackReader::readTrackData(Sample *buf, long len)
{
  long actLen;
  long count = len;
  long nread = 0;

  while (count > 0) {
    actLen = reader.readData(buf + nread, count);

    if (actLen < 0) {
      return -1;
    }

    if (actLen != count) {
      // end of audio data in sub-track reached, skip to next sub-track
      readSubTrack_ = readSubTrack_->next_;

      // next sub-track must exists since requested length match available data
      assert(readSubTrack_ != NULL); 

      reader.init(readSubTrack_);
      if (reader.openData() != 0) {
	return -1;
      }
    }

    count -= actLen;
    nread += actLen;
  }

  return len;
}

// Seeks to specified sample.
// return: 0: OK
//        10: sample out of range
//        return code of 'TrackData::openData()'
int TrackReader::seekSample(unsigned long sample)
{
  int ret;

  assert(open_ != 0);

  // find sub track containing 'sample'
  SubTrack *st = track_->findSubTrack(sample);

  if (st == NULL) 
    return 10;

  // open sub track if necessary
  if (readSubTrack_ != st) {
    readSubTrack_ = st;
    reader.init(readSubTrack_);

    if ((ret = reader.openData()) != 0) 
      return ret;
  }

  assert(sample >= readSubTrack_->start());

  unsigned long offset = sample - readSubTrack_->start();

  // seek in sub track
  if ((ret = reader.seekSample(offset)) != 0)
    return ret;

  readPosSample_ = sample;

  return 0;
}

long TrackReader::readSamples(Sample *buf, long len)
{
  long ret;
  long i;
  assert(open_ != 0);

  if (readPosSample_ + (unsigned long)len > track_->length_.samples()) {
    if ((len = track_->length_.samples() - readPosSample_) <= 0) {
      return 0;
    }
  }

  if (track_->type_ == TrackData::AUDIO) {
    if ((ret = readTrackData(buf, len)) > 0)
      readPosSample_ += ret;
  }
  else {
    for (i = 0; i < len; i++) {
      buf[i].left(16000);
      buf[i].right(16000);
    }
    readPosSample_ += len;
    ret = len;
  }
  
  return ret;
}


SubTrackIterator::SubTrackIterator(const Track *t)
{
  track_ = t;
  iterator_ = NULL;
}


SubTrackIterator::~SubTrackIterator()
{
  track_ = NULL;
  iterator_ = NULL;
}

const SubTrack *SubTrackIterator::first()
{
  iterator_ = track_->subTracks_;

  return next();
}

const SubTrack *SubTrackIterator::next()
{
  if (iterator_ != NULL) {
    SubTrack *s = iterator_;

    iterator_ = iterator_->next_;
    return s;
  }
  else {
    return NULL;
  }
}

