/*
	Now playing plugin for Sonique 2.0
	Copyright (C) 2003  Jerome Kuptz

	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "MediaFilterNow.h"
#include "sq2_now_playing.h"

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////// NowFilter /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

MError MediaErrors[ERR_MEDIA__NUM];

NowFilter::NowFilter()
{
	MGENERIC_CONSTRUCTOR();

	m_bEnabled     = true;
	m_bStoreFile   = false;
	m_bUseUnicode  = false;
	m_bMirc        = false;
	m_State        = MediaState_Stopped;
	m_pChain       = NULL;
	m_pAudioFormat = NULL;
	m_pMyDest      = NULL;
	m_pPrefs       = NULL;

	m_ErrorSys->RegisterErrors(GetMediaErrors(MediaErrors), lenof(MediaErrors), ERR_MEDIA__MODULE);
}

NowFilter::~NowFilter()
{
	ChangeState(MediaState_Stopped);

	if (m_pAudioFormat)
		m_pAudioFormat = NULL;
}

MRESULT NowFilter::QueryModule(const MID& mid, void** ppv, bool bAddReference , const MGeneric* pPrev  )
{
	if( mid == MID_MEDIAFILTER || mid == MID_MEDIAFILTER_NOW) {
		*ppv = (MMediaFilter*)this;
	} else if( mid == MID_MEDIAINFORMATION ) {
		*ppv = (MMediaInformation*)this;
	} else if( mid == MID_MEDIASETTINGS ) {
		*ppv = (MMediaSettings*)this;
	} else {
		*ppv = NULL;
		return GetBasicError(ERR_BASIC_UNKNOWNOBJECT);
	}
	if( *ppv )
	{
		if( bAddReference )
			((MGeneric*)*ppv)->AddRef();
		return M_OK;
	}

	return GetBasicError(ERR_BASIC_OUTOFMEMORY);
}

///////////////////////////////////////////////////////// MMediaFilter /////////////////////////////////////////////////////////
MRESULT NowFilter::GetNumInputs( ) const
{
	return 1;
}
MRESULT NowFilter::GetInput( MMediaInput** ppToHere, int32 nWhich ) const
{
	if( nWhich == 0 )
	{
		*ppToHere = (NowFilter*)this;
		return M_OK;
	} else {
		return GetBasicError(ERR_BASIC_NOTFOUND);
	}
}
MRESULT NowFilter::GetInput( MMediaInput** ppToHere, const wchar_t* wsWhich ) const
{
	if( !GetInputName().CompareTo(wsWhich) )
	{
		*ppToHere = (NowFilter*)this;
		return M_OK;
	} else {
		return GetBasicError(ERR_BASIC_NOTFOUND);
	}
}
MRESULT NowFilter::GetNumOutputs( ) const
{
	return 1;
}
MRESULT NowFilter::GetOutput( MMediaOutput** ppToHere, int32 nWhich ) const
{
	if( nWhich == 0 )
	{
		*ppToHere = (NowFilter*)this;
		return M_OK;
	} else {
		return GetBasicError(ERR_BASIC_NOTFOUND);
	}
}
MRESULT NowFilter::AddedToChain( MMediaChain* pChain )
{
	m_pChain = pChain;
	return M_OK;
}
MRESULT NowFilter::GetOutput( MMediaOutput** ppToHere, const wchar_t* wsWhich ) const
{
	if( !GetOutputName().CompareTo(wsWhich) )
	{
		*ppToHere = (NowFilter*)this;
		return M_OK;
	} else {
		return GetBasicError(ERR_BASIC_NOTFOUND);
	}
}


///////////////////////////////////////////////////////// MMediaInput /////////////////////////////////////////////////////////
WString NowFilter::GetInputName( ) const
{
	return L"input";
}

MRESULT NowFilter::CanHandle( MMediaOutput* pOutput, uint32 *plConfidence, uint32 *plMerit )
{
	if( m_State != MediaState_Stopped && m_State != MediaState_FormatDetection )
		return GetError(MediaErrors, ERR_MEDIA_NOTVALIDINTHISSTATE);

	AutoCriticalSection acs(&m_NowCs);

	Array< RefPtr<MMediaFormat> > CompatibleFormats;

	MRESULT res = GetCompatibleFormats(pOutput, CompatibleFormats);
	if( FAILED(res) )
		return res;

	if( CompatibleFormats.GetCount() )
	{
		*plConfidence = 100;
		*plMerit = 1;
	} else {
		*plConfidence = 0;
		*plMerit = 0;

	}

	return M_OK;
}

MRESULT NowFilter::GetCompatibleFormats( MMediaOutput* pOutput, Array<RefPtr<MMediaFormat> >& CompatibleFormats )
{
	Array<RefPtr<MMediaFormat> > Formats;
	MMediaFormat* pFormat;
	MRESULT res;
	uint32 i;

	res = pOutput->GetFormats(Formats);
	if( FAILED(res) )
		return res;

	for( i = 0; i < Formats.GetCount(); i++ )
	{
		pFormat = Formats[i];
		if( pFormat->MediaType == MID_MEDIATYPE_PCM )
		{
			continue;
		}
		Formats.RemoveAt(i);
		i--;
	}
	CompatibleFormats = Formats;

	return M_OK;
}

MRESULT NowFilter::AttachInput( MMediaOutput* pOutput )
{
	m_pMySource = pOutput;
	return M_OK;
}

MRESULT NowFilter::GetFilter( MMediaFilter** ppToHere )
{
	*ppToHere = this;
	return M_OK;
}


bool NowFilter::FillOutFormatting(WString wsFormat, WString& wsDest)
{
	// fields support
	//%a = artist
	//%t = title
	//%l = album
	//%y = year
	//%f = format
	//%g = genre
	//%r = track
	//%u = file url/path
	//%n = filename
	//%c = comments
	//%b = bitrate
	//%d = duration
	//%v = cbr/vbr
	//%s = samplerate
	//%x = version string

	int x = 0;
	int iLen = wsFormat.Len();

	while (x < iLen) {
		if (wsFormat[x] == '%') {
			x++;
			WString wsInfo;
			if (wsFormat[x] == 'a') {
				m_pChain->GetStringInfo(L"artist", wsInfo);
			} else if (wsFormat[x] == 't') {
				m_pChain->GetStringInfo(L"title", wsInfo);
			} else if (wsFormat[x] == 'y') {
				m_pChain->GetStringInfo(L"year", wsInfo);
			} else if (wsFormat[x] == 'f') {
				m_pChain->GetStringInfo(L"format", wsInfo);
			} else if (wsFormat[x] == 'g') {
				m_pChain->GetStringInfo(L"genre", wsInfo);
			} else if (wsFormat[x] == 'r') {
				m_pChain->GetStringInfo(L"track", wsInfo);
			} else if (wsFormat[x] == 'c') {
				m_pChain->GetStringInfo(L"comment", wsInfo);
			} else if (wsFormat[x] == 'u') {
				m_pChain->GetStringInfo(L"URL", wsInfo);
			} else if (wsFormat[x] == 'l') {
				m_pChain->GetStringInfo(L"album", wsInfo);
			} else if (wsFormat[x] == 'b') {
				int32 wsInteger;
				m_pChain->GetIntegerInfo(L"averagebitrate", wsInteger);
				wsInteger = wsInteger/1000;
				char buffer[50];
				sprintf(buffer, "%i", wsInteger);
				wsInfo = (WString)buffer;
			} else if (wsFormat[x] == 'd') {
				double wsReal;
				double wsHour;
				double wsMinute;
				double wsSecond;

				m_pChain->GetRealInfo(L"totaltime", wsReal);

				wsMinute = wsReal/60;
				wsSecond = (int)wsReal%60;
				wsHour   = wsMinute/60;
				wsMinute = (int)wsMinute%60;

				char buffer[50];
				if (wsHour < 1) {
					sprintf(buffer, "%02d:%02d",(int)wsMinute,(int)wsSecond);
				}
				else {
					sprintf(buffer, "%d:%02d:%02d",(int)wsHour,(int)wsMinute,(int)wsSecond);
				}
				wsInfo = (WString)buffer;
			} else if (wsFormat[x] == 'v') {
				int32 wsInteger;
				m_pChain->GetIntegerInfo(L"IsVBR", wsInteger);
				wsInfo = wsInteger == 1 ? "VBR" : "CBR";
			} else if (wsFormat[x] == 's') {
				int32 wsInteger;
				double wsReal;
				m_pChain->GetIntegerInfo(L"samplerate", wsInteger);
				wsReal = wsInteger/1000.0;
				char buffer[50];
				sprintf(buffer, "%g", wsReal);
				wsInfo = (WString)buffer;
			} else if (wsFormat[x] == 'e') {
				m_pChain->GetStringInfo(L"URL", wsInfo);
				wsInfo = filesys::GetExtension(wsInfo);
				wsInfo = wsInfo.SubString(1);
			} else if (wsFormat[x] == 'n') {
				m_pChain->GetStringInfo(L"URL", wsInfo);
				wsInfo = filesys::GetBaseName(wsInfo);
			} else if (wsFormat[x] == 'x') {
				Sonique2CoreCtrlPtr pCore = Sonique2CoreCtrlPtr();
				wsInfo = pCore->GetVersionString();
			} else if (wsFormat[x] == 'h') {
				int32 wsInteger;
				m_pChain->GetIntegerInfo(L"channels", wsInteger);
				if (wsInteger == 1) {
					wsInfo = "Mono";
				}
				else if (wsInteger == 2) {
					wsInfo = "Stereo";
				}
				else if (wsInteger > 2) {
					char buffer[50];
					sprintf(buffer, "%d Channels", wsInteger);
					wsInfo = (WString)buffer;
				}
			}

			wsDest += wsInfo;
		} else {
			wsDest.AppendChars(wsFormat[x], 1);
		}

		x++;
	}

	return true;
}


MRESULT NowFilter::ChangeState( MediaState State )
{
	m_NowCs.Enter();

	// we only care about a new play.
	// Other possible states of interest for this plugin could be...
	//MediaState_Stopped
	//MediaState_Paused
	if (m_State != MediaState_Playing && State == MediaState_Playing) {
		pConsole = ConsoleServerPtr();
		WString wsOutput;

		if (m_bStoreFile) {
			IOObject m_pFile;
			m_pFile.OpenFile(m_wsFile, access_write|share_read|create_always|hint_sequential);
			// write the output to a file.  File defaults to ACII.
			if (m_pFile.IsOpen()) {
				FillOutFormatting(m_wsFormat, wsOutput);
				if (m_bUseUnicode) {
					m_pFile.WriteString(wsOutput); // writes as unicode
				}
				else {
					m_pFile.WriteString(AString(wsOutput)); // writes as standard ascii text file.
				}
			} else {
				pConsole->FPrint(console_error, L"unable to open/create the file %s\r\n", AString(m_wsFile));
			}

			m_pFile.Close();
		}

		wsOutput = "";

		if (m_bMirc) {
			/* eh... this is probably incomplete.  I did not test it as
			far as using another application that communicates with mIRC at
			the same time. */
			HWND hwnd = FindWindow("mIRC", NULL);

			if (hwnd != NULL) {
				FillOutFormatting(m_wsMircCmd, wsOutput);

				int iLen = wsOutput.Len();

				HANDLE hMap = CreateFileMapping(NULL,
								NULL,
								PAGE_READWRITE,
								0,
								iLen,
								"mIRC");

				if (GetLastError() == ERROR_ALREADY_EXISTS) {
					DWORD dSize = 0;

					if (GetFileSize(hMap, &dSize) == NO_ERROR) {
						iLen = (int) dSize;
					}
				}

				if (hMap != NULL) {
					char* szContents = (char*) MapViewOfFile(hMap,
											 FILE_MAP_ALL_ACCESS,
											 0,
											 0,
											 iLen);

					// update the file map with the command
					lstrcpy(szContents,AString(wsOutput));

					// issue the configured mirc command to the current instance of mIRC.
					long lRes =  SendMessage(hwnd,  WM_MCOMMAND, 1, 0L);

					UnmapViewOfFile(szContents);
					CloseHandle(hMap);
				} else {
					pConsole->Output(console_error, L"unable to establish interprocess communication with mirc");
				}

			} else {
				pConsole->Output(console_error, L"could not find running instance of mirc");
			}
		}
	}

	m_State = State;
	m_NowCs.Leave();
	return M_OK;
}
MRESULT NowFilter::SeekFilter( double tTime, bool bAccurately, bool bFlush )
{
	return M_OK;
}
MRESULT NowFilter::FlushFilter( )
{
	return M_OK;
}

