/* 
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  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, 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "bufferfilter.h"
#include "..\..\..\DSUtil\DSUtil.h"

#ifdef REGISTER_FILTER

const AMOVIESETUP_MEDIATYPE sudPinTypesIn[] =
{
	{&MEDIATYPE_NULL, &MEDIASUBTYPE_NULL},
};

const AMOVIESETUP_MEDIATYPE sudPinTypesOut[] =
{
	{&MEDIATYPE_NULL, &MEDIASUBTYPE_NULL},
};

const AMOVIESETUP_PIN sudpPins[] =
{
    {L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, NULL, countof(sudPinTypesIn), sudPinTypesIn},
    {L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, countof(sudPinTypesOut), sudPinTypesOut}
};

const AMOVIESETUP_FILTER sudFilter[] =
{
	{&__uuidof(CBufferFilter), L"MPC - Buffer Filter", MERIT_DO_NOT_USE, countof(sudpPins), sudpPins}
};

CFactoryTemplate g_Templates[] =
{
	{sudFilter[0].strName, sudFilter[0].clsID, CreateInstance<CBufferFilter>, NULL, &sudFilter[0]}
};

int g_cTemplates = countof(g_Templates);

STDAPI DllRegisterServer()
{
	return AMovieDllRegisterServer2(TRUE);
}

STDAPI DllUnregisterServer()
{
	return AMovieDllRegisterServer2(FALSE);
}

#include "..\..\FilterApp.h"

CFilterApp theApp;

#endif

//
// CBufferFilter
//

CBufferFilter::CBufferFilter(LPUNKNOWN lpunk, HRESULT* phr) 
	: CTransformFilter(NAME("CBufferFilter"), lpunk, __uuidof(this))
	, m_nSamplesToBuffer(2)
{
	HRESULT hr = S_OK;

	do
	{
		if(!(m_pInput = new CTransformInputPin(NAME("Transform input pin"), this, &hr, L"In"))) hr = E_OUTOFMEMORY;
		if(FAILED(hr)) break;

		if(!(m_pOutput = new CBufferFilterOutputPin(this, &hr))) hr = E_OUTOFMEMORY;
		if(FAILED(hr)) {delete m_pInput, m_pInput = NULL; break;}
	}
	while(false);

	if(phr) *phr = hr;
}

CBufferFilter::~CBufferFilter()
{
}

STDMETHODIMP CBufferFilter::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
	return
		QI(IBufferFilter)
		__super::NonDelegatingQueryInterface(riid, ppv);
}

// IBufferFilter

STDMETHODIMP CBufferFilter::SetBuffers(int nBuffers)
{
	if(!m_pOutput)
		return E_FAIL;

	if(m_pOutput->IsConnected()) // TODO: allow "on-the-fly" changes
		return VFW_E_ALREADY_CONNECTED;

	m_nSamplesToBuffer = nBuffers;

	return S_OK;
}

STDMETHODIMP_(int) CBufferFilter::GetBuffers()
{
	return(m_nSamplesToBuffer);
}

STDMETHODIMP_(int) CBufferFilter::GetFreeBuffers()
{
	CBufferFilterOutputPin* pPin = (CBufferFilterOutputPin*)m_pOutput;
	return(pPin && pPin->m_pOutputQueue ? (m_nSamplesToBuffer - pPin->m_pOutputQueue->GetQueueCount()) : 0);
}

STDMETHODIMP CBufferFilter::SetPriority(DWORD dwPriority)
{
	CBufferFilterOutputPin* pPin = (CBufferFilterOutputPin*)m_pOutput;
	return(pPin && pPin->m_pOutputQueue ? (pPin->m_pOutputQueue->SetPriority(dwPriority) ? S_OK : E_FAIL) : E_UNEXPECTED);
}

//

HRESULT CBufferFilter::Receive(IMediaSample* pSample)
{
	/*  Check for other streams and pass them on */
	AM_SAMPLE2_PROPERTIES* const pProps = m_pInput->SampleProps();
	if(pProps->dwStreamId != AM_STREAM_MEDIA)
		return m_pOutput->Deliver(pSample);

	HRESULT hr;
	ASSERT(pSample);
	IMediaSample* pOutSample;

	ASSERT(m_pOutput != NULL);

	// Set up the output sample
	hr = InitializeOutputSample(pSample, &pOutSample);

	if(FAILED(hr))
		return hr;

	// Start timing the transform (if PERF is defined)
	MSR_START(m_idTransform);

	// have the derived class transform the data

	hr = Transform(pSample, pOutSample);

	// Stop the clock and log it (if PERF is defined)
	MSR_STOP(m_idTransform);

	if(FAILED(hr)) {
		DbgLog((LOG_TRACE,1,TEXT("Error from transform")));
	}
	else {
		// the Transform() function can return S_FALSE to indicate that the
		// sample should not be delivered; we only deliver the sample if it's
		// really S_OK (same as NOERROR, of course.)
		if(hr == NOERROR) {
			hr = m_pOutput->Deliver(pOutSample);
			m_bSampleSkipped = FALSE;   // last thing no longer dropped
		}
		else {
			// S_FALSE returned from Transform is a PRIVATE agreement
			// We should return NOERROR from Receive() in this cause because returning S_FALSE
			// from Receive() means that this is the end of the stream and no more data should
			// be sent.
			if(S_FALSE == hr) {

				//  Release the sample before calling notify to avoid
				//  deadlocks if the sample holds a lock on the system
				//  such as DirectDraw buffers do
				pOutSample->Release();
				m_bSampleSkipped = TRUE;
				if(!m_bQualityChanged) {
					NotifyEvent(EC_QUALITY_CHANGE,0,0);
					m_bQualityChanged = TRUE;
				}
				return NOERROR;
			}
		}
	}

	// release the output buffer. If the connected pin still needs it,
	// it will have addrefed it itself.
	pOutSample->Release();

	return hr;
}

