/*****************************************************************************
 *
 *  mencoder.cpp -- MEncoder interface.
 *
 *  Copyright (C) 2010 Starbuck <starbuck@labdv.com>
 *  All rights reserved.
 *
 *  This file is part of the JobManager module of the DVDx 4 application.
 *
 *  GNU Lesser General Public License Usage
 *  This file may be used under the terms of the GNU Lesser General
 *  Public License version 2.1 as published by the Free Software
 *  Foundation and appearing in the file LICENSE.LGPL included in the
 *  packaging of this file.  Please review the following information to
 *  ensure the GNU Lesser General Public License version 2.1 requirements
 *  will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 *
 *  GNU General Public License Usage
 *  Alternatively, this file may be used under the terms of the GNU
 *  General Public License version 3.0 as published by the Free Software
 *  Foundation and appearing in the file LICENSE.GPL included in the
 *  packaging of this file.  Please review the following information to
 *  ensure the GNU General Public License version 3.0 requirements will be
 *  met: http://www.gnu.org/copyleft/gpl.html.
 *
 *****************************************************************************/

#define	RX_PARSE_MENCODER_SIGNATURE		"(mencoder)\\s+([^\\s]+).*MPlayer Team"
#ifdef WIN32
#define	RX_PARSE_MENCODER_OUTPUT_HELP	"([^\\s]+)\\s+-\\s+([^\\n]*)\\r\\n"
#else
#define	RX_PARSE_MENCODER_OUTPUT_HELP	"([^\\s]+)\\s+-\\s+([^\\n]*)\\n"
#endif
#define	RX_SPLIT_MENCODER_OUTPUT_HELP	"Available codecs:(.*)Available codecs:(.*)Available output formats:(.*)"

#include "mencoder.h"
#include <QDebug>
#include <QDir>
#include <QRegExp>

MEncoder::MEncoder()
{
	m_interfaceName = "MEncoder";
	m_interfaceState = Idle;

	m_divxFourcc << "DVI3" << "DVI4" << "DIVX" << "DX50";
	m_xvidRecodedFourcc = "DX50";  // best choice for most players
}

QString MEncoder::encX264Bitrate(double bitrate)
{
	bitrate = bitrate / 1000.0;

	return QString("bitrate=%1").arg((int)bitrate);
}

QString MEncoder::encX264Preset(const QString &format, const QString &codec)
{
	QString opt;

	if (format != "mov" && format != "ipod")
		if (codec.contains("lr"))						opt = "preset=medium";
		else if (codec.contains(QRegExp("hd|divx")))	opt = "preset=ultrafast";
		else											opt = "preset=fast";
	else												opt = "preset=veryfast";

	return opt;
}

QString MEncoder::encX264Profile(const QString &format, const int width, const int height)
{
	QString opt;

	if (format == "mov")					opt = "profile=main";
	else if (format =="ipod")
		if (width > 640 && height > 480 )	opt = "profile=main";		// iPod touch
		else								opt = "profile=baseline";	// iPod classic
	else if (format =="psp")				opt = "profile=main";		// PSP
	else if (height < 240 )					opt = "profile=baseline";	// Mobile
	else if (width <= 720 && height <= 480)	opt = "profile=main";		// Smartphone & Standard-Definition TV
	else									opt = "profile=high";		// HDTV & disc storage

	return opt;
}

QStringList MEncoder::encX264Tuning(const QString &format, const QString &codec, const double fps)
{
	QStringList opts;

	// We hope "bframes=3" works as expected even if lavf is broken
	if (codec.contains("divx"))
	{
		opts << "vbv_bufsize=25000"
			<< "vbv_maxrate=20000"
			<< "bframes=3"
			<< "level_idc=40"
			<< "trellis=0"
			<< "nomixed_refs"
			<< "no8x8dct"
			<< "noweight_b"
			<< "weightp=0";

		if (fps) opts << QString("keyint=%1").arg( int(fps * 4.0));
	}
	else if (format == "mov" || format == "ipod")
	{
		// QuickTime 7 requirements
		opts << "bframes=1"
			<< "nob_adapt"
			<< "no8x8dct";
	}
	else if (format == "psp")
	{
		opts << "level_idc=30"
			<< "bframes=3"
			<< "weightp=0"
			<< "b_pyramid=0"
			<< "no8x8dct"
			<< "nocabac"
			<< "cqm=flat"
			<< "vbv_maxrate=2000"
			<< "vbv_bufsize=2000";
	}
	else if ( format == "avi" )
	{
		opts << "bframes=3";
	}
	else
	{
		//*Experimental: cause broken LAVF
		opts << "bframes=0";
	}

	return opts;
}

QString MEncoder::encXvidBitrate(double bitrate)
{
	if (bitrate > 16000.0) bitrate = bitrate / 1000.0;

	return QString("bitrate=%1").arg((int)bitrate);
}