MRESULT NowFilter::GatherFilterInformation( InformationHint Hint )
{
	return M_OK;
}



///////////////////////////////////////////////////////// MMediaOutput /////////////////////////////////////////////////////////

WString NowFilter::GetOutputName( ) const
{
	return L"output";
}
MRESULT NowFilter::GetPacket( RefPtr<MPacket>& pToHere )
{
	// we just pass the packets along.  If one was to write a real DSP
	// plugin then this is the point where you'd make your data modifications,
	// and pass them up the chain in pToHere.
	RefPtr<MPacket> pToNew;
	MRESULT res = m_pMySource->GetPacket(pToNew);

	if( FAILED(res) )
		return res;

	pToHere = pToNew;

	return M_OK;
}

MRESULT NowFilter::GetFormats( Array<RefPtr<MMediaFormat> >& Formats )
{
	MRESULT res = GetCompatibleFormats(m_pMySource, Formats);
	if( FAILED(res) )
		return res;
	return M_OK;
}

MRESULT NowFilter::SelectFormat( MMediaFormat* pFormat )
{
	return m_pMySource->SelectFormat(pFormat);
}
MRESULT NowFilter::AttachOutput( MMediaInput* pInput )
{
	m_pMyDest = pInput;
	return M_OK;
}


///////////////////////////////////////////////////////// MMediaInformation /////////////////////////////////////////////////////////

