/*
 * $Id: FGFilter.cpp 193 2007-09-09 09:12:21Z alexwild $
 *
 * (C) 2003-2006 Gabest
 * (C) 2006-2007 see AUTHORS
 *
 * This file is part of mplayerc.
 *
 * Mplayerc 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 3 of the License, or
 * (at your option) any later version.
 *
 * Mplayerc 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "stdafx.h"
#include "FGFilter.h"
#include "..\..\DSUtil\DSUtil.h"
#include "DX7AllocatorPresenter.h"
#include "DX9AllocatorPresenter.h"
#include "EVRAllocatorPresenter.h"
#include <moreuuids.h>


//
// CFGFilter
//

CFGFilter::CFGFilter(const CLSID& clsid, CStringW name, UINT64 merit)
	: m_clsid(clsid)
	, m_name(name)
{
	m_merit.val = merit;
}

const CAtlList<GUID>& CFGFilter::GetTypes() const
{
	return m_types;
}

void CFGFilter::SetTypes(const CAtlList<GUID>& types)
{
	m_types.RemoveAll();
	m_types.AddTailList(&types);
}

void CFGFilter::AddType(const GUID& majortype, const GUID& subtype)
{
	m_types.AddTail(majortype);
	m_types.AddTail(subtype);
}

bool CFGFilter::CheckTypes(const CAtlArray<GUID>& types, bool fExactMatch)
{
	POSITION pos = m_types.GetHeadPosition();
	while(pos)
	{
		const GUID& majortype = m_types.GetNext(pos);
		if(!pos) {ASSERT(0); break;}
		const GUID& subtype = m_types.GetNext(pos);

		for(int i = 0, len = types.GetCount() & ~1; i < len; i += 2)
		{
			if(fExactMatch)
			{
				if(majortype == types[i] && majortype != GUID_NULL
				&& subtype == types[i+1] && subtype != GUID_NULL)
            		return true;
			}
			else
			{
				if((majortype == GUID_NULL || types[i] == GUID_NULL || majortype == types[i])
				&& (subtype == GUID_NULL || types[i+1] == GUID_NULL || subtype == types[i+1]))
					return true;
			}
		}
	}

	return false;
}

//
// CFGFilterRegistry
//

CFGFilterRegistry::CFGFilterRegistry(IMoniker* pMoniker, UINT64 merit) 
	: CFGFilter(GUID_NULL, L"", merit)
	, m_pMoniker(pMoniker)
{
	if(!m_pMoniker) return;

	LPOLESTR str = NULL;
	if(FAILED(m_pMoniker->GetDisplayName(0, 0, &str))) return;
	m_DisplayName = m_name = str;
	CoTaskMemFree(str), str = NULL;

	CComPtr<IPropertyBag> pPB;
	if(SUCCEEDED(m_pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPB)))
	{
		CComVariant var;
		if(SUCCEEDED(pPB->Read(CComBSTR(_T("FriendlyName")), &var, NULL)))
		{
			m_name = var.bstrVal;
			var.Clear();
		}

		if(SUCCEEDED(pPB->Read(CComBSTR(_T("CLSID")), &var, NULL)))
		{
			CLSIDFromString(var.bstrVal, &m_clsid);
			var.Clear();
		}

		if(SUCCEEDED(pPB->Read(CComBSTR(_T("FilterData")), &var, NULL)))
		{			
			BSTR* pstr;
			if(SUCCEEDED(SafeArrayAccessData(var.parray, (void**)&pstr)))
			{
				ExtractFilterData((BYTE*)pstr, var.parray->cbElements*(var.parray->rgsabound[0].cElements));
				SafeArrayUnaccessData(var.parray);
			}

			var.Clear();
		}
	}

	if(merit != MERIT64_DO_USE) m_merit.val = merit;
}

CFGFilterRegistry::CFGFilterRegistry(CStringW DisplayName, UINT64 merit) 
	: CFGFilter(GUID_NULL, L"", merit)
	, m_DisplayName(DisplayName)
{
	if(m_DisplayName.IsEmpty()) return;

	CComPtr<IBindCtx> pBC;
	CreateBindCtx(0, &pBC);

	ULONG chEaten;
	if(S_OK != MkParseDisplayName(pBC, CComBSTR(m_DisplayName), &chEaten, &m_pMoniker))
		return;

	CComPtr<IPropertyBag> pPB;
	if(SUCCEEDED(m_pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPB)))
	{
		CComVariant var;
		if(SUCCEEDED(pPB->Read(CComBSTR(_T("FriendlyName")), &var, NULL)))
		{
			m_name = var.bstrVal;
			var.Clear();
		}

		if(SUCCEEDED(pPB->Read(CComBSTR(_T("CLSID")), &var, NULL)))
		{
			CLSIDFromString(var.bstrVal, &m_clsid);
			var.Clear();
		}

		if(SUCCEEDED(pPB->Read(CComBSTR(_T("FilterData")), &var, NULL)))
		{			
			BSTR* pstr;
			if(SUCCEEDED(SafeArrayAccessData(var.parray, (void**)&pstr)))
			{
				ExtractFilterData((BYTE*)pstr, var.parray->cbElements*(var.parray->rgsabound[0].cElements));
				SafeArrayUnaccessData(var.parray);
			}

			var.Clear();
		}
	}

	if(merit != MERIT64_DO_USE) m_merit.val = merit;
}

CFGFilterRegistry::CFGFilterRegistry(const CLSID& clsid, UINT64 merit) 
	: CFGFilter(clsid, L"", merit)
{
	if(m_clsid == GUID_NULL) return;

	CString guid = CStringFromGUID(m_clsid);

	CRegKey key;

	if(ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("CLSID\\") + guid, KEY_READ))
	{
		ULONG nChars = 0;
		if(ERROR_SUCCESS == key.QueryStringValue(NULL, NULL, &nChars))
		{
			CString name;
			if(ERROR_SUCCESS == key.QueryStringValue(NULL, name.GetBuffer(nChars), &nChars))
			{
				name.ReleaseBuffer(nChars);
				m_name = name;
			}
		}

		key.Close();
	}

	CRegKey catkey;

	if(ERROR_SUCCESS == catkey.Open(HKEY_CLASSES_ROOT, _T("CLSID\\{083863F1-70DE-11d0-BD40-00A0C911CE86}\\Instance"), KEY_READ))
	{
		if(ERROR_SUCCESS != key.Open(catkey, guid, KEY_READ))
		{
			// illiminable pack uses the name of the filter and not the clsid, have to enum all keys to find it...

			FILETIME ft;
			TCHAR buff[256];
			DWORD len = countof(buff);
			for(DWORD i = 0; ERROR_SUCCESS == catkey.EnumKey(i, buff, &len, &ft); i++, len = countof(buff))
			{
				if(ERROR_SUCCESS == key.Open(catkey, buff, KEY_READ))
				{
					TCHAR clsid[256];
					len = countof(clsid);
					if(ERROR_SUCCESS == key.QueryStringValue(_T("CLSID"), clsid, &len) && GUIDFromCString(clsid) == m_clsid)
						break;

					key.Close();
				}
			}
		}

		if(key)
		{
			ULONG nChars = 0;
			if(ERROR_SUCCESS == key.QueryStringValue(_T("FriendlyName"), NULL, &nChars))
			{
				CString name;
				if(ERROR_SUCCESS == key.QueryStringValue(_T("FriendlyName"), name.GetBuffer(nChars), &nChars))
				{
					name.ReleaseBuffer(nChars);
					m_name = name;
				}
			}

			ULONG nBytes = 0;
			if(ERROR_SUCCESS == key.QueryBinaryValue(_T("FilterData"), NULL, &nBytes))
			{
				CAutoVectorPtr<BYTE> buff;
				if(buff.Allocate(nBytes) && ERROR_SUCCESS == key.QueryBinaryValue(_T("FilterData"), buff, &nBytes))
				{
					ExtractFilterData(buff, nBytes);
				}
			}

			key.Close();
		}
	}

	if(merit != MERIT64_DO_USE) m_merit.val = merit;
}

HRESULT CFGFilterRegistry::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	HRESULT hr = E_FAIL;
	
	if(m_pMoniker)
	{
		if(SUCCEEDED(hr = m_pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)ppBF)))
		{
			m_clsid = ::GetCLSID(*ppBF);
		}
	}
	else if(m_clsid != GUID_NULL)
	{
		CComQIPtr<IBaseFilter> pBF;

		if(FAILED(pBF.CoCreateInstance(m_clsid))) 
			return E_FAIL;

		*ppBF = pBF.Detach();
		
		hr = S_OK;
	}

	return hr;
};

[uuid("97f7c4d4-547b-4a5f-8332-536430ad2e4d")]
interface IAMFilterData : public IUnknown
{
	STDMETHOD (ParseFilterData) (BYTE* rgbFilterData, ULONG cb, BYTE** prgbRegFilter2) PURE;
	STDMETHOD (CreateFilterData) (REGFILTER2* prf2, BYTE** prgbFilterData, ULONG* pcb) PURE;
};

void CFGFilterRegistry::ExtractFilterData(BYTE* p, UINT len)
{
	CComPtr<IAMFilterData> pFD;
	BYTE* ptr = NULL;

	if(SUCCEEDED(pFD.CoCreateInstance(CLSID_FilterMapper2))
	&& SUCCEEDED(pFD->ParseFilterData(p, len, (BYTE**)&ptr)))
	{
		REGFILTER2* prf = (REGFILTER2*)*(DWORD*)ptr; // this is f*cked up

		m_merit.mid = prf->dwMerit;

		if(prf->dwVersion == 1)
		{
			for(UINT i = 0; i < prf->cPins; i++)
			{
				if(prf->rgPins[i].bOutput)
					continue;

				for(UINT j = 0; j < prf->rgPins[i].nMediaTypes; j++)
				{
					if(!prf->rgPins[i].lpMediaType[j].clsMajorType || !prf->rgPins[i].lpMediaType[j].clsMinorType)
						break;

					const REGPINTYPES& rpt = prf->rgPins[i].lpMediaType[j];
					AddType(*rpt.clsMajorType, *rpt.clsMinorType);
				}
			}
		}
		else if(prf->dwVersion == 2)
		{
			for(UINT i = 0; i < prf->cPins2; i++)
			{
				if(prf->rgPins2[i].dwFlags&REG_PINFLAG_B_OUTPUT)
					continue;

				for(UINT j = 0; j < prf->rgPins2[i].nMediaTypes; j++)
				{
					if(!prf->rgPins2[i].lpMediaType[j].clsMajorType || !prf->rgPins2[i].lpMediaType[j].clsMinorType)
						break;

					const REGPINTYPES& rpt = prf->rgPins2[i].lpMediaType[j];
					AddType(*rpt.clsMajorType, *rpt.clsMinorType);
				}
			}
		}

		CoTaskMemFree(prf);
	}
	else
	{
		BYTE* base = p;

		#define ChkLen(size) if(p - base + size > (int)len) return;

		ChkLen(4)
		if(*(DWORD*)p != 0x00000002) return; // only version 2 supported, no samples found for 1
		p += 4;

		ChkLen(4)
		m_merit.mid = *(DWORD*)p; p += 4;

		m_types.RemoveAll();

		ChkLen(8)
		DWORD nPins = *(DWORD*)p; p += 8;
		while(nPins-- > 0)
		{
			ChkLen(1)
			BYTE n = *p-0x30; p++;
			
			ChkLen(2)
			WORD pi = *(WORD*)p; p += 2;
			ASSERT(pi == 'ip');

			ChkLen(1)
			BYTE x33 = *p; p++;
			ASSERT(x33 == 0x33);

			ChkLen(8)
			bool fOutput = !!(*p&REG_PINFLAG_B_OUTPUT);
			p += 8;

			ChkLen(12)
			DWORD nTypes = *(DWORD*)p; p += 12;
			while(nTypes-- > 0)
			{
				ChkLen(1)
				BYTE n = *p-0x30; p++;

				ChkLen(2)
				WORD ty = *(WORD*)p; p += 2;
				ASSERT(ty == 'yt');

				ChkLen(5)
				BYTE x33 = *p; p++;
				ASSERT(x33 == 0x33);
				p += 4;

				ChkLen(8)
				if(*(DWORD*)p < (p-base+8) || *(DWORD*)p >= len 
				|| *(DWORD*)(p+4) < (p-base+8) || *(DWORD*)(p+4) >= len)
				{
					p += 8;
					continue;
				}

				GUID majortype, subtype;
				memcpy(&majortype, &base[*(DWORD*)p], sizeof(GUID)); p += 4;
				if(!fOutput) AddType(majortype, subtype); 
			}
		}

		#undef ChkLen
	}
}

//
// CFGFilterFile
//

CFGFilterFile::CFGFilterFile(const CLSID& clsid, CString path, CStringW name, UINT64 merit)
	: CFGFilter(clsid, name, merit)
	, m_path(path)
	, m_hInst(NULL)
{
}

HRESULT CFGFilterFile::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	return LoadExternalFilter(m_path, m_clsid, ppBF);
}

//
// CFGFilterVideoRenderer
//

CFGFilterVideoRenderer::CFGFilterVideoRenderer(HWND hWnd, const CLSID& clsid, CStringW name, UINT64 merit) 
	: CFGFilter(clsid, name, merit)
	, m_hWnd(hWnd)
{
	AddType(MEDIATYPE_Video, MEDIASUBTYPE_NULL);
}

HRESULT CFGFilterVideoRenderer::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	HRESULT hr = S_OK;

	CComPtr<ISubPicAllocatorPresenter> pCAP;

	if(m_clsid == CLSID_VMR7AllocatorPresenter 
	|| m_clsid == CLSID_VMR9AllocatorPresenter 
	|| m_clsid == CLSID_DXRAllocatorPresenter
	|| m_clsid == CLSID_EVRAllocatorPresenter)
	{
		if(SUCCEEDED(CreateAP7(m_clsid, m_hWnd, &pCAP))
		|| SUCCEEDED(CreateAP9(m_clsid, m_hWnd, &pCAP))
		|| SUCCEEDED(CreateEVR(m_clsid, m_hWnd, &pCAP)))
		{
			CComPtr<IUnknown> pRenderer;
			if(SUCCEEDED(hr = pCAP->CreateRenderer(&pRenderer)))
			{
				*ppBF = CComQIPtr<IBaseFilter>(pRenderer).Detach();
				pUnks.AddTail(pCAP);
			}
		}
	}
	else
	{
		CComPtr<IBaseFilter> pBF;
		if(SUCCEEDED(pBF.CoCreateInstance(m_clsid)))
		{
			BeginEnumPins(pBF, pEP, pPin)
			{
				if(CComQIPtr<IMixerPinConfig, &IID_IMixerPinConfig> pMPC = pPin)
				{
					pUnks.AddTail(pMPC);
					break;
				}
			}
			EndEnumPins

			*ppBF = pBF.Detach();
		}
	}

	if(!*ppBF) hr = E_FAIL;

	return hr;
}

//
// CFGFilterList
//

CFGFilterList::CFGFilterList()
{
}

CFGFilterList::~CFGFilterList()
{
	RemoveAll();
}

void CFGFilterList::RemoveAll()
{
	while(!m_filters.IsEmpty())
	{
		const filter_t& f = m_filters.RemoveHead();
		if(f.autodelete) delete f.pFGF;
	}

	m_sortedfilters.RemoveAll();
}

void CFGFilterList::Insert(CFGFilter* pFGF, int group, bool exactmatch, bool autodelete)
{
	if(CFGFilterRegistry* f1r = dynamic_cast<CFGFilterRegistry*>(pFGF))
	{
		POSITION pos = m_filters.GetHeadPosition();
		while(pos)
		{
			filter_t& f2 = m_filters.GetNext(pos);

			if(group != f2.group) continue;

			if(CFGFilterRegistry* f2r = dynamic_cast<CFGFilterRegistry*>(f2.pFGF))
			{
				if(f1r->GetMoniker() && f2r->GetMoniker() && S_OK == f1r->GetMoniker()->IsEqual(f2r->GetMoniker())
				|| f1r->GetCLSID() != GUID_NULL && f1r->GetCLSID() == f2r->GetCLSID())
				{
					TRACE(_T("FGM: Inserting %d %d %016I64x '%s' NOT!\n"), 
						group, exactmatch, pFGF->GetMerit(),
						pFGF->GetName().IsEmpty() ? CStringFromGUID(pFGF->GetCLSID()) : CString(pFGF->GetName()));

					if(autodelete) delete pFGF;
					return;
				}
			}
		}
	}

	POSITION pos = m_filters.GetHeadPosition();
	while(pos)
	{
		if(m_filters.GetNext(pos).pFGF == pFGF)
		{
			TRACE(_T("FGM: Inserting %d %d %016I64x '%s' DUP!\n"), 
				group, exactmatch, pFGF->GetMerit(),
				pFGF->GetName().IsEmpty() ? CStringFromGUID(pFGF->GetCLSID()) : CString(pFGF->GetName()));

			if(autodelete) delete pFGF;
			return;
		}
	}

	TRACE(_T("FGM: Inserting %d %d %016I64x '%s'\n"), 
		group, exactmatch, pFGF->GetMerit(),
		pFGF->GetName().IsEmpty() ? CStringFromGUID(pFGF->GetCLSID()) : CString(pFGF->GetName()));

	filter_t f = {m_filters.GetCount(), pFGF, group, exactmatch, autodelete};
	m_filters.AddTail(f);

	m_sortedfilters.RemoveAll();
}

POSITION CFGFilterList::GetHeadPosition()
{
	if(m_sortedfilters.IsEmpty())
	{
		CAtlArray<filter_t> sort;
		sort.SetCount(m_filters.GetCount());
		POSITION pos = m_filters.GetHeadPosition();
		for(int i = 0; pos; i++) sort[i] = m_filters.GetNext(pos);
		qsort(&sort[0], sort.GetCount(), sizeof(sort[0]), filter_cmp);
		for(size_t i = 0; i < sort.GetCount(); i++) 
			if(sort[i].pFGF->GetMerit() >= MERIT64_DO_USE) 
				m_sortedfilters.AddTail(sort[i].pFGF);
	}

	TRACE(_T("FGM: Sorting filters\n"));

	POSITION pos = m_sortedfilters.GetHeadPosition();
	while(pos)
	{
		CFGFilter* pFGF = m_sortedfilters.GetNext(pos);
		TRACE(_T("FGM: - %016I64x '%s'\n"), pFGF->GetMerit(), pFGF->GetName().IsEmpty() ? CStringFromGUID(pFGF->GetCLSID()) : CString(pFGF->GetName()));
	}

	return m_sortedfilters.GetHeadPosition();
}

CFGFilter* CFGFilterList::GetNext(POSITION& pos)
{
	return m_sortedfilters.GetNext(pos);
}

int CFGFilterList::filter_cmp(const void* a, const void* b)
{
	filter_t* fa = (filter_t*)a;
	filter_t* fb = (filter_t*)b;

	if(fa->group < fb->group) return -1;
	if(fa->group > fb->group) return +1;

	if(fa->pFGF->GetCLSID() == fb->pFGF->GetCLSID())
	{
		CFGFilterFile* fgfa = dynamic_cast<CFGFilterFile*>(fa->pFGF);
		CFGFilterFile* fgfb = dynamic_cast<CFGFilterFile*>(fb->pFGF);

		if(fgfa && !fgfb) return -1;
		if(!fgfa && fgfb) return +1;
	}

	if(fa->pFGF->GetMerit() > fb->pFGF->GetMerit()) return -1;
	if(fa->pFGF->GetMerit() < fb->pFGF->GetMerit()) return +1;

	if(fa->exactmatch && !fb->exactmatch) return -1;
	if(!fa->exactmatch && fb->exactmatch) return +1;

	if(fa->index < fb->index) return -1;
	if(fa->index > fb->index) return +1;

	return 0;
}
