/*
  Copyright (C) 2005 Benjamin Meyer <ben at meyerhome dot net>

  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <config.h>

#include "encoderlame.h"
#include "encoderlameconfig.h"
#include "audiocd_lame_encoder.h"

#include <kdebug.h>
#include <tqgroupbox.h>
#include <kprocess.h>
#include <kdebug.h>

#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdeapplication.h>
#include <tqfileinfo.h>
#include <tdetempfile.h>
#include <kstandarddirs.h>
#include "collectingprocess.h"

extern "C"
{
	TDE_EXPORT void create_audiocd_encoders(TDEIO::SlaveBase *slave, TQPtrList<AudioCDEncoder> &encoders) {
		encoders.append(new EncoderLame(slave));
	}
}

static int bitrates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };

class EncoderLame::Private
{
public:
	int bitrate;
	bool waitingForWrite;
	bool processHasExited;
	TQString lastErrorMessage;
	TQStringList genreList;
	uint lastSize;
	TDEProcess *currentEncodeProcess;
	KTempFile *tempFile;
};

EncoderLame::EncoderLame(TDEIO::SlaveBase *slave) : TQObject(), AudioCDEncoder(slave) {
	d = new Private();
	d->waitingForWrite = false;
	d->processHasExited = false;
	d->lastSize = 0;
	loadSettings();
}

EncoderLame::~EncoderLame(){
	delete d;
}

TQWidget* EncoderLame::getConfigureWidget(TDEConfigSkeleton** manager) const {
	(*manager) = Settings::self();
	TDEGlobal::locale()->insertCatalogue("audiocd_encoder_lame");
	EncoderLameConfig *config = new EncoderLameConfig();
	config->cbr_settings->hide();
	return config;
}

bool EncoderLame::init(){
	// Determine if lame is installed on the system or not.
	if ( TDEStandardDirs::findExe( "lame" ).isEmpty() )
		return false;

	// Ask lame for the list of genres it knows; otherwise it barfs when doing
	// e.g. lame --tg 'Vocal Jazz'
    CollectingProcess proc;
	proc << "lame" << "--genre-list";
	proc.start(TDEProcess::Block, TDEProcess::Stdout);

	if(proc.exitStatus() != 0)
		return false;

	const TQByteArray data = proc.collectedStdout();
	TQString str;
	if ( !data.isEmpty() )
		str = TQString::fromLocal8Bit( data, data.size() );

	d->genreList = TQStringList::split( '\n', str );
	// Remove the numbers in front of every genre
	for( TQStringList::Iterator it = d->genreList.begin(); it != d->genreList.end(); ++it ) {
		TQString& genre = *it;
		uint i = 0;
		while ( i < genre.length() && ( genre[i].isSpace() || genre[i].isDigit() ) )
			++i;
		genre = genre.mid( i );

	}
	//kdDebug(7117) << "Available genres:" << d->genreList << endl;

	return true;
}

void EncoderLame::loadSettings(){
	// Generate the command line arguments for the current settings
	args.clear();

	Settings *settings = Settings::self();

	int quality = settings->quality();
	if (quality < 0 ) quality = quality *-1;
	if (quality > 9) quality = 9;

	int method = settings->bitrate_constant() ? 0 : 1 ;

	if (method == 0) {
		// Constant Bitrate Encoding
		args.append("-b");
		args.append(TQString("%1").arg(bitrates[settings->cbr_bitrate()]));
		d->bitrate = bitrates[settings->cbr_bitrate()];
		args.append("-q");
		args.append(TQString("%1").arg(quality));
	}
	else {
		// Variable Bitrate Encoding
		if (settings->vbr_average_br()) {
			args.append("--abr");
			args.append(TQString("%1").arg(bitrates[settings->vbr_mean_brate()]));
			d->bitrate = bitrates[settings->vbr_mean_brate()];
			if (settings->vbr_min_br()){
				args.append("-b");
				args.append(TQString("%1").arg(bitrates[settings->vbr_min_brate()]));
			}
			if (settings->vbr_min_hard())
				args.append("-F");
			if (settings->vbr_max_br()){
				args.append("-B");
				args.append(TQString("%1").arg(bitrates[settings->vbr_max_brate()]));
			}
		} else {
			d->bitrate = 128;
			args.append("-V");
			args.append(TQString("%1").arg(quality));
		}
		if ( !settings->vbr_xing_tag() )
			args.append("-t");
	}

	args.append("-m");
	switch ( settings->stereo() ) {
		case 0:
			args.append("s");
			break;
		case 1:
			args.append("j");
			break;
		case 2:
			args.append("d");
			break;
		case 3:
			args.append("m");
			break;
		default:
			args.append("s");
			break;
	}

	if(settings->copyright())
		args.append("-c");
	if(!settings->original())
		args.append("-o");
	if(settings->iso())
		args.append("--strictly-enforce-ISO");
	if(settings->crc())
		args.append("-p");

	if ( settings->enable_lowpass() ) {
		args.append("--lowpass");
		args.append(TQString("%1").arg(settings->lowfilterfreq()));

		if (settings->set_lpf_width()){
			args.append("--lowpass-width");
			args.append(TQString("%1").arg(settings->lowfilterwidth()));
		}
	}

	if ( settings->enable_highpass()) {
		args.append("--hipass");
		args.append(TQString("%1").arg(settings->highfilterfreq()));

		if (settings->set_hpf_width()){
			args.append("--hipass-width");
			args.append(TQString("%1").arg(settings->highfilterwidth()));
		}
	}
}

unsigned long EncoderLame::size(long time_secs) const {
	return (time_secs * d->bitrate * 1000)/8;
}

long EncoderLame::readInit(long /*size*/){
	// Create TDEProcess
	d->currentEncodeProcess	= new TDEProcess(0);
	TQString prefix = locateLocal("tmp", "");
	d->tempFile = new KTempFile(prefix, ".mp3");
	d->tempFile->setAutoDelete(true);
	d->lastErrorMessage = TQString();
	d->processHasExited = false;

	// -x bitswap
	// -r raw/pcm
	// -s 44.1 (because it is raw you have to specify this)
	*(d->currentEncodeProcess)      << "lame" << "--verbose" << "-r" << "-s" << "44.1";

	*(d->currentEncodeProcess) << args;
	if(Settings::self()->id3_tag())
		*d->currentEncodeProcess << trackInfo;

	// Read in stdin, output to the temp file
	*d->currentEncodeProcess << "-" << d->tempFile->name().latin1();

	//kdDebug(7117) << d->currentEncodeProcess->args() << endl;


	connect(d->currentEncodeProcess, TQ_SIGNAL(receivedStdout(TDEProcess *, char *, int)),
	                 this, TQ_SLOT(receivedStdout(TDEProcess *, char *, int)));
	connect(d->currentEncodeProcess, TQ_SIGNAL(receivedStderr(TDEProcess *, char *, int)),
	                 this, TQ_SLOT(receivedStderr(TDEProcess *, char *, int)));
	connect(d->currentEncodeProcess, TQ_SIGNAL(wroteStdin(TDEProcess *)),
	                 this, TQ_SLOT(wroteStdin(TDEProcess *)));

	connect(d->currentEncodeProcess, TQ_SIGNAL(processExited(TDEProcess *)),
	                 this, TQ_SLOT(processExited(TDEProcess *)));

	// Launch!
	d->currentEncodeProcess->start(TDEProcess::NotifyOnExit, KShellProcess::All);
	return 0;
}

