/* NMM - Network-Integrated Multimedia Middleware
 *
 * Copyright (C) 2006
 *               Motama GmbH, Saarbruecken, Germany
 *
 * Maintainer: Bernhard Fuchshumer <fub@motama.com>
 *
 * 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; version 2
 * of the License only.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 */

#include "abstractmediaproducer.h"
#include "backend.h"
#include <QTimer>
#include <kdebug.h>
#include <kurl.h>

//nmm specific
#include "nmm/base/ProxyApplication.hpp"
#include "nmm/base/sync/MultiAudioVideoSynchronizer.hpp"
#include "nmm/interfaces/file/ISeekable.hpp"
#include "nmm/interfaces/general/progress/ITimeProgressListener.hpp"
#include "nmm/interfaces/general/progress/ITimeProgress.hpp"


namespace Phonon
{
namespace nmm
{
AbstractMediaProducer::AbstractMediaProducer( Backend* backend, QObject* parent )
	: QObject( parent )
	, m_graphHandler(0)
	, m_eventHandlerRegistered(false)
	, m_cnode(0)
	, m_trackDuration(0)
	, m_currentTime(0)
	, m_aboutToFinishTime( 1e3 )
	, m_aboutToFinishNotEmitted( true )
	, m_eod(false)
	, m_url("")
	, m_state( Phonon::LoadingState )
	, m_tickInterval( 1e3 )
	, m_stage1Reached( false )
    , m_lastSeek(0)
	, m_trackDurationListener(this, &AbstractMediaProducer::trackDuration)
	, m_timeProgressListener(this, &AbstractMediaProducer::setTimeProgress)
	, m_backend( backend )
	, m_endTrackReceived(0)
{
	kDebug(1234) << k_funcinfo << endl;
}

AbstractMediaProducer::~AbstractMediaProducer()
{
	kDebug(1234) << k_funcinfo << endl;
	deinitGraphHandler();
}

void AbstractMediaProducer::deinitGraphHandler()
{
	if (m_graphHandler!=0)
	{
		delete m_graphHandler;
	}
	m_graphHandler=0;
	m_cnode=0;
	m_eventHandlerRegistered=false;
	m_stage1Reached=false;

	//if (m_config!=0)
	//delete m_config;
}

void AbstractMediaProducer::initGraphHandler()
{
	kDebug(1234) << k_funcinfo << endl;
	
	if (m_graphHandler)
		deinitGraphHandler();
	
	m_graphHandler = new GraphHandler(m_backend->nmmApplication());

	// set up synchronizer for audio/video
	m_graphHandler->setMultiAudioVideoSynchronizer(m_avSync.getCheckedInterface<IMultiAudioVideoSynchronizer>());
	//m_config = new ConfigProxy(m_graphHandler);
}

void AbstractMediaProducer::registerEventHandler()
{
	//register some AbstractMediaProducer specific event handlers
	kDebug(1234) << k_funcinfo << endl;

	if (m_cnode == 0)
		m_cnode = m_graphHandler->getCompositeNode();
	m_cnode->registerEventListener(ITrackDuration::trackDuration_event, &m_trackDurationListener);
	m_cnode->registerEventListener(ITimeProgressListener::setTimeProgress_event, &m_timeProgressListener);
	
	//m_graphHandler->setTimeReportInterval(m_tickInterval);

	ITimeProgress_var progress(m_cnode->getInterface<ITimeProgress>());
	if (progress.get()) 
	{
		progress->setProgressUpdateInterval(m_tickInterval*1e6);
		progress->setProgressUpdateEnabled(true);
	}

	m_eventHandlerRegistered = true;
}

bool AbstractMediaProducer::addVideoPath( QObject* videoPath )
{
	kDebug(1234) << k_funcinfo << endl;
	Q_ASSERT( videoPath );
	VideoPath* vp = qobject_cast<VideoPath*>( videoPath );
	Q_ASSERT( vp );
	Q_ASSERT( !m_videoPathList.contains( vp ) );
	m_videoPathList.append( vp );

	// WAIT untill in stage1
	if ( m_graphHandler && m_graphHandler->stageReached()>=1)
		return vp->insertInto(m_graphHandler);
	else
		return true;
}

bool AbstractMediaProducer::addAudioPath( QObject* audioPath )
{
	kDebug(1234) << k_funcinfo << endl;
	Q_ASSERT( audioPath );
	AudioPath* ap = qobject_cast<AudioPath*>( audioPath );
	Q_ASSERT( ap );
	Q_ASSERT( !m_audioPathList.contains( ap ) );
	m_audioPathList.append( ap );
	connect(ap, SIGNAL(endTrack()), SLOT(endTrack()));

	// WAIT untill in stage1
	if ( m_graphHandler && m_graphHandler->stageReached()>=1)
	{
		return ap->insertInto(m_graphHandler);
	}
	else
		return true;
}

void AbstractMediaProducer::removeVideoPath( QObject* videoPath )
{
	Q_ASSERT( videoPath );
	VideoPath* vp = qobject_cast<VideoPath*>( videoPath );
	Q_ASSERT( vp );
	Q_ASSERT( m_videoPathList.contains( vp ) );
	m_videoPathList.removeAll( vp );

	//remove corresponding branch as well
	if (m_graphHandler->getURL() != 0)
	{
		m_graphHandler->removeBranch(vp->getBranchId());
	}
}

void AbstractMediaProducer::removeAudioPath( QObject* audioPath )
{
	Q_ASSERT( audioPath );
	AudioPath* ap = qobject_cast<AudioPath*>( audioPath );
	Q_ASSERT( ap );
	Q_ASSERT( m_audioPathList.contains( ap ) );
	m_audioPathList.removeAll( ap );

	//remove corresponding branch as well
	if (m_graphHandler->getURL() != 0)
	{
		m_graphHandler->removeBranch(ap->getBranchId());
	}
}

bool AbstractMediaProducer::addVideoPaths()
{
	//TODO add requested filters

	Q_ASSERT(m_graphHandler && m_graphHandler->stageReached()>=1);

	bool ret=true;
	foreach( VideoPath* vp, m_videoPathList )
	{
		ret &= vp->insertInto(m_graphHandler);
	}
	return ret;
}

bool AbstractMediaProducer::addAudioPaths()
{
	//TODO add requested filters
	kDebug(1234)<<k_funcinfo<<endl;
	Q_ASSERT(m_graphHandler && m_graphHandler->stageReached()>=1);

	bool ret=true;
	foreach( AudioPath* ap, m_audioPathList )
	{
		ret &= ap->insertInto(m_graphHandler);
	}
	return ret;
}

void AbstractMediaProducer::stage1Reached()
{
	kDebug(1234)<<k_funcinfo<<endl;
	m_stage1Reached=true;
	addAudioPaths();
	addVideoPaths();
}

State AbstractMediaProducer::state() const
{
	//kDebug(1234) << k_funcinfo << endl;
	return m_state;
}

bool AbstractMediaProducer::hasVideo() const
{
	//kDebug(1234) << k_funcinfo << endl;
	try
	{
		return m_graphHandler->hasVideo();
	}
	catch(...)
	{
		//TODO print some error msg
		return false;
	}
}

bool AbstractMediaProducer::isSeekable() const
{
	if( m_cnode )
	{
		try
		{
			ISeekable* seek= m_cnode->getCheckedInterface<ISeekable>();
			if (seek && seek->hasTimeSeek() || seek->hasPercentSeek())
				return true;
		}
		catch (InterfaceNotFoundException)
		{
			return false;
		}
	}
	return false;
}

qint64 AbstractMediaProducer::currentTime() const
{
	//kDebug(1234) << k_funcinfo << endl;
	return m_currentTime;
}

qint32 AbstractMediaProducer::tickInterval() const
{
	kDebug(1234) << k_funcinfo << endl;
	return m_tickInterval;
}

void AbstractMediaProducer::setTickInterval( qint32 newTickInterval )
{
	kDebug(1234) << k_funcinfo << " " << newTickInterval << endl;
	m_tickInterval = newTickInterval;
	if (m_cnode)
	{	
		ITimeProgress_var progress(m_cnode->getInterface<ITimeProgress>());
		if (progress.get()) 
			progress->setProgressUpdateInterval(m_tickInterval*1e6);
	}
	//m_graphHandler->setTimeReportInterval(m_tickInterval);
}

void AbstractMediaProducer::play()
{
	kDebug(1234) << k_funcinfo << this->url() << endl;
	m_endTrackReceived=0;
	
	if (state() == Phonon::PausedState)
	{
		IMultiAudioVideoSynchronizer* synchronizer = m_avSync.getCheckedInterface<IMultiAudioVideoSynchronizer>();
		synchronizer->wakeup();
	}
	else
	{	
		if (m_cnode == 0)
		{
			setupGraph();
			m_cnode->reachStarted();
		}
		else
			m_cnode->reachStarted();
	}
	setState( Phonon::PlayingState );
}

void AbstractMediaProducer::pause()
{
	kDebug(1234) << k_funcinfo << endl;
	IMultiAudioVideoSynchronizer* synchronizer = m_avSync.getCheckedInterface<IMultiAudioVideoSynchronizer>();

	if (state() == Phonon::PausedState)
	{
		kDebug(1234)<<"wake up"<<endl;
		synchronizer->wakeup();
		setState( Phonon::PlayingState );
	}
	else
	{
		kDebug(1234)<<"pause"<<endl;
		synchronizer->pause();
		setState( Phonon::PausedState );
	}
}

void AbstractMediaProducer::internalStop()
{
	kDebug(1234) << k_funcinfo << endl;
	/* We reach this member function only on EOT,
	 * this is because nmm alyays wants to send EOT after stop.
	 * We wait for that eot here
	 */
	m_lastSeek=0;
	m_eod=false;
	m_currentTime=0;
	m_aboutToFinishNotEmitted=true;

	if (m_cnode)
	{
		//try{
		m_cnode->stop();
		m_cnode->deactivate();
		m_cnode->deinitOutput();
		//}
		//catch(...){}
	}
	setState( Phonon::StoppedState );
}

void AbstractMediaProducer::stop()
{
	kDebug(1234) << k_funcinfo << endl;
	if (state()==Phonon::StoppedState)
		return;

	if (m_cnode)
	{
		//try{
		IMultiAudioVideoSynchronizer* synchronizer = m_avSync.getCheckedInterface<IMultiAudioVideoSynchronizer>();
		synchronizer->wakeup();
		
		m_cnode->stop();
		m_cnode->flush();
		if (m_eod) //we are waiting end of track
			m_cnode->reachStarted();
		else //end of track will not arrive
			internalStop();

		//}
		//catch(...){}
	}
	m_eod=false;

}

void AbstractMediaProducer::seek( qint64 time )
{
	kDebug(1234) << k_funcinfo << endl;
	if( isSeekable() )
	{

		if (m_cnode)
		{
			try 
			{
				ISeekable* seek=m_cnode->getCheckedInterface<ISeekable>();
				if (seek->hasTimeSeek())
				{
					long int nsec = 0;
					long int sec = time/1e3;
					Time dest(sec, nsec);
					seek->seekTimeTo(dest);
				}
				else if (seek->hasPercentSeek() && m_trackDuration)
				{
					kDebug(1234) << "dest: " << time << ", " << m_trackDuration << endl;
					Rational dest(time, m_trackDuration);
					seek->seekPercentTo(dest);
				}
				m_currentTime=time;
			}
			catch (InterfaceNotFoundException)
			{
			}
		}

		m_lastSeek=time;
		m_cnode->flush();
	}
}

void AbstractMediaProducer::setState( State newstate )
{
	if( newstate == m_state )
		return;
	State oldstate = m_state;
	m_state = newstate;
	switch( newstate )
	{
		case Phonon::PausedState:
		case Phonon::BufferingState:
			break;
		case Phonon::PlayingState:
			break;
		case Phonon::StoppedState:
		case Phonon::ErrorState:
		case Phonon::LoadingState:
			break;
	}
	kDebug(1234) << "emit stateChanged( " << newstate << ", " << oldstate << " )" << endl;
	emit stateChanged( newstate, oldstate );
}

qint64 AbstractMediaProducer::totalTime() const
{
	return m_trackDuration;
}


void AbstractMediaProducer::setupGraph()
{
	if (!m_graphHandler)
		initGraphHandler();
	if (!m_cnode)
		m_cnode = m_graphHandler->getCompositeNode();

	if (!m_eventHandlerRegistered)
		registerEventHandler();

}

QStringList AbstractMediaProducer::availableAudioStreams() const
{
	QStringList streamList;
	int count = m_graphHandler->getNumberOfAudioStreams();  //will return -1 if not in at least stage 1.
	for (int i=0;i<count;i++)
	{
		streamList << QLatin1String( m_graphHandler->getAudioStreamName(i).c_str() );
	}
	return streamList;
}

QStringList AbstractMediaProducer::availableVideoStreams() const
{
	QStringList streamList;
	int count = m_graphHandler->getNumberOfVideoStreams();  //will return -1 if not in at least stage 1.
	for (int i=0;i<count;i++)
	{
		streamList << QLatin1String( m_graphHandler->getVideoStreamName(i).c_str() );
	}
	return streamList;
}

QStringList AbstractMediaProducer::availableSubtitleStreams() const
{
	QStringList ret;
	//TODO Subtitles currently not supported
	return ret;
}

QString AbstractMediaProducer::selectedAudioStream( const QObject* audioPath ) const
{
	return m_selectedAudioStream[ qobject_cast<const AudioPath*>( audioPath ) ];
}

QString AbstractMediaProducer::selectedVideoStream( const QObject* videoPath ) const
{
	return m_selectedVideoStream[ qobject_cast<const VideoPath*>( videoPath ) ];
}

QString AbstractMediaProducer::selectedSubtitleStream( const QObject* videoPath ) const
{
	//TODO Subtitles currently not supported
	return m_selectedSubtitleStream[ qobject_cast<const VideoPath*>( videoPath ) ];
}

void AbstractMediaProducer::selectAudioStream( const QString& streamName, const QObject* audioPath )
{
	if( availableAudioStreams().contains( streamName ) )
	{
		m_selectedAudioStream[ qobject_cast<const AudioPath*>( audioPath ) ] = streamName;
		//TODO remove obsolete branch and add new one with new stream || implement update in GraphHandler
	}
}

void AbstractMediaProducer::selectVideoStream( const QString& streamName, const QObject* videoPath )
{
	if( availableVideoStreams().contains( streamName ) )
	{
		m_selectedVideoStream[ qobject_cast<const VideoPath*>( videoPath ) ] = streamName;
		//TODO remove obsolete branch and add new one with new stream || implement update in GraphHandler
	}
}

void AbstractMediaProducer::selectSubtitleStream( const QString& streamName, const QObject* videoPath )
{
	//TODO Subtitles currently not supported
	if( availableSubtitleStreams().contains( streamName ) )
		m_selectedSubtitleStream[ qobject_cast<const VideoPath*>( videoPath ) ] = streamName;
}

Result AbstractMediaProducer::endTrack()
{
	kDebug(1234) << k_funcinfo << endl;
	
	if (!m_endTrackReceived)
	{
		this->internalStop();
		emit this->finished();
	}
	m_endTrackReceived++;
	return SUCCESS;
}

Result AbstractMediaProducer::trackDuration(Interval& i)
{
	emit length(i.sec*1e3+i.nsec/1e6);
	kDebug(1234)<<k_funcinfo<<endl;
	kDebug(1234) << "got track duration event: " << i.getPrettyString().c_str() << endl;
	this->setTrackDuration(i.sec*1e3);
	return SUCCESS;
}

void AbstractMediaProducer::setTrackDuration(qint64 duration ) 
{ 
	m_trackDuration=duration; 
}

Result AbstractMediaProducer::setTimeProgress(Time& actual)
{
	m_currentTime=m_lastSeek+actual.sec*1e3+actual.nsec/1e6;
	kDebug(1234) << k_funcinfo << "current time " << m_currentTime <<endl;
	emit tick(m_currentTime);
	if(m_trackDuration == 0)//means that no trackDuration event was received yet.
		return SUCCESS;
	if( m_currentTime >= m_trackDuration - m_aboutToFinishTime ) // about to finish
	{
		if( m_aboutToFinishNotEmitted )
		{
			kDebug(1234) << k_funcinfo << "about to finish in " << m_aboutToFinishTime <<endl;
			m_aboutToFinishNotEmitted = false;
			emit aboutToFinish( m_trackDuration - m_currentTime );
		}
	}
	kDebug(1234) << k_funcinfo << "track duration" << m_trackDuration << endl;
	return SUCCESS;
}

qint32 AbstractMediaProducer::aboutToFinishTime() const
{
	kDebug(1234) << k_funcinfo << endl;
	return m_aboutToFinishTime;
}

void AbstractMediaProducer::setAboutToFinishTime( qint32 newAboutToFinishTime )
{
	kDebug(1234) << k_funcinfo << endl;
	m_aboutToFinishTime = newAboutToFinishTime;
	if( currentTime() < m_trackDuration - m_aboutToFinishTime ) // not about to finish
		m_aboutToFinishNotEmitted = true;
}

void AbstractMediaProducer::setUrl(const KUrl& url)
{
	m_url=url.url().toUtf8().data();
}

KUrl AbstractMediaProducer::url()
{
//QString::fromUtf8( m_graphHandler->getURL()->getURL().c_str() )QString::fromUtf8( m_graphHandler->getURL()->getURL().c_str() )
	return KUrl(QString(m_url.c_str()));
}

}
}
#include "abstractmediaproducer.moc"
// vim: sw=4 ts=4 tw=80 noet
