/* 
 *
 * $Id: k3bcdtext.cpp 619556 2007-01-03 17:38:12Z trueg $
 * Copyright (C) 2003-2007 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 <config.h>

#include "k3bcdtext.h"
#include "k3bcrc.h"

#include <k3bdebug.h>

#include <tqtextcodec.h>

#include <string.h>


namespace K3bDevice {

  struct cdtext_pack {
    unsigned char id1;
    unsigned char id2;
    unsigned char id3;
#ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN
    unsigned char dbcc:       1;
    unsigned char blocknum:   3;
    unsigned char charpos:    4;
#else
    unsigned char charpos:    4;
    unsigned char blocknum:   3;
    unsigned char dbcc:       1;
#endif
    unsigned char data[12];
    unsigned char crc[2];
  };

  /**
   * This one is taken from cdrecord
   */
  struct text_size_block {
    char charcode;
    char first_track;
    char last_track;
    char copyr_flags;
    char pack_count[16];
    char last_seqnum[8];
    char language_codes[8];
  };

  void debugRawTextPackData( const unsigned char* data, int dataLen )
  {
    k3bDebug() << endl << " id1    | id2    | id3    | charps | blockn | dbcc | data           | crc |" << endl;
  
    cdtext_pack* pack = (cdtext_pack*)data;
  
    for( int i = 0; i < dataLen/18; ++i ) {
      TQString s;
      s += TQString( " %1 |" ).arg( pack[i].id1, 6, 16 );
      s += TQString( " %1 |" ).arg( pack[i].id2, 6 );
      s += TQString( " %1 |" ).arg( pack[i].id3, 6 );
      s += TQString( " %1 |" ).arg( pack[i].charpos, 6 );
      s += TQString( " %1 |" ).arg( pack[i].blocknum, 6 );
      s += TQString( " %1 |" ).arg( pack[i].dbcc, 4 );
//       char str[12];
//       sprintf( str, "%c%c%c%c%c%c%c%c%c%c%c%c",
//         pack[i].data[0] == '\0' ? '�' : pack[i].data[0],
//         pack[i].data[1] == '\0' ? '�' : pack[i].data[1],
//         pack[i].data[2] == '\0' ? '�' : pack[i].data[2],
//         pack[i].data[3] == '\0' ? '�' : pack[i].data[3],
//         pack[i].data[4] == '\0' ? '�' : pack[i].data[4],
//         pack[i].data[5] == '\0' ? '�' : pack[i].data[5],
//         pack[i].data[6] == '\0' ? '�' : pack[i].data[6],
//         pack[i].data[7] == '\0' ? '�' : pack[i].data[7],
//         pack[i].data[8] == '\0' ? '�' : pack[i].data[8],
//         pack[i].data[9] == '\0' ? '�' : pack[i].data[9],
//         pack[i].data[10] == '\0' ? '�' : pack[i].data[10],
//         pack[i].data[11] == '\0' ? '�' : pack[i].data[11] );
//       s += TQString( " %1 |" ).arg( "'" + TQCString(str,13) + "'", 14 );
//       TQ_UINT16 crc = pack[i].crc[0]<<8|pack[i].crc[1];
//       s += TQString( " %1 |" ).arg( crc );
      k3bDebug() << s << endl;
    }
  }

}



K3bDevice::CdText::CdText()
{
}


K3bDevice::CdText::CdText( const K3bDevice::CdText& text )
  : TQValueVector<K3bDevice::TrackCdText>( text ),
    m_title( text.title() ),
    m_performer( text.performer() ),
    m_songwriter( text.songwriter() ),
    m_composer( text.composer() ),
    m_arranger( text.arranger() ),
    m_message( text.message() ),
    m_discId( text.discId() ),
    m_upcEan( text.upcEan() )
{
}


K3bDevice::CdText::CdText( const unsigned char* data, int len )
{
  setRawPackData( data, len );
}


K3bDevice::CdText::CdText( const TQByteArray& b )
{
  setRawPackData( b );
}