void EncoderLame::processExited ( TDEProcess *process ){
	kdDebug(7117) << "Lame Encoding process exited with: " << process->exitStatus() << endl;
	d->processHasExited = true;
}

void EncoderLame::receivedStderr( TDEProcess * /*process*/, char *buffer, int /*buflen*/ ){
	kdDebug(7117) << "Lame stderr: " << buffer << endl;
	if ( !d->lastErrorMessage.isEmpty() )
		d->lastErrorMessage += '\t';
	d->lastErrorMessage += TQString::fromLocal8Bit( buffer );
}

void EncoderLame::receivedStdout( TDEProcess * /*process*/, char *buffer, int /*length*/ ){
	kdDebug(7117) << "Lame stdout: " << buffer << endl;
}

void EncoderLame::wroteStdin( TDEProcess * /*procces*/ ){
	d->waitingForWrite = false;
}

long EncoderLame::read(int16_t *buf, int frames){
	if(!d->currentEncodeProcess)
		return 0;
        if (d->processHasExited)
		return -1;

	// Pipe the raw data to lame
	char * cbuf = reinterpret_cast<char *>(buf);
	d->currentEncodeProcess->writeStdin( cbuf, frames*4);

	// We can't return until the buffer has been written
	d->waitingForWrite = true;
	while(d->waitingForWrite && d->currentEncodeProcess->isRunning()){
		kapp->processEvents();
		usleep(1);
	}

	// Determine the file size increase
	TQFileInfo file(d->tempFile->name());
	uint change = file.size() - d->lastSize;
	d->lastSize = file.size();
	return change;
}

long EncoderLame::readCleanup(){
	if(!d->currentEncodeProcess)
		return 0;

	// Let lame tag the first frame of the mp3
	d->currentEncodeProcess->closeStdin();
	while( d->currentEncodeProcess->isRunning()){
		kapp->processEvents();
		usleep(1);
	}

	// Now copy the file out of the temp into tdeio
	TQFile file( d->tempFile->name() );
	if ( file.open( IO_ReadOnly ) ) {
		TQByteArray output;
		char data[1024];
		while ( !file.atEnd() ) {
			uint read = file.readBlock(data, 1024);
			output.setRawData(data, read);
			ioslave->data(output);
			output.resetRawData(data, read);
		}
		file.close();
	}

	// cleanup the process and temp
	delete d->currentEncodeProcess;
	delete d->tempFile;
	d->lastSize = 0;

	return 0;
}

void EncoderLame::fillSongInfo( KCDDB::CDInfo info, int track, const TQString &comment ){
	trackInfo.clear();
	trackInfo.append("--tt");
	trackInfo.append(info.trackInfoList[track].get("title").toString());

	trackInfo.append("--ta");
	trackInfo.append(info.get("artist").toString());

	trackInfo.append("--tl");
	trackInfo.append(info.get("title").toString());

	trackInfo.append("--ty");
	trackInfo.append(TQString("%1").arg(info.get("year").toString()));

	trackInfo.append("--tc");
	trackInfo.append(comment);

	trackInfo.append("--tn");
	trackInfo.append(TQString("%1").arg(track+1));

	const TQString genre = info.get( "genre" ).toString();
	if ( d->genreList.find( genre ) != d->genreList.end() )
	{
		trackInfo.append("--tg");
		trackInfo.append(genre);
	}
}


TQString EncoderLame::lastErrorMessage() const
{
	return d->lastErrorMessage;
}

#include "encoderlame.moc"
