/*
 *
 * $Id: k3baudiotrack.cpp 620139 2007-01-05 11:59:05Z trueg $
 * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * 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.
 * See the file "COPYING" for the exact licensing terms.
 */


#include "k3baudiotrack.h"
#include "k3baudiodoc.h"
#include "k3baudiodatasource.h"

#include <k3baudiodecoder.h>
#include <k3bcore.h>
#include <k3bcdtextvalidator.h>

#include <tqstring.h>

#include <kdebug.h>



class K3bAudioTrack::Private
{
public:
  Private() {
    cdTextValidator = new K3bCdTextValidator();
  }

  ~Private() {
    delete cdTextValidator;
  }

  K3bCdTextValidator* cdTextValidator;
};


K3bAudioTrack::K3bAudioTrack()
  : m_parent(0),
    m_copy(false),
    m_preEmp(false),
    m_index0Offset(150),
    m_prev(0),
    m_next(0),
    m_firstSource(0),
    m_currentSource(0),
    m_alreadyReadBytes(0),
    m_currentlyDeleting(false)
{
  d = new Private;
}


K3bAudioTrack::K3bAudioTrack( K3bAudioDoc* parent )
  : m_parent(parent),
    m_copy(false),
    m_preEmp(false),
    m_index0Offset(150),
    m_prev(0),
    m_next(0),
    m_firstSource(0),
    m_currentSource(0),
    m_alreadyReadBytes(0),
    m_currentlyDeleting(false)
{
  d = new Private;
}


K3bAudioTrack::~K3bAudioTrack()
{
  kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) " << this << endl;
  //
  // It is crucial that we do not emit the changed signal here because otherwise
  // the doc will delete us again once we are empty!
  //
  m_currentlyDeleting = true;

  // fix the list
  take();

  kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) deleting sources." << endl;

  // delete all sources
  while( m_firstSource )
    delete m_firstSource->take();

  kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) finished" << endl;

  delete d;
}


void K3bAudioTrack::emitChanged()
{
  if( m_parent )
    m_parent->slotTrackChanged( this );
}


void K3bAudioTrack::setArtist( const TQString& a )
{
  setPerformer( a );
}


void K3bAudioTrack::setPerformer( const TQString& a )
{
  TQString s( a );
  d->cdTextValidator->fixup( s );
  m_cdText.setPerformer(s);
  emitChanged();
}


void K3bAudioTrack::setTitle( const TQString& t )
{
  TQString s( t );
  d->cdTextValidator->fixup( s );
  m_cdText.setTitle(s);
  emitChanged();
}


void K3bAudioTrack::setArranger( const TQString& t )
{
  TQString s( t );
  d->cdTextValidator->fixup( s );
  m_cdText.setArranger(s);
  emitChanged();
}


void K3bAudioTrack::setSongwriter( const TQString& t )
{
  TQString s( t );
  d->cdTextValidator->fixup( s );
  m_cdText.setSongwriter(s);
  emitChanged();
}


void K3bAudioTrack::setComposer( const TQString& t )
{
  TQString s( t );
  d->cdTextValidator->fixup( s );
  m_cdText.setComposer(s);
  emitChanged();
}


void K3bAudioTrack::setIsrc( const TQString& t )
{
  m_cdText.setIsrc(t);
  emitChanged();
}


void K3bAudioTrack::setCdTextMessage( const TQString& t )
{
  TQString s( t );
  d->cdTextValidator->fixup( s );
  m_cdText.setMessage(s);
  emitChanged();
}


void K3bAudioTrack::setCdText( const K3bDevice::TrackCdText& cdtext )
{
  m_cdText = cdtext;
  emitChanged();
}


K3bAudioDataSource* K3bAudioTrack::lastSource() const
{
  K3bAudioDataSource* s = m_firstSource;
  while( s && s->next() )
    s = s->next();
  return s;
}


bool K3bAudioTrack::inList() const
{
  if( doc() )
    return ( doc()->firstTrack() == this || m_prev != 0 );
  else
    return false;
}


K3b::Msf K3bAudioTrack::length() const
{
  K3b::Msf length;
  K3bAudioDataSource* source = m_firstSource;
  while( source ) {
    length += source->length();
    source = source->next();
  }
  return length;
}


KIO::filesize_t K3bAudioTrack::size() const
{
  return length().audioBytes();
}


unsigned int K3bAudioTrack::trackNumber() const
{
  if( m_prev )
    return m_prev->trackNumber() + 1;
  else
    return 1;
}


K3b::Msf K3bAudioTrack::index0() const
{
  // we save the index0Offset as length of the resulting pregap
  // this way the length of the track does not need to be ready
  // when creating the track.
  return length() - m_index0Offset;
}


K3b::Msf K3bAudioTrack::postGap() const
{
  if( next() )
    return m_index0Offset;
  else
    return 0;
}