K3bDevice::CdText::CdText( int size )
{
  resize( size );
}


void K3bDevice::CdText::clear()
{
  TQValueVector<TrackCdText>::clear();

  m_title.setLength(0);
  m_performer.setLength(0);
  m_songwriter.setLength(0);
  m_composer.setLength(0);
  m_arranger.setLength(0);
  m_message.setLength(0);
  m_discId.setLength(0);
  m_upcEan.setLength(0);
}


void K3bDevice::CdText::setRawPackData( const unsigned char* data, int len )
{
  clear();

  int r = len%18;
  if( r > 0 && r != 4 ) {
    k3bDebug() << "(K3bDevice::CdText) invalid cdtext size: " << len << endl;
  }
  else if( len-r > 0 ) {
    debugRawTextPackData( &data[r], len-r );

    cdtext_pack* pack = (cdtext_pack*)&data[r];


    for( int i = 0; i < (len-r)/18; ++i ) {

      if( pack[i].dbcc ) {
	k3bDebug() << "(K3bDevice::CdText) Double byte code not supported" << endl;
	return;
      }

      //
      // For some reason all crc bits are inverted.
      //
      pack[i].crc[0] ^= 0xff;
      pack[i].crc[1] ^= 0xff;

      TQ_UINT16 crc = calcX25( reinterpret_cast<unsigned char*>(&pack[i]), 18 );

      pack[i].crc[0] ^= 0xff;
      pack[i].crc[1] ^= 0xff;

      if( crc != 0x0000 )
	k3bDebug() << "(K3bDevice::CdText) CRC invalid!" << endl;


      //
      // pack.data has a length of 12
      //
      // id1 tells us the tracknumber of the data (0 for global)
      // data may contain multiple \0. In that case after every \0 the track number increases 1
      //

      char* nullPos = (char*)pack[i].data - 1;
	
      unsigned int trackNo = pack[i].id2;

      while( nullPos ) {
	if( count() < trackNo )
	  resize( trackNo );

	char* nextNullPos = (char*)::memchr( nullPos+1, '\0', 11 - (nullPos - (char*)pack[i].data) );
	TQString txtstr;	    
	if( nextNullPos ) // take all chars up to the next null
	  txtstr = TQString::fromLatin1( (char*)nullPos+1, nextNullPos - nullPos - 1 );
	else // take all chars to the end of the pack data (12 bytes)
	  txtstr = TQString::fromLatin1( (char*)nullPos+1, 11 - (nullPos - (char*)pack[i].data) );
	  
	//
	// a tab character means to use the same as for the previous track
	//
	if( txtstr == "\t" )
	  txtstr = textForPackType( pack[i].id1, trackNo-1 );

	switch( pack[i].id1 ) {
	case 0x80: // Title
	  if( trackNo == 0 )
	    m_title.append( txtstr );
	  else
	    at(trackNo-1).m_title.append( txtstr );
	  break;

	case 0x81: // Performer
	  if( trackNo == 0 )
	    m_performer.append( txtstr );
	  else
	    at(trackNo-1).m_performer.append( txtstr );
	  break;

	case 0x82: // Writer
	  if( trackNo == 0 )
	    m_songwriter.append( txtstr );
	  else
	    at(trackNo-1).m_songwriter.append( txtstr );
	  break;

	case 0x83: // Composer
	  if( trackNo == 0 )
	    m_composer.append( txtstr );
	  else
	    at(trackNo-1).m_composer.append( txtstr );
	  break;

	case 0x84: // Arranger
	  if( trackNo == 0 )
	    m_arranger.append( txtstr );
	  else
	    at(trackNo-1).m_arranger.append( txtstr );
	  break;

	case 0x85: // Message
	  if( trackNo == 0 )
	    m_message.append( txtstr );
	  else
	    at(trackNo-1).m_message.append( txtstr );
	  break;

	case 0x86: // Disc identification
	  // only global
	  if( trackNo == 0 )
	    m_discId.append( txtstr );
	  break;

	case 0x8e: // Upc or isrc
	  if( trackNo == 0 )
	    m_upcEan.append( txtstr );
	  else
	    at(trackNo-1).m_isrc.append( txtstr );
	  break;

	  // TODO: support for binary data
	  // 0x88: TOC 
	  // 0x89: second TOC
	  // 0x8f: Size information

	default:
	  break;
	}
	  
	trackNo++;
	nullPos = nextNullPos;
      }
    }

    // remove empty fields at the end
    unsigned int i = count();
    while( i > 0 && at(i-1).isEmpty() )
      --i;
    resize( i );
  }
  else
    k3bDebug() << "(K3bDevice::CdText) zero-sized CD-TEXT: " << len << endl; 
}