HRESULT CBufferFilter::Transform(IMediaSample* pIn, IMediaSample* pOut)
{
	BYTE* pDataIn = NULL;
	BYTE* pDataOut = NULL;

	pIn->GetPointer(&pDataIn);
	pOut->GetPointer(&pDataOut);

	long len = pIn->GetActualDataLength();
	long size = pOut->GetSize();

	if(!pDataIn || !pDataOut || len > size || len <= 0) return S_FALSE;

	memcpy(pDataOut, pDataIn, min(len, size));
	
	pOut->SetActualDataLength(min(len, size));

	return S_OK;
}

HRESULT CBufferFilter::CheckInputType(const CMediaType* mtIn)
{
	return S_OK;
}

HRESULT CBufferFilter::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
{
	return mtIn->MatchesPartial(mtOut) ? S_OK : VFW_E_TYPE_NOT_ACCEPTED;
}

HRESULT CBufferFilter::DecideBufferSize(IMemAllocator* pAllocator, ALLOCATOR_PROPERTIES* pProperties)
{
	if(m_pInput->IsConnected() == FALSE) return E_UNEXPECTED;

	CComPtr<IMemAllocator> pAllocatorIn;
	m_pInput->GetAllocator(&pAllocatorIn);
	if(!pAllocatorIn) return E_UNEXPECTED;

	pAllocatorIn->GetProperties(pProperties);

	pProperties->cBuffers = max(m_nSamplesToBuffer, pProperties->cBuffers);

	HRESULT hr;
	ALLOCATOR_PROPERTIES Actual;
    if(FAILED(hr = pAllocator->SetProperties(pProperties, &Actual))) 
		return hr;

    return(pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer
		? E_FAIL
		: NOERROR);
}

HRESULT CBufferFilter::GetMediaType(int iPosition, CMediaType* pMediaType)
{
    if(m_pInput->IsConnected() == FALSE) return E_UNEXPECTED;

	// TODO: offer all input types from upstream and allow reconnection at least in stopped state
	if(iPosition < 0) return E_INVALIDARG;
    if(iPosition > 0) return VFW_S_NO_MORE_ITEMS;

	CopyMediaType(pMediaType, &m_pInput->CurrentMediaType());

	return S_OK;
}

HRESULT CBufferFilter::StopStreaming()
{
	CBufferFilterOutputPin* pPin = (CBufferFilterOutputPin*)m_pOutput;
	if(m_pInput && pPin && pPin->m_pOutputQueue)
	{
		while(!m_pInput->IsFlushing() && pPin->m_pOutputQueue->GetQueueCount() > 0) 
			Sleep(50);
	}

	return __super::StopStreaming();
}

//
// CBufferFilterOutputPin
//

CBufferFilterOutputPin::CBufferFilterOutputPin(CTransformFilter* pFilter, HRESULT* phr)
	: CTransformOutputPin(NAME("CBufferFilterOutputPin"), pFilter, phr, L"Out")
{
}

HRESULT CBufferFilterOutputPin::Active()
{
	CAutoLock lock_it(m_pLock);

	if(m_Connected && !m_pOutputQueue)
	{
		HRESULT hr = NOERROR;

		m_pOutputQueue.Attach(new CBufferFilterOutputQueue(m_Connected, &hr));
		if(!m_pOutputQueue) hr = E_OUTOFMEMORY;

		if(FAILED(hr))
		{
			m_pOutputQueue.Free();
			return hr;
		}
	}

	return __super::Active();
}

HRESULT CBufferFilterOutputPin::Inactive()
{
	CAutoLock lock_it(m_pLock);
	m_pOutputQueue.Free();
	return __super::Inactive();
}

HRESULT CBufferFilterOutputPin::Deliver(IMediaSample* pMediaSample)
{
	if(!m_pOutputQueue) return NOERROR;
	pMediaSample->AddRef();
	return m_pOutputQueue->Receive(pMediaSample);
}

#define CallQueue(call) \
		if(!m_pOutputQueue) return NOERROR; \
		m_pOutputQueue->##call; \
		return NOERROR; \

HRESULT CBufferFilterOutputPin::DeliverEndOfStream()
{
	CallQueue(EOS());
}

HRESULT CBufferFilterOutputPin::DeliverBeginFlush()
{
	CallQueue(BeginFlush());
}

HRESULT CBufferFilterOutputPin::DeliverEndFlush()
{
	CallQueue(EndFlush());
}

HRESULT CBufferFilterOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
	CallQueue(NewSegment(tStart, tStop, dRate));
}
