/*****************************************************************************
 *
 *  interfaceffprobe.cpp -- Interface to FFmpeg probe CLI.
 *
 *  Copyright (C) 2010 Starbuck <starbuck@labdv.com>
 *  All rights reserved.
 *
 *  This file is part of the Encoder 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_FFPROBE_SIGNATURE		"(FFprobe)\\s+([^\\s]+)"

#define	RX_PARSE_FFPROBE_FORMAT			"\\[FORMAT\\](.*)\\[/FORMAT\\]"
#define	RX_PARSE_FFPROBE_STREAM			"\\[STREAM\\](.*)\\[/STREAM\\]"

#define	LSTR_CATCH_FFPROBE_FILENAME		"filename"
#define	LSTR_CATCH_FFPROBE_FORMATNAME	"format_name"
#define	LSTR_CATCH_FFPROBE_FORMATLNAME	"format_long_name"
#define	LSTR_CATCH_FFPROBE_INDEX		"index"
#define	LSTR_CATCH_FFPROBE_DURATION		"duration"
#define	LSTR_CATCH_FFPROBE_START		"start_time"
#define	LSTR_CATCH_FFPROBE_BITRATE		"bit_rate"
#define	LSTR_CATCH_FFPROBE_CODECTYPE	"codec_type"
#define	LSTR_CATCH_FFPROBE_CODECNAME	"codec_name"
#define	LSTR_CATCH_FFPROBE_CODECLNAME	"codec_long_name"
#define	LSTR_CATCH_FFPROBE_CODECTAG		"codec_tag_string"
#define	LSTR_CATCH_FFPROBE_WIDTH		"width"
#define	LSTR_CATCH_FFPROBE_HEIGHT		"height"
#define	LSTR_CATCH_FFPROBE_SAR			"sample_aspect_ratio"
#define	LSTR_CATCH_FFPROBE_DAR			"display_aspect_ratio"
#define	LSTR_CATCH_FFPROBE_PIXFMT		"pix_fmt"
#define	LSTR_CATCH_FFPROBE_FPS			"avg_frame_rate"
#define	LSTR_CATCH_FFPROBE_RFPS			"r_frame_rate"
#define	LSTR_CATCH_FFPROBE_SAMPLING		"sample_rate"
#define	LSTR_CATCH_FFPROBE_BITS			"bits_per_sample"
#define	LSTR_CATCH_FFPROBE_CHANNELS		"channels"
#define	LSTR_CATCH_FFPROBE_LANGUAGE		"TAG:language"
#define	LSTR_CATCH_FFPROBE_TITLE		"TAG:title"
#define	LSTR_CATCH_FFPROBE_MAJORBRAND	"TAG:major_brand"

#include "interfaceffprobe.h"
#include "macros.h"
#include <QProcess>
#include <QRegExp>
#include <QDebug>

InterfaceFFprobe::InterfaceFFprobe()
{
}

void InterfaceFFprobe::initialize(const QString &p, 
		interfaceffprobe_t *d, utility_status_t *s)
{
	exec = p;
	data = d;
	status = s;

	formatFields	<< LSTR_CATCH_FFPROBE_FILENAME
					<< LSTR_CATCH_FFPROBE_FORMATNAME
					<< LSTR_CATCH_FFPROBE_FORMATLNAME
					<< LSTR_CATCH_FFPROBE_MAJORBRAND
					<< LSTR_CATCH_FFPROBE_DURATION;

	streamFields	<< LSTR_CATCH_FFPROBE_DURATION
					<< LSTR_CATCH_FFPROBE_START
					<< LSTR_CATCH_FFPROBE_BITRATE
					<< LSTR_CATCH_FFPROBE_CODECTYPE
					<< LSTR_CATCH_FFPROBE_CODECNAME
					<< LSTR_CATCH_FFPROBE_CODECLNAME
					<< LSTR_CATCH_FFPROBE_CODECTAG
					<< LSTR_CATCH_FFPROBE_WIDTH
					<< LSTR_CATCH_FFPROBE_HEIGHT
					<< LSTR_CATCH_FFPROBE_SAR
					<< LSTR_CATCH_FFPROBE_DAR
					<< LSTR_CATCH_FFPROBE_PIXFMT
					<< LSTR_CATCH_FFPROBE_FPS
					<< LSTR_CATCH_FFPROBE_RFPS
					<< LSTR_CATCH_FFPROBE_SAMPLING
					<< LSTR_CATCH_FFPROBE_BITS
					<< LSTR_CATCH_FFPROBE_CHANNELS
					<< LSTR_CATCH_FFPROBE_LANGUAGE
					<< LSTR_CATCH_FFPROBE_TITLE;
}

void InterfaceFFprobe::setBinPath(const QString &binPath)
{
	exec = binPath;
}

void InterfaceFFprobe::cliCmdGetInfo(const QString &path, QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Video source to read
	*args << path << "-show_format" << "-show_streams"; 

	qDebug() << "InterfaceFFprobe::cliCmdGetInfo() returns command:" << *cmd << "with options" << *args;	

}

void InterfaceFFprobe::cliCmdGetStatus(QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	*args << "-version";

	qDebug() << "InterfaceFFprobe::cliCmdGetStatus() returns command:" << *cmd << "with options" << *args;
}

bool InterfaceFFprobe::parseStatus(QString *stdOut)
{
	qDebug() << "InterfaceFFprobe::parseStatus() starts parsing status raw data";

	// Parse stardard out

	QRegExp rx(RX_PARSE_FFPROBE_SIGNATURE);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(false);
	rx.indexIn(*stdOut);
	if (rx.numCaptures() != 2 || rx.cap(1).length() == 0 || rx.cap(2).length() == 0)
	{
		qDebug("InterfaceFFprobe::parseStatus() - Failed to parse FFprobe standard output (parsing signature)");
		status->state = Video::Status::ParseFail;
		return false;
	}
	status->utility = rx.cap(1);
	status->version = rx.cap(2);

	status->state = Video::Status::EngineOk;
	return true;
}

void InterfaceFFprobe::parseData(interfaceffprobe_t *data, QString *cout, QString *cerr)
{
	qDebug() << "InterfaceFFprobe::parseData() starts parsing data";

	// Parse stardard out (only what we need to complete title/movie info

	QString format;
	QStringList streams;
	int pos;

	QRegExp rx(RX_PARSE_FFPROBE_FORMAT);
	rx.setMinimal(true);
	rx.indexIn(*cout);
	if ( rx.numCaptures() != 1 || rx.cap(1).length() == 0 )
	{
		qDebug("InterfaceFFprobe::parseData() - Failed to parse FFprobe standard output");
		return;
	}
	format =  rx.cap(1).trimmed();

	rx.setPattern(RX_PARSE_FFPROBE_STREAM);
	pos = 0;
	while ((pos = rx.indexIn(*cout, pos)) != -1) 
	{
		streams <<  rx.cap(1).trimmed();

		pos += rx.matchedLength() - 1;
	}

	qDebug() << format;
	qDebug() << streams;

	QStringList outputLines;
	QHash<QString, QString> fieldStrings;

	outputLines = format.split("\n", QString::SkipEmptyParts);

	while ( !outputLines.isEmpty() )
	{
		QString dataLine = outputLines.takeFirst().trimmed();

		QStringListIterator f(formatFields);
		while (f.hasNext())
		{
			QString field = f.next();
			QString completeField = field + "=";
			if (dataLine.startsWith(field))
			{
				fieldStrings.insert( field, dataLine.remove(0, completeField.length()) );
			}
		}
	}
	
	data->path = fieldStrings.value( LSTR_CATCH_FFPROBE_FILENAME, "" );
	data->muxer_long_name = fieldStrings.value( LSTR_CATCH_FFPROBE_FORMATLNAME, "" );
	data->duration = fieldStrings.value( LSTR_CATCH_FFPROBE_DURATION, "0.0" ).toDouble();	

	QString muxer = fieldStrings.value( LSTR_CATCH_FFPROBE_FORMATNAME, "" );

	if (muxer.contains(","))
	{
		QString major_brand = fieldStrings.value( LSTR_CATCH_FFPROBE_MAJORBRAND, "" );

		if (major_brand.isEmpty())
		{
			QStringList allMuxers = muxer.split(",", QString::SkipEmptyParts);

			if (!allMuxers.isEmpty()) data->muxer = allMuxers.at(0);
		}
		else
		{
			data->muxer = major_brand;
		}
	}
	else
	{
		data->muxer = fieldStrings.value( LSTR_CATCH_FFPROBE_FORMATNAME, "" );
	}

	for (int i = 0; i < streams.count(); ++i)
	{
		fieldStrings.clear();
		outputLines = streams[i].split("\n", QString::SkipEmptyParts);

		while ( !outputLines.isEmpty() )
		{
			QString dataLine = outputLines.takeFirst().trimmed();

			QStringListIterator f(streamFields);
			while (f.hasNext())
			{
				QString field = f.next();
				QString completeField = field + "=";
				if (dataLine.startsWith(field))
				{
					fieldStrings.insert( field, dataLine.remove(0, completeField.length()) );
				}
			}
		}
	
		if ( !fieldStrings.contains(LSTR_CATCH_FFPROBE_CODECTYPE) )
		{
			qDebug("InterfaceFFprobe::parseData() - Ignore stream data with missing 'codec_type'");
		}
		else if ( fieldStrings.value(LSTR_CATCH_FFPROBE_CODECTYPE) == "video" )
		{
			interfaceffprobe_t::video_t *stream = data->addVideo();

			stream->index = fieldStrings.value( LSTR_CATCH_FFPROBE_INDEX, "-1" ).toInt();
			stream->codec = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECNAME, "" );
			stream->codec_long_name = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECLNAME, "" );
			stream->codec_tag_string = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECTAG, "" );	
			stream->pixel_format = fieldStrings.value( LSTR_CATCH_FFPROBE_PIXFMT, "" );	
			stream->sample_aspect_ratio = fieldToDouble(fieldStrings.value( LSTR_CATCH_FFPROBE_SAR, "0.0" ));	
			stream->display_aspect_ratio = fieldToDouble(fieldStrings.value( LSTR_CATCH_FFPROBE_DAR, "0.0" ));	
			stream->bitrate = fieldStrings.value( LSTR_CATCH_FFPROBE_BITRATE, "0.0" ).toDouble() / 1000.0;	
			stream->fps = fieldToDouble(fieldStrings.value( LSTR_CATCH_FFPROBE_FPS, "0.0" ));	
			stream->r_fps = fieldToDouble(fieldStrings.value( LSTR_CATCH_FFPROBE_RFPS, "0.0" ));	
			stream->start_time = fieldStrings.value( LSTR_CATCH_FFPROBE_DURATION, "0.0" ).toDouble();	
			stream->duration = fieldStrings.value( LSTR_CATCH_FFPROBE_DURATION, "0.0" ).toDouble();	
			
			stream->rez = QSize( fieldStrings.value( LSTR_CATCH_FFPROBE_WIDTH, "0.0" ).toInt(),
								 fieldStrings.value( LSTR_CATCH_FFPROBE_HEIGHT, "0.0" ).toInt() );
		}
		else if ( fieldStrings.value(LSTR_CATCH_FFPROBE_CODECTYPE) == "audio" )
		{
			interfaceffprobe_t::audio_t *stream = data->addAudio();

			stream->index = fieldStrings.value( LSTR_CATCH_FFPROBE_INDEX, "-1" ).toInt();
			stream->codec = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECNAME, "" );
			stream->codec_long_name = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECLNAME, "" );
			stream->sampling = fieldStrings.value( LSTR_CATCH_FFPROBE_SAMPLING, "0" ).toInt();	
			stream->bits = fieldStrings.value( LSTR_CATCH_FFPROBE_BITS, "0" ).toInt();	
			stream->channels = fieldStrings.value( LSTR_CATCH_FFPROBE_CHANNELS, "0" ).toInt();	
			stream->bitrate = fieldStrings.value( LSTR_CATCH_FFPROBE_BITRATE, "0.0" ).toDouble() / 1000.0;	
			stream->start_time = fieldStrings.value( LSTR_CATCH_FFPROBE_DURATION, "0.0" ).toDouble();	
			stream->duration = fieldStrings.value( LSTR_CATCH_FFPROBE_DURATION, "0.0" ).toDouble();	
			stream->language = fieldStrings.value( LSTR_CATCH_FFPROBE_LANGUAGE, "" );
			stream->title = fieldStrings.value( LSTR_CATCH_FFPROBE_TITLE, "" );
		}
		else if ( fieldStrings.value(LSTR_CATCH_FFPROBE_CODECTYPE) == "subtitle" )
		{
			interfaceffprobe_t::subtitle_t *stream = data->addSubtitle();

			stream->index = fieldStrings.value( LSTR_CATCH_FFPROBE_INDEX, "-1" ).toInt();
			stream->codec = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECNAME, "" );
			stream->codec_long_name = fieldStrings.value( LSTR_CATCH_FFPROBE_CODECLNAME, "" );
			stream->language = fieldStrings.value( LSTR_CATCH_FFPROBE_LANGUAGE, "" );
			stream->title = fieldStrings.value( LSTR_CATCH_FFPROBE_TITLE, "" );
		}
		else
		{
			qDebug() << "InterfaceFFprobe::parseData() - Ignore stream data which 'codec_type' is"
				<< fieldStrings.value(LSTR_CATCH_FFPROBE_CODECTYPE);
		}
	}
}

double InterfaceFFprobe::fieldToDouble(const QString &field)
{
	double value(0.0);

	if (field == "N/A")
	{
		value = 0.0;
	}
	else if (field.contains("."))
	{
		value = field.toDouble();
	}
	else if (field.contains("/"))
	{
		QRegExp rx("(\\d+)/(\\d+)");
		rx.setMinimal(false);
		rx.indexIn(field);
		if (rx.numCaptures() == 2 && rx.cap(1).toInt() > 0 && rx.cap(2).toInt() > 0)
		{
			QStringList operandes = field.split("/");		

			value = operandes[0].toDouble() / operandes[1].toDouble();
		}
	}
	else if (field.contains(":"))
	{
		QRegExp rx("(\\d+):(\\d+)");
		rx.setMinimal(false);
		rx.indexIn(field);
		if (rx.numCaptures() == 2 && rx.cap(1).toInt() > 0 && rx.cap(2).toInt() > 0)
		{
			QStringList operandes = field.split(":");		

			value = operandes[0].toDouble() / operandes[1].toDouble();
		}
	}

	return value;
}