void K3bDevice::CdText::setRawPackData( const TQByteArray& b )
{
  setRawPackData( reinterpret_cast<const unsigned char*>(b.data()), b.size() );
}

TQByteArray K3bDevice::CdText::rawPackData() const
{ 
  // FIXME: every pack block may only consist of up to 255 packs.

  unsigned int pc = 0;
  unsigned int alreadyCountedPacks = 0;


  //
  // prepare the size information block
  //
  text_size_block tsize;
  ::memset( &tsize, 0, sizeof(text_size_block) );
  tsize.charcode = 0;              // ISO 8859-1
  tsize.first_track = 1;
  tsize.last_track = count();
  tsize.pack_count[0xF] = 3;
  tsize.language_codes[0] = 0x09;  // English (from cdrecord)


  //
  // create the CD-Text packs
  //
  TQByteArray data(0);
  for( int i = 0; i <= 6; ++i ) {
    if( textLengthForPackType( 0x80 | i ) ) {
      appendByteArray( data, createPackData( 0x80 | i, pc ) );
      tsize.pack_count[i] = pc - alreadyCountedPacks;
      alreadyCountedPacks = pc;
    }
  }
  if( textLengthForPackType( 0x8E ) ) {
    appendByteArray( data, createPackData( 0x8E, pc ) );
    tsize.pack_count[0xE] = pc - alreadyCountedPacks;
    alreadyCountedPacks = pc;
  }


  // pc is the number of the next pack and we add 3 size packs
  tsize.last_seqnum[0] = pc + 2;


  //
  // create the size info packs
  //
  unsigned int dataFill = data.size();
  data.resize( data.size() + 3 * sizeof(cdtext_pack) );
  for( int i = 0; i < 3; ++i ) {
    cdtext_pack pack;
    ::memset( &pack, 0, sizeof(cdtext_pack) );
    pack.id1 = 0x8F;
    pack.id2 = i;
    pack.id3 = pc+i;
    ::memcpy( pack.data, &reinterpret_cast<char*>(&tsize)[i*12], 12 );
    savePack( &pack, data, dataFill );
  }

  //
  // add MMC header
  //
  TQByteArray a( 4 );
  a[0] = (data.size()+2)>>8 & 0xff;
  a[1] = (data.size()+2) & 0xff;
  a[2] = a[3] = 0;
  appendByteArray( a, data );

  return a;
}


void K3bDevice::CdText::appendByteArray( TQByteArray& a, const TQByteArray& b ) const
{
  unsigned int oldSize = a.size();
  a.resize( oldSize + b.size() );
  ::memcpy( &a.data()[oldSize], b.data(), b.size() );
}