void K3bAudioTrack::setIndex0( const K3b::Msf& msf )
{
  if( msf == 0 )
    m_index0Offset = 0;
  else
    m_index0Offset = length() - msf;
}


K3bAudioTrack* K3bAudioTrack::take()
{
  if( inList() ) {
    if( !m_prev )
      doc()->setFirstTrack( m_next );
    if( !m_next )
      doc()->setLastTrack( m_prev );
    
    if( m_prev )
      m_prev->m_next = m_next;
    if( m_next )
      m_next->m_prev = m_prev;
    
    m_prev = m_next = 0;

    // remove from doc
    if( m_parent )
      m_parent->slotTrackRemoved(this);
    m_parent = 0;
  }

  return this;
}


void K3bAudioTrack::moveAfter( K3bAudioTrack* track )
{
  kdDebug() << "(K3bAudioTrack::moveAfter( " << track << " )" << endl;
  if( !track ) {
    if( !doc() ) {
      kdDebug() << "(K3bAudioTrack::moveAfter) no parent set" << endl;
      return;
    }
      
    // make sure we do not mess up the list
    if( doc()->lastTrack() )
      moveAfter( doc()->lastTrack() );
    else {
      doc()->setFirstTrack( take() );
      doc()->setLastTrack( this );
    }
  }
  else if( track == this ) {
    kdDebug() << "(K3bAudioTrack::moveAfter) trying to move this after this." << endl;
    return;
  }
  else {
    // remove this from the list
    take();
    
    // set the new parent doc
    m_parent = track->doc();
    
    K3bAudioTrack* oldNext = track->m_next;
    
    // set track as prev
    track->m_next = this;
    m_prev = track;
    
    // set oldNext as next
    if( oldNext )
      oldNext->m_prev = this;
    m_next = oldNext;

    if( !m_prev )
      doc()->setFirstTrack( this );    
    if( !m_next )
      doc()->setLastTrack( this );
  }

  emitChanged();
}


void K3bAudioTrack::moveAhead( K3bAudioTrack* track )
{
  if( !track ) {
    if( !doc() ) {
      kdDebug() << "(K3bAudioTrack::moveAfter) no parent set" << endl;
      return;
    }

    // make sure we do not mess up the list
    if( doc()->firstTrack() )
      moveAhead( doc()->firstTrack() );
    else {
      doc()->setFirstTrack( take() );
      doc()->setLastTrack( this );
    }
  }
  else if( track == this ) {
    kdDebug() << "(K3bAudioTrack::moveAhead) trying to move this ahead of this." << endl;
    return;
  }
  else {
    // remove this from the list
    take();
    
    // set the new parent doc
    m_parent = track->doc();
    
    K3bAudioTrack* oldPrev = track->m_prev;
    
    // set track as next
    m_next = track;
    track->m_prev = this;
    
    // set oldPrev as prev
    m_prev = oldPrev;
    if( oldPrev )
      oldPrev->m_next = this;
    
    if( !m_prev )
      doc()->setFirstTrack( this );
    if( !m_next )
      doc()->setLastTrack( this );
  }

  emitChanged();
}


void K3bAudioTrack::merge( K3bAudioTrack* trackToMerge, K3bAudioDataSource* sourceAfter )
{
  kdDebug() << "(K3bAudioTrack::merge) " << trackToMerge << " into " << this << endl;
  if( this == trackToMerge ) {
    kdDebug() << "(K3bAudioTrack::merge) trying to merge this with this." << endl;
    return;
  }

  // remove the track to merge to make sure it does not get deleted by the doc too early
  trackToMerge->take();

  // in case we prepend all of trackToMerge's sources
  if( !sourceAfter ) {
    kdDebug() << "(K3bAudioTrack::merge) merging " << trackToMerge->firstSource() << endl;
    if( m_firstSource ) {
      trackToMerge->firstSource()->moveAhead( m_firstSource );
    }
    else {
      addSource( trackToMerge->firstSource()->take() );
    }
    sourceAfter = m_firstSource;
  }

  kdDebug() << "(K3bAudioTrack::merge) now merge the other sources." << endl;
  // now merge all sources into this track
  while( trackToMerge->firstSource() ) {
    K3bAudioDataSource* s = trackToMerge->firstSource();
    kdDebug() << "(K3bAudioTrack::merge) merging source " << s << " from track " << s->track() << " into track " 
	      << this << " after source " << sourceAfter << endl;
    s->moveAfter( sourceAfter );
    sourceAfter = s;
  }
  
  // TODO: should we also merge the indices?

  // now we can safely delete the track we merged
  delete trackToMerge;

  kdDebug() << "(K3bAudioTrack::merge) finished"  << endl;

  emitChanged();
}


void K3bAudioTrack::setFirstSource( K3bAudioDataSource* source )
{
  // reset the reading stuff since this might be a completely new source list
  m_currentSource = 0;
  m_alreadyReadBytes = 0;

  m_firstSource = source;
  while( source ) {
    source->m_track = this;
    source = source->next();
  }

  emitChanged();
}


