/*
 *
 * $Id: k3bsoxencoder.cpp 731310 2007-10-31 10:30: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 <config.h>

#include "k3bsoxencoder.h"
#include "base_k3bsoxencoderconfigwidget.h"

#include <k3bprocess.h>
#include <k3bcore.h>
#include <k3bexternalbinmanager.h>
#include <k3bpluginfactory.h>

#include <kdebug.h>
#include <tdeconfig.h>
#include <tdelocale.h>

#include <tqfileinfo.h>
#include <tqfile.h>
#include <tqvalidator.h>
#include <tqlineedit.h>
#include <tqcombobox.h>
#include <tqcheckbox.h>
#include <tqlayout.h>
#include <tqregexp.h>

#include <sys/types.h>
#include <sys/wait.h>


K_EXPORT_COMPONENT_FACTORY( libk3bsoxencoder, K3bPluginFactory<K3bSoxEncoder>( "libk3bsoxencoder" ) )


// the sox external program
class K3bSoxProgram : public K3bExternalProgram
{
 public:
  K3bSoxProgram()
    : K3bExternalProgram( "sox" ) {
  }

  bool scan( const TQString& p ) {
    if( p.isEmpty() )
      return false;

    TQString path = p;
    TQFileInfo fi( path );
    if( fi.isDir() ) {
      if( path[path.length()-1] != '/' )
        path.append("/");
      path.append("sox");
    }

    if( !TQFile::exists( path ) )
      return false;

    // probe version
    TDEProcess vp;
    K3bProcessOutputCollector out( &vp );

    vp << path << "-h";
    if( vp.start( TDEProcess::Block, TDEProcess::AllOutput ) ) {
      TQRegExp versionRe ("sox: +(SoX )?(Version |v)([^\\n]+)");
      // Should match:
      //  * sox: Version 12.2.3
      //  * sox: SoX Version 12.18.1
      //  * sox: SoX v14.0.0
      //  * sox:      SoX v14.4.0
      int pos = versionRe.search( out.output() );
      if( pos > 0 ) {
        TQString version = versionRe.cap(3);

        K3bExternalBin* bin = new K3bExternalBin( this );

        bin->path = path;
        bin->version = version;

        addBin( bin );

        return true;
      }
    }
    return false;
  }
};


class K3bSoxEncoder::Private
{
 public:
  Private()
    : process(0) {
  }

  K3bProcess* process;
  TQString fileName;
};


K3bSoxEncoder::K3bSoxEncoder( TQObject* parent, const char* name )
  : K3bAudioEncoder( parent, name )
{
  if( k3bcore->externalBinManager()->program( "sox" ) == 0 )
    k3bcore->externalBinManager()->addProgram( new K3bSoxProgram() );

  d = new Private();
}


K3bSoxEncoder::~K3bSoxEncoder()
{
  delete d->process;
  delete d;
}


void K3bSoxEncoder::finishEncoderInternal()
{
  if( d->process ) {
    if( d->process->isRunning() ) {
      ::close( d->process->stdinFd() );

      // this is kind of evil...
      // but we need to be sure the process exited when this method returnes
      ::waitpid( d->process->pid(), 0, 0 );
    }
  }
}


void K3bSoxEncoder::slotSoxFinished( TDEProcess* p )
{
  if( !p->normalExit() || p->exitStatus() != 0 )
    kdDebug() << "(K3bSoxEncoder) sox exited with error." << endl;
}


bool K3bSoxEncoder::openFile( const TQString& ext, const TQString& filename, const K3b::Msf& )
{
  d->fileName = filename;
  return initEncoderInternal( ext );
}


void K3bSoxEncoder::closeFile()
{
  finishEncoderInternal();
}


bool K3bSoxEncoder::initEncoderInternal( const TQString& extension )
{
  const K3bExternalBin* soxBin = k3bcore->externalBinManager()->binObject( "sox" );
  if( soxBin ) {
    delete d->process;
    d->process = new K3bProcess();
    d->process->setSplitStdout(true);
    d->process->setRawStdin(true);

    connect( d->process, TQ_SIGNAL(processExited(TDEProcess*)),
             this, TQ_SLOT(slotSoxFinished(TDEProcess*)) );
    connect( d->process, TQ_SIGNAL(stderrLine(const TQString&)),
             this, TQ_SLOT(slotSoxOutputLine(const TQString&)) );
    connect( d->process, TQ_SIGNAL(stdoutLine(const TQString&)),
             this, TQ_SLOT(slotSoxOutputLine(const TQString&)) );

    // input settings
    *d->process << soxBin->path
                << "-t" << "raw"    // raw samples
                << "-r" << "44100"  // samplerate
                << "-s"             // signed linear
                << "-w"             // 16-bit words
                << "-c" << "2"      // stereo
                << "-";             // read from stdin

    // output settings
    *d->process << "-t" << extension;

    TDEConfig* c = k3bcore->config();
    c->setGroup( "K3bSoxEncoderPlugin" );
    if( c->readBoolEntry( "manual settings", false ) ) {
      *d->process << "-r" << TQString::number( c->readNumEntry( "samplerate", 44100 ) )
        << "-c" << TQString::number( c->readNumEntry( "channels", 2 ) );

      int size = c->readNumEntry( "data size", 16 );
      *d->process << ( size == 8 ? TQString("-b") : ( size == 32 ? TQString("-l") : TQString("-w") ) );

      TQString encoding = c->readEntry( "data encoding", "signed" );
      if( encoding == "unsigned" )
        *d->process << "-u";
      else if( encoding == "u-law" )
        *d->process << "-U";
      else if( encoding == "A-law" )
        *d->process << "-A";
      else if( encoding == "ADPCM" )
        *d->process << "-a";
      else if( encoding == "IMA_ADPCM" )
        *d->process << "-i";
      else if( encoding == "GSM" )
        *d->process << "-g";
      else if( encoding == "Floating-point" )
        *d->process << "-f";
      else
        *d->process << "-s";
    }

    *d->process << d->fileName;

    kdDebug() << "***** sox parameters:" << endl;
    const TQValueList<TQCString>& args = d->process->args();
    TQString s;
    for( TQValueList<TQCString>::const_iterator it = args.begin(); it != args.end(); ++it ) {
      s += *it + " ";
    }
    kdDebug() << s << flush << endl;


    return d->process->start( TDEProcess::NotifyOnExit, TDEProcess::All );
  }
  else {
    kdDebug() << "(K3bSoxEncoder) could not find sox bin." << endl;
    return false;
  }
}


long K3bSoxEncoder::encodeInternal( const char* data, TQ_ULONG len )
{
  if( d->process ) {
    if( d->process->isRunning() )
      return ::write( d->process->stdinFd(), (const void*)data, len );
    else
      return -1;
  }
  else
    return -1;
}


void K3bSoxEncoder::slotSoxOutputLine( const TQString& line )
{
  kdDebug() << "(sox) " << line << endl;
}


TQStringList K3bSoxEncoder::extensions() const
{
  static TQStringList s_extensions;
  if( s_extensions.isEmpty() ) {
    s_extensions << "au"
                 << "8svx"
                 << "aiff"
                 << "avr"
                 << "cdr"
                 << "cvs"
                 << "dat"
                 << "gsm"
                 << "hcom"
                 << "maud"
                 << "sf"
                 << "sph"
                 << "smp"
                 << "txw"
                 << "vms"
                 << "voc"
                 << "wav"
                 << "wve"
                 << "raw";
  }

  if( k3bcore->externalBinManager()->foundBin( "sox" ) )
    return s_extensions;
  else
    return TQStringList(); // no sox -> no encoding
}


TQString K3bSoxEncoder::fileTypeComment( const TQString& ext ) const
{
  if( ext == "au" )
    return i18n("Sun AU");
  else if( ext == "8svx" )
    return i18n("Amiga 8SVX");
  else if( ext == "aiff" )
    return i18n("AIFF");
  else if( ext == "avr" )
    return i18n("Audio Visual Research");
  else if( ext == "cdr" )
    return i18n("CD-R");
  else if( ext == "cvs" )
    return i18n("CVS");
  else if( ext == "dat" )
    return i18n("Text Data");
  else if( ext == "gsm" )
    return i18n("GSM Speech");
  else if( ext == "hcom" )
    return i18n("Macintosh HCOM");
  else if( ext == "maud" )
    return i18n("Maud (Amiga)");
  else if( ext == "sf" )
    return i18n("IRCAM");
  else if( ext == "sph" )
    return i18n("SPHERE");
  else if( ext == "smp" )
    return i18n("Turtle Beach SampleVision");
  else if( ext == "txw" )
    return i18n("Yamaha TX-16W");
  else if( ext == "vms" )
    return i18n("VMS");
  else if( ext == "voc" )
    return i18n("Sound Blaster VOC");
  else if( ext == "wav" )
    return i18n("Wave (Sox)");
  else if( ext == "wve" )
    return i18n("Psion 8-bit A-law");
  else if( ext == "raw" )
    return i18n("Raw");
  else
    return i18n("Error");
}


long long K3bSoxEncoder::fileSize( const TQString&, const K3b::Msf& msf ) const
{
  // for now we make a rough assumption based on the settings
  TDEConfig* c = k3bcore->config();
  c->setGroup( "K3bSoxEncoderPlugin" );
  if( c->readBoolEntry( "manual settings", false ) ) {
    int sr =  c->readNumEntry( "samplerate", 44100 );
    int ch = c->readNumEntry( "channels", 2 );
    int wsize = c->readNumEntry( "data size", 16 );

    return msf.totalFrames()*sr*ch*wsize/75;
  }
  else {
    // fallback to raw
    return msf.audioBytes();
  }
}


K3bPluginConfigWidget* K3bSoxEncoder::createConfigWidget( TQWidget* parent,
                                                          const char* name ) const
{
  return new K3bSoxEncoderSettingsWidget( parent, name );
}



K3bSoxEncoderSettingsWidget::K3bSoxEncoderSettingsWidget( TQWidget* parent, const char* name )
  : K3bPluginConfigWidget( parent, name )
{
  w = new base_K3bSoxEncoderConfigWidget( this );
  w->m_editSamplerate->setValidator( new TQIntValidator( w->m_editSamplerate ) );

  TQHBoxLayout* lay = new TQHBoxLayout( this );
  lay->setMargin( 0 );

  lay->addWidget( w );
}


K3bSoxEncoderSettingsWidget::~K3bSoxEncoderSettingsWidget()
{
}


void K3bSoxEncoderSettingsWidget::loadConfig()
{
  TDEConfig* c = k3bcore->config();

  c->setGroup( "K3bSoxEncoderPlugin" );

  w->m_checkManual->setChecked( c->readBoolEntry( "manual settings", false ) );

  int channels = c->readNumEntry( "channels", 2 );
  w->m_comboChannels->setCurrentItem( channels == 4 ? 2 : channels-1 );

  w->m_editSamplerate->setText( TQString::number( c->readNumEntry( "samplerate", 44100 ) ) );

  TQString encoding = c->readEntry( "data encoding", "signed" );
  if( encoding == "unsigned" )
    w->m_comboEncoding->setCurrentItem(1);
  else if( encoding == "u-law" )
    w->m_comboEncoding->setCurrentItem(2);
  else if( encoding == "A-law" )
    w->m_comboEncoding->setCurrentItem(3);
  else if( encoding == "ADPCM" )
    w->m_comboEncoding->setCurrentItem(4);
  else if( encoding == "IMA_ADPCM" )
    w->m_comboEncoding->setCurrentItem(5);
  else if( encoding == "GSM" )
    w->m_comboEncoding->setCurrentItem(6);
  else if( encoding == "Floating-point" )
    w->m_comboEncoding->setCurrentItem(7);
  else
    w->m_comboEncoding->setCurrentItem(0);

  int size = c->readNumEntry( "data size", 16 );
  w->m_comboSize->setCurrentItem( size == 8 ? 0 : ( size == 32 ? 2 : 1 ) );
}


void K3bSoxEncoderSettingsWidget::saveConfig()
{
  TDEConfig* c = k3bcore->config();

  c->setGroup( "K3bSoxEncoderPlugin" );

  c->writeEntry( "manual settings", w->m_checkManual->isChecked() );

  c->writeEntry( "channels", w->m_comboChannels->currentItem() == 0
                 ? 1
                 : ( w->m_comboChannels->currentItem() == 2
                   ? 4
                   : 2 ) );

  c->writeEntry( "data size", w->m_comboSize->currentItem() == 0
                 ? 8
                 : ( w->m_comboSize->currentItem() == 2
                   ? 32
                   : 16 ) );

  c->writeEntry( "samplerate", w->m_editSamplerate->text().toInt() );

  TQString enc;
  switch( w->m_comboEncoding->currentItem() ) {
  case 1:
    enc = "unsigned";
    break;
  case 2:
    enc = "u-law";
    break;
  case 3:
    enc = "A-law";
    break;
  case 4:
    enc = "ADPCM";
    break;
  case 5:
    enc = "IMA_ADPCM";
    break;
  case 6:
    enc = "GSM";
    break;
  case 7:
    enc = "Floating-point";
    break;
  default:
    enc = "signed";
    break;
  }
  c->writeEntry( "data encoding", enc );
}


#include "k3bsoxencoder.moc"