// this method also creates completely empty packs
TQByteArray K3bDevice::CdText::createPackData( int packType, unsigned int& packCount ) const
{
  TQByteArray data;
  unsigned int dataFill = 0;
  TQCString text = encodeCdText( textForPackType( packType, 0 ) );
  unsigned int currentTrack = 0;
  unsigned int textPos = 0;
  unsigned int packPos = 0;

  //
  // initialize the first pack
  //
  cdtext_pack pack;
  ::memset( &pack, 0, sizeof(cdtext_pack) );
  pack.id1 = packType;
  pack.id3 = packCount;

  //
  // We break this loop when all texts have been packed
  //
  while( 1 ) {
    //
    // Copy as many bytes as possible into the pack
    //
    int copyBytes = TQMIN( 12-packPos, text.length()-textPos );
    ::memcpy( reinterpret_cast<char*>(&pack.data[packPos]), &text.data()[textPos], copyBytes );
    textPos += copyBytes;
    packPos += copyBytes;


    //
    // Check if the packdata is full
    //
    if( packPos > 11 ) {

      savePack( &pack, data, dataFill );
      ++packCount;

      //
      // reset the pack
      //
      ::memset( &pack, 0, sizeof(cdtext_pack) );
      pack.id1 = packType;
      pack.id2 = currentTrack;
      pack.id3 = packCount;
      packPos = 0;

      // update the charpos in case we continue a text in the next pack
      if( textPos <= text.length() )
	pack.charpos = ( textPos > 15 ? 15 : textPos );
    }


    //
    // Check if we have no text data left
    //
    if( textPos >= text.length() ) {

      // add one zero spacer byte
      ++packPos;

      ++currentTrack;

      // Check if all texts have been packed
      if( currentTrack > count() ) {
	savePack( &pack, data, dataFill );
	++packCount;

	data.resize( dataFill );
	return data;
      }

      // next text block
      text = encodeCdText( textForPackType( packType, currentTrack ) );
      textPos = 0;
    }
  }
}


void K3bDevice::CdText::savePack( cdtext_pack* pack, TQByteArray& data, unsigned int& dataFill ) const
{
  // create CRC
  TQ_UINT16 crc = calcX25( reinterpret_cast<unsigned char*>(pack), sizeof(cdtext_pack)-2 );

  // invert for Redbook compliance
  crc ^= 0xffff;

  pack->crc[0] = (crc>>8) & 0xff;
  pack->crc[1] = crc & 0xff;


  // append the pack to data  
  if( data.size() < dataFill + sizeof(cdtext_pack) )
    data.resize( dataFill + sizeof(cdtext_pack), TQGArray::SpeedOptim );

  ::memcpy( &data.data()[dataFill], reinterpret_cast<char*>( pack ), sizeof(cdtext_pack) );

  dataFill += sizeof(cdtext_pack);
}


// track 0 means global cdtext
const TQString& K3bDevice::CdText::textForPackType( int packType, unsigned int track ) const
{
  switch( packType ) {
  default:
  case 0x80:
    if( track == 0 )
      return title();
    else
      return at(track-1).title();

  case 0x81:
    if( track == 0 )
      return performer();
    else
      return at(track-1).performer();

  case 0x82:
    if( track == 0 )
      return songwriter();
    else
      return at(track-1).songwriter();

  case 0x83:
    if( track == 0 )
      return composer();
    else
      return at(track-1).composer();

  case 0x84:
    if( track == 0 )
      return arranger();
    else
      return at(track-1).arranger();

  case 0x85:
    if( track == 0 )
      return message();
    else
      return at(track-1).message();

  case 0x86:
    if( track == 0 )
      return discId();
    else 
      return TQString();

//   case 0x87:
//     if( track == 0 )
//       return genre();
//     else
//       return at(track-1).title();

  case 0x8E:
    if( track == 0 )
      return upcEan();
    else
      return at(track-1).isrc();
  }
}


// count the overall length of a certain packtype texts
unsigned int K3bDevice::CdText::textLengthForPackType( int packType ) const
{
  unsigned int len = 0;
  for( unsigned int i = 0; i <= count(); ++i )
    len += encodeCdText( textForPackType( packType, i ) ).length();
  return len;
}