void K3bAudioTrack::addSource( K3bAudioDataSource* source )
{
  if( !source )
    return;

  K3bAudioDataSource* s = m_firstSource;
  while( s && s->next() )
    s = s->next();
  if( s )
    source->moveAfter( s );
  else
    setFirstSource( source->take() );
}


void K3bAudioTrack::sourceChanged( K3bAudioDataSource* )
{
  if( m_currentlyDeleting )
    return;

  // TODO: update indices

  if( m_index0Offset > length() )
    m_index0Offset = length()-1;

  emitChanged();
}


int K3bAudioTrack::numberSources() const
{
  K3bAudioDataSource* source = m_firstSource;
  int i = 0;
  while( source ) {
    source = source->next();
    ++i;
  }
  return i;
}


bool K3bAudioTrack::seek( const K3b::Msf& msf )
{
  K3bAudioDataSource* source = m_firstSource;

  K3b::Msf pos;
  while( source && pos + source->length() < msf ) {
    pos += source->length();
    source = source->next();
  }

  if( source ) {
    m_currentSource = source;
    m_alreadyReadBytes = msf.audioBytes();
    return source->seek( msf - pos );
  }
  else
    return false;
}


int K3bAudioTrack::read( char* data, unsigned int max )
{
  if( !m_currentSource ) {
    m_currentSource = m_firstSource;
    if( m_currentSource )
      m_currentSource->seek(0);
    m_alreadyReadBytes = 0;
  }

  int readData = m_currentSource->read( data, max );
  if( readData == 0 ) {
    m_currentSource = m_currentSource->next();
    if( m_currentSource ) {
      m_currentSource->seek(0);
      return read( data, max ); // read from next source
    }
  }

  m_alreadyReadBytes += readData;

  return readData;
}


K3bAudioTrack* K3bAudioTrack::copy() const
{
  K3bAudioTrack* track = new K3bAudioTrack();

  track->m_copy = m_copy;
  track->m_preEmp = m_preEmp;
  track->m_index0Offset = m_index0Offset;
  track->m_cdText = m_cdText;
  K3bAudioDataSource* source = m_firstSource;
  while( source ) {
    track->addSource( source->copy() );
    source = source->next();
  }

  return track;
}


K3bAudioTrack* K3bAudioTrack::split( const K3b::Msf& pos )
{
  if( pos < length() ) {
    // search the source
    // pos will be the first sector of the new track
    K3b::Msf currentPos;
    K3bAudioDataSource* source = firstSource();
    while( source && currentPos + source->length() <= pos ) {
      currentPos += source->length();
      source = source->next();
    }

    K3bAudioDataSource* splitSource = 0;
    if( currentPos > 0 && currentPos == pos ) {
      // no need to split a source
      splitSource = source;
    }
    else {
      splitSource = source->split( pos - currentPos );
    }

    // the new track should include all sources from splitSource and below
    K3bAudioTrack* splitTrack = new K3bAudioTrack();
    splitTrack->m_cdText = m_cdText;
    source = splitSource;
    while( source ) {
      K3bAudioDataSource* addSource = source;
      source = source->next();
      splitTrack->addSource( addSource );
    }

    kdDebug() << "(K3bAudioTrack) moving track " << splitTrack << " after this (" << this << ") with parent " << doc() << endl;
    splitTrack->moveAfter( this );

    return splitTrack;
  }
  else
    return 0;
}


K3bDevice::Track K3bAudioTrack::toCdTrack() const
{
  if( !inList() )
    return K3bDevice::Track();

  K3b::Msf firstSector;
  K3bAudioTrack* track = doc()->firstTrack();
  while( track != this ) {
    firstSector += track->length();
    track = track->next();
  }

  K3bDevice::Track cdTrack( firstSector, 
			    firstSector + length() - 1,
			    K3bDevice::Track::AUDIO );

    // FIXME: auch im audiotrack copy permitted
  cdTrack.setCopyPermitted( !copyProtection() );
  cdTrack.setPreEmphasis( preEmp() );
  
  // FIXME: add indices != 0
  
  // no index 0 for the last track. Or should we allow this???
  if( doc()->lastTrack() != this )
    cdTrack.setIndex0( index0() );
  
  // FIXME: convert to TQCString
  //    cdTrack.setIsrc( isrc() );
  
  return cdTrack;
}


void K3bAudioTrack::debug()
{
  kdDebug() << "Track " << this << endl
	    << "  Prev: " << m_prev << endl
	    << "  Next: " << m_next << endl
	    << "  Sources:" << endl;
  K3bAudioDataSource* s = m_firstSource;
  while( s ) {
    kdDebug() << "  " << s << " - Prev: " << s->prev() << " Next: " << s->next() << endl;
    s = s->next();
  }
}