MRESULT NowFilter::GetStringInfo( const wchar_t* wsWhich, WString& wsToHere )
{

	if( !WString::Compare(wsWhich, L"modulename") )
	{
		wsToHere = "NowPlaying";
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"moduledescription") ) {
		wsToHere = "Sonique 2 Now Playing announcer";
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"modulecopyright") ) {
		wsToHere = "2003 Jerome Kuptz";
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"mirc_cmd") ) {
		wsToHere = m_wsMircCmd;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"filefrmt") ) {
		wsToHere = m_wsFormat;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"file") ) {
		wsToHere = m_wsFile;
		return M_OK;
	}

	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::GetIntegerInfo( const wchar_t* wsWhich, int32& lToHere )
{
	if( !WString::Compare(wsWhich, L"enabled") )
	{
		lToHere = m_bEnabled;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"usefile") ) {
		lToHere = m_bStoreFile;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"useunicode") ) {
		lToHere = m_bUseUnicode;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"usemirc") ) {
		lToHere = m_bMirc;
		return M_OK;
	}
	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::GetRealInfo( const wchar_t* wsWhich, double& lfToHere )
{
	if( !WString::Compare(wsWhich, L"moduleversion") ) {
		lfToHere = 1.0;
		return M_OK;
	}
	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::EnumerateInformation( Array<MediaInfo>& Info )
{

	MediaInfo* i;

	i = &Info.AddTail();
	i->Name = L"modulename";
	i->Desc = L"the name of this audio component";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();
	i->Name = L"moduledescription";
	i->Desc = L"the human readable name of this audio component";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();
	i->Name = L"modulecopyright";
	i->Desc = L"the owner of the copyright of this audio component";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();
	i->Name = L"moduleversion";
	i->Desc = L"the revision number of this audio component";
	i->Type = (MediaInfoType)(MediaInfo_Real|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	i->Name = L"usemirc";
	i->Desc = L"enables mirc announcements";
	i->Type = (MediaInfoType)(MediaInfo_Integer|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	i->Name = L"usefile";
	i->Desc = L"enables writes of current track to a file";
	i->Type = (MediaInfoType)(MediaInfo_Integer|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	i->Name = L"mirc_cmd";
	i->Desc = L"the command to use for mIRC";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	i->Name = L"file";
	i->Desc = L"full path of file to write to";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	i->Name = L"filefrmt";
	i->Desc = L"format of the text to write.";
	i->Type = (MediaInfoType)(MediaInfo_String|MediaInfo_ModuleSpecific);
	i = &Info.AddTail();

	return M_OK;
}


///////////////////////////////////////////////////////// MMediaSettings /////////////////////////////////////////////////////////

MRESULT NowFilter::SetStringInfo( const wchar_t* wsWhich, const WString& Info )
{
	AutoCriticalSection acs(&m_NowCs);
	if( !WString::Compare(wsWhich, L"mirc_cmd") ) {
		m_wsMircCmd = Info;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"filefrmt") ) {
		m_wsFormat = Info;
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"file") ) {
		m_wsFile = Info;
		return M_OK;
	}

	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::SetIntegerInfo( const wchar_t* wsWhich, int32 Info )
{
	AutoCriticalSection acs(&m_NowCs);
	if( !WString::Compare(wsWhich, L"enabled") )
	{
		m_bEnabled = (Info != 0);
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"usefile") ) {
		m_bStoreFile= (Info != 0);
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"useunicode") ) {
		m_bUseUnicode= (Info != 0);
		return M_OK;
	} else if( !WString::Compare(wsWhich, L"usemirc") ) {
		m_bMirc = (Info != 0);
		return M_OK;
	}
	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::SetRealInfo( const wchar_t* wsWhich, double Info )
{
	return GetBasicError(ERR_BASIC_NOTFOUND);
}
MRESULT NowFilter::LoadSettings( MPreferences* pPrefs )
{
	int32 lVal;

	if( SUCCEEDED(pPrefs->GetInt( L"enabled", lVal)) )
		SetIntegerInfo(L"enabled", lVal);
	else
	{
		if( SUCCEEDED(GetIntegerInfo(L"enabled", lVal)) )
			pPrefs->SetInt( L"enabled", lVal);
	}

	if( SUCCEEDED(pPrefs->GetInt( L"usefile", lVal)) )
		SetIntegerInfo(L"usefile", lVal);
	else
	{
		if( SUCCEEDED(GetIntegerInfo(L"usefile", lVal)) )
			pPrefs->SetInt( L"usefile", lVal);
	}

	if( SUCCEEDED(pPrefs->GetInt( L"useunicode", lVal)) )
		SetIntegerInfo(L"useunicode", lVal);
	else
	{
		if( SUCCEEDED(GetIntegerInfo(L"useunicode", lVal)) )
			pPrefs->SetInt( L"useunicode", lVal);
	}

	if( SUCCEEDED(pPrefs->GetInt( L"usemirc", lVal)) )
		SetIntegerInfo(L"usemirc", lVal);
	else
	{
		if( SUCCEEDED(GetIntegerInfo(L"usemirc", lVal)) )
			pPrefs->SetInt( L"usemirc", lVal);
	}

	WString wsVal;

	if( SUCCEEDED(pPrefs->GetWString( L"file", wsVal)) )
		SetStringInfo(L"file", wsVal);
	else
	{
		if( SUCCEEDED(GetStringInfo(L"file", wsVal)) )
			pPrefs->SetWString( L"file", wsVal);
	}

	if( SUCCEEDED(pPrefs->GetWString( L"filefrmt", wsVal)) )
		SetStringInfo(L"filefrmt", wsVal);
	else
	{
		if( SUCCEEDED(GetStringInfo(L"filefrmt", wsVal)) )
			pPrefs->SetWString( L"filefrmt", wsVal);
	}

		if( SUCCEEDED(pPrefs->GetWString( L"mirc_cmd", wsVal)) )
		SetStringInfo(L"mirc_cmd", wsVal);
	else
	{
		if( SUCCEEDED(GetStringInfo(L"mirc_cmd", wsVal)) )
			pPrefs->SetWString( L"mirc_cmd", wsVal);
	}

	m_pPrefs = pPrefs;
	return M_OK;
}
MRESULT NowFilter::SaveSettings( MPreferences* pPrefs )
{
	AutoCriticalSection acs(&m_NowCs);
	int32 lVal;

	if( SUCCEEDED(GetIntegerInfo(L"enabled", lVal)) )
		pPrefs->SetInt( L"enabled", lVal);

	if( SUCCEEDED(GetIntegerInfo(L"usefile", lVal)) )
		pPrefs->SetInt( L"usefile", lVal);

	if( SUCCEEDED(GetIntegerInfo(L"useunicode", lVal)) )
		pPrefs->SetInt( L"useunicode", lVal);

	if( SUCCEEDED(GetIntegerInfo(L"usemirc", lVal)) )
		pPrefs->SetInt( L"usemirc", lVal);

	WString wsVal;

	if( SUCCEEDED(GetStringInfo(L"file", wsVal)) )
		pPrefs->SetWString( L"file", wsVal);

	if( SUCCEEDED(GetStringInfo(L"filefrmt", wsVal)) )
		pPrefs->SetWString( L"filefrmt", wsVal);

	if( SUCCEEDED(GetStringInfo(L"mirc_cmd", wsVal)) )
		pPrefs->SetWString( L"mirc_cmd", wsVal);

	return M_OK;
}
MRESULT	NowFilter::FilterMessage( const wchar_t* wsMessageName, void* pData )
{
	if( !WString::Compare(wsMessageName, L"NowPlayingPluginReloadSettings") )
	{
		LoadSettings(m_pPrefs);
	}

	return M_OK;
}