TQCString K3bDevice::encodeCdText( const TQString& s, bool* illegalChars )
{
  if( illegalChars )
    *illegalChars = false;

  // TODO: do this without QT
  TQTextCodec* codec = TQTextCodec::codecForName("ISO8859-1");
  if( codec ) {
    TQCString encoded = codec->fromUnicode( s );
    return encoded;
  }
  else {
    TQCString r(s.length()+1);

    for( unsigned int i = 0; i < s.length(); ++i ) {
      if( s[i].latin1() == 0 ) { // non-ASCII character
	r[i] = ' ';
	if( illegalChars )
	  *illegalChars = true;
      }
      else
	r[i] = s[i].latin1();
    }

    return r;
  }
}


bool K3bDevice::CdText::checkCrc( const TQByteArray& rawData )
{
  return checkCrc( reinterpret_cast<const unsigned char*>(rawData.data()), rawData.size() );
}


bool K3bDevice::CdText::checkCrc( const unsigned char* data, int len )
{
  int r = len%18;
  if( r > 0 && r != 4 ) {
    k3bDebug() << "(K3bDevice::CdText) invalid cdtext size: " << len << endl;
    return false;
  }
  else {
    len -= r;

    // TODO: what if the crc field is not used? All zeros?

    for( int i = 0; i < (len-r)/18; ++i ) {
      cdtext_pack* pack = (cdtext_pack*)&data[r];

      //
      // For some reason all crc bits are inverted.
      //
      pack[i].crc[0] ^= 0xff;
      pack[i].crc[1] ^= 0xff;

      int crc = calcX25( reinterpret_cast<unsigned char*>(&pack[i]), 18 );

      pack[i].crc[0] ^= 0xff;
      pack[i].crc[1] ^= 0xff;

      if( crc != 0x0000 )
	return false;
    }

    return true;
  }
}


void K3bDevice::CdText::debug() const
{
  // debug the stuff
  k3bDebug() << "CD-TEXT data:" << endl
	    << "Global:" << endl
	    << "  Title:      '" << title() << "'" << endl
	    << "  Performer:  '" << performer() << "'" << endl
	    << "  Songwriter: '" << songwriter() << "'" << endl
	    << "  Composer:   '" << composer() << "'" << endl
	    << "  Arranger:   '" << arranger() << "'" << endl
	    << "  Message:    '" << message() << "'" << endl
	    << "  Disc ID:    '" << discId() << "'" << endl
	    << "  Upc Ean:    '" << upcEan() << "'" << endl;
  for( unsigned int i = 0; i < count(); ++i ) {
    k3bDebug() << "Track " << (i+1) << ":" << endl
	      << "  Title:      '" << at(i).title() << "'" << endl
	      << "  Performer:  '" << at(i).performer() << "'" << endl
	      << "  Songwriter: '" << at(i).songwriter() << "'" << endl
	      << "  Composer:   '" << at(i).composer() << "'" << endl
	      << "  Arranger:   '" << at(i).arranger() << "'" << endl
	      << "  Message:    '" << at(i).message() << "'" << endl
	      << "  Isrc:       '" << at(i).isrc() << "'" << endl;
  }
}


bool K3bDevice::TrackCdText::operator==( const K3bDevice::TrackCdText& other ) const
{
  return( m_title == other.m_title &&
	  m_performer == other.m_performer &&
	  m_songwriter == other.m_songwriter &&
	  m_composer == other.m_composer &&
	  m_arranger == other.m_arranger &&
	  m_message == other.m_message &&
	  m_isrc == other.m_isrc );
}


bool K3bDevice::TrackCdText::operator!=( const K3bDevice::TrackCdText& other ) const
{
  return !operator==( other );
}


bool K3bDevice::CdText::operator==( const K3bDevice::CdText& other ) const
{
  return( m_title == other.m_title &&
	  m_performer == other.m_performer &&
	  m_songwriter == other.m_songwriter &&
	  m_composer == other.m_composer &&
	  m_arranger == other.m_arranger &&
	  m_message == other.m_message &&
	  m_discId == other.m_discId &&
	  m_upcEan == other.m_upcEan &&
	  TQValueVector<TrackCdText>::operator==( other ) );
}


bool K3bDevice::CdText::operator!=( const K3bDevice::CdText& other ) const
{
  return !operator==( other );
}