QString MEncoder::encXvidProfile(const int width, const int height, 
	const QString &aviFourcc, const QString &tvStandard)
{
	QString opt;

	if (!width || !height) return opt;

	if (m_divxFourcc.contains(aviFourcc))
	{
		if (width <= 176 && height <= 144)			opt = "profile=dxnhandheld";
		else if (width <= 352 && height <= 288)
			if (tvStandard == "NTSC")				opt = "profile=dxnportntsc";
			else if (tvStandard == "PAL")			opt = "profile=dxnportpal";
			else if (height == 240)					opt = "profile=dxnportntsc";
			else									opt = "profile=dxnportpal";
		else if (width <= 352 && height <= 576)
			if (tvStandard == "NTSC")				opt = "profile=dxnhtntsc";
			else if (tvStandard == "PAL")			opt = "profile=dxnhtpal";
			else if (height == 480)					opt = "profile=dxnhtntsc";
			else									opt = "profile=dxnhtpal";
		else if (width <= 1280 && height <= 720)	opt = "profile=dxnhdtv";
	}
	else
	{
		if (width <= 176 && height <= 144)			opt = "profile=asp1";
		else if (width <= 352 && height <= 288)		opt = "profile=sp3";
		else if (width <= 352 && height <= 576)		opt = "profile=asp4";
		else if (width <= 1280 && height <= 720)	opt = "profile=asp5";
	}

	return opt;
}

QStringList MEncoder::encXvidTuning(const double bitrate)
{
	QStringList opts;

	// Tuning from bitrate
	if (bitrate < 600.0)
		opts << "turbo" << "vhq=0" << "nohq_ac" << "notrellis" << "me_quality=4";
	else if (bitrate < 1200.0)
		opts << "vhq=1" << "nohq_ac";
	else
		opts << "chroma_opt" << "vhq=4" << "hq_ac" << "trellis" << "me_quality=6";

	return opts;
}

void MEncoder::interfaceReset()
{
	m_interfaceState = Idle;
}

bool MEncoder::interfaceStart(const QString &path, const QString &libPath)  // IProcessor interface
{
	if (path.isEmpty()) return m_interfaceState;

	if (m_interfacePath == path)
	{
		if (m_interfaceState == Failed) return false;
		if (m_interfaceState == Ready) return true;
	}

	m_interfaceState = Idle;
	m_interfacePath = path.toLatin1();
	m_interfaceLibPath = libPath;

	m_availableAudioEncoders.clear();
	m_availableFormatEncoders.clear();
	m_availableSubtitleEncoders.clear();
	m_availableVideoEncoders.clear();

	m_console->log(Console::Verbose, tr("MEncoder: start interface."));
	qDebug() << "MEncoder: start interface" << m_interfacePath;

	QString err, stdErr, stdOut;
	QStringList args;

	// Get MEncoder version and supported codecs and formats
	args = QStringList() << "-ovc" << "help" << "-oac" << "help" << "-of" << "help";

	if (!run(&args, &stdOut, &stdErr, &err))
	{
		m_console->log(tr("MEncoder: %1.").arg(err));
		qDebug() << "MEncoder: failed to start interface" << err << m_interfacePath;
		m_interfaceState = Failed;
		return false;
	}

	m_console->stdOut(stdOut);
	m_console->stdErr(stdErr);

	parseInterfaceVersion(&stdOut);

	// Split output codecs and formats
	QRegExp rx(RX_SPLIT_MENCODER_OUTPUT_HELP);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(false);
	rx.indexIn(stdOut);
	if (rx.numCaptures() != 3)
	{
		qDebug() << "MEncoder: failed to split codecs/formats help" << m_interfacePath;
		m_interfaceState = Failed;
		return false;
	}

	QString outputVideoHelp = rx.cap(1);
	QString outputAudioHelp = rx.cap(2);
	QString outputFormatHelp = rx.cap(3);

	parseOutputHelpFormats(&outputVideoHelp, &m_availableVideoEncoders);
	parseOutputHelpFormats(&outputAudioHelp, &m_availableAudioEncoders);
	parseOutputHelpFormats(&outputFormatHelp, &m_availableFormatEncoders);

	m_availableSubtitleEncoders.insert( "inlay", "Inlaid subtitles" );

	m_console->log(Console::Verbose, tr("MEncoder: interface ready."));
	qDebug() << "MEncoder: interface ready" << m_interfacePath;
	m_interfaceState = Ready;
	return true;
}

void MEncoder::parseInterfaceVersion(QString *cmdOutput)
{
	QRegExp rx(RX_PARSE_MENCODER_SIGNATURE);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(false);
	rx.indexIn(*cmdOutput);
	if (rx.numCaptures() != 2 || rx.cap(1).length() == 0 || rx.cap(2).length() == 0)
	{
		m_console->log(Console::Verbose, tr("MEncoder: failed to parse signature."));
		qDebug() << "MEncoder: failed to parse signature" << m_interfacePath;
		return;
	}

	m_interfaceExec = rx.cap(1);
	m_interfaceVersion = rx.cap(2);
}

void MEncoder::parseOutputHelpFormats(QString *cmdOutput, QMap<QString, QString> *availableEncoders)
{
	QRegExp rx(RX_PARSE_MENCODER_OUTPUT_HELP);
	rx.setCaseSensitivity(Qt::CaseSensitive);
	rx.setMinimal(true);
	int pos(0);
	while ((pos = rx.indexIn(*cmdOutput, pos)) != -1) 
	{
		qDebug() << "MEncoder: found available codec or muxer:" << rx.cap(1) << rx.cap(2);

		availableEncoders->insert(rx.cap(1), rx.cap(2));
		pos += rx.matchedLength();
	}
}
