/*  This file is part of the KDE project.

Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.

This library 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "qmeminputpin.h"
#include "qbasefilter.h"
#include "compointer.h"

#include <QtCore/QDebug>

QT_BEGIN_NAMESPACE

namespace Phonon
{
    namespace DS9
    {

        QMemInputPin::QMemInputPin(QBaseFilter *parent, const QVector<AM_MEDIA_TYPE> &mt, bool transform, QPin *output) :
            QPin(parent, PINDIR_INPUT, mt), m_shouldDuplicateSamples(true), m_transform(transform), m_output(output)
        {
        }

        QMemInputPin::~QMemInputPin()
        {
        }

        STDMETHODIMP QMemInputPin::QueryInterface(REFIID iid, void **out)
        {
            if (!out) {
                return E_POINTER;
            }

            if (iid == IID_IMemInputPin) {
                *out = static_cast<IMemInputPin*>(this);
                AddRef();
                return S_OK;
            } else {
                return QPin::QueryInterface(iid, out);
            }
        }

        STDMETHODIMP_(ULONG) QMemInputPin::AddRef()
        {
            return QPin::AddRef();
        }

        STDMETHODIMP_(ULONG) QMemInputPin::Release()
        {
            return QPin::Release();
        }

        STDMETHODIMP QMemInputPin::EndOfStream()
        {
            //this allows to serialize with Receive calls
            QMutexLocker locker(&m_mutexReceive);
            IPin *conn = m_output ? m_output->connected() : 0;
            if (conn) {
                conn->EndOfStream();
            }
            return S_OK;
        }

        STDMETHODIMP QMemInputPin::BeginFlush()
        {
            //pass downstream
            IPin *conn = m_output ? m_output->connected() : 0;
            if (conn) {
                conn->BeginFlush();
            }
            QMutexLocker locker(&m_mutex);
            m_flushing = true;
            return S_OK;
        }

        STDMETHODIMP QMemInputPin::EndFlush()
        {
            //pass downstream
            IPin *conn = m_output ? m_output->connected() : 0;
            if (conn) {
                conn->EndFlush();
            }
            QMutexLocker locker(&m_mutex);
            m_flushing = false;
            return S_OK;
        }

        STDMETHODIMP QMemInputPin::NewSegment(REFERENCE_TIME start, REFERENCE_TIME stop, double rate)
        {
            if (m_output)
                m_output->NewSegment(start, stop, rate);
            return S_OK;
        }

        //reimplementation to set the type for the output pin
        //no need to make a deep copy here
        STDMETHODIMP QMemInputPin::ReceiveConnection(IPin *pin ,const AM_MEDIA_TYPE *mt)
        {
            HRESULT hr = QPin::ReceiveConnection(pin, mt);
            if (hr == S_OK &&
                mt->majortype != MEDIATYPE_NULL &&
                mt->subtype != MEDIASUBTYPE_NULL &&
                mt->formattype != GUID_NULL && m_output) {
                    //we tell the output pin that it should connect with this type
                    hr = m_output->setAcceptedMediaType(connectedType());
            }
            return hr;
        }

        STDMETHODIMP QMemInputPin::GetAllocator(IMemAllocator **alloc)
        {
            if (!alloc) {
                return E_POINTER;
            }

            *alloc = memoryAllocator(true);
            if (*alloc) {
                return S_OK;
            }

            return VFW_E_NO_ALLOCATOR;
        }

        STDMETHODIMP QMemInputPin::NotifyAllocator(IMemAllocator *alloc, BOOL readonly)
        {
            if (!alloc) {
                return E_POINTER;
            }

            {
                QMutexLocker locker(&m_mutex);
                m_shouldDuplicateSamples = m_transform && readonly;
            }

            setMemoryAllocator(alloc);

            if (m_output) {
                ComPointer<IMemInputPin> input(m_output, IID_IMemInputPin);
                input->NotifyAllocator(alloc, m_shouldDuplicateSamples);
            }

            return S_OK;
        }

        STDMETHODIMP QMemInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *prop)
        {
            if (!prop) {
                return E_POINTER;
            }

            //we have no particular requirements
            return E_NOTIMPL;
        }

        STDMETHODIMP QMemInputPin::Receive(IMediaSample *sample)
        {
            QMutexLocker locker(&m_mutexReceive);
            if (!sample) {
                return E_POINTER;
            }

            if (filterState() == State_Stopped) {
                return VFW_E_WRONG_STATE;
            }

            if (isFlushing()) {
                return S_FALSE; //we are still flushing
            }

            if (!m_shouldDuplicateSamples) {
                //we do it just once
                HRESULT hr = m_parent->processSample(sample);
                if (!SUCCEEDED(hr)) {
                    return hr;
                }
            }

            if (m_output) {
                IMediaSample *outSample = m_shouldDuplicateSamples ?
                    duplicateSampleForOutput(sample, m_output->memoryAllocator())
                    : sample;

                if (m_shouldDuplicateSamples) {
                    m_parent->processSample(outSample);
                }

                ComPointer<IMemInputPin> input(m_output->connected(), IID_IMemInputPin);
                if (input) {
                    input->Receive(outSample);
                }

                if (m_shouldDuplicateSamples) {
                    outSample->Release();
                }
            }
            return S_OK;
        }

        STDMETHODIMP QMemInputPin::ReceiveMultiple(IMediaSample **samples,long count,long *nbDone)
        {
            //no need to lock here: there is no access to member data
            if (!samples || !nbDone) {
                return E_POINTER;
            }

            *nbDone = 0; //initialization
            while( *nbDone != count) {
                HRESULT hr = Receive(samples[*nbDone]);
                if (FAILED(hr)) {
                    return hr;
                }
                (*nbDone)++;
            }

            return S_OK;
        }

        STDMETHODIMP QMemInputPin::ReceiveCanBlock()
        {
            //we test the output to see if it can block
            if (m_output) {
                ComPointer<IMemInputPin> meminput(m_output->connected(), IID_IMemInputPin);
                if (meminput && meminput->ReceiveCanBlock() != S_FALSE) {
                    return S_OK;
                }
            }
            return S_FALSE;
        }


        ALLOCATOR_PROPERTIES QMemInputPin::getDefaultAllocatorProperties() const
        {
            //those values reduce buffering a lot (good for the volume effect)
            ALLOCATOR_PROPERTIES prop = {4096, 1, 1, 0};
            return prop;
        }


        IMediaSample *QMemInputPin::duplicateSampleForOutput(IMediaSample *sample, IMemAllocator *alloc)
        {
            LONG length = sample->GetActualDataLength();

            HRESULT hr = alloc->Commit();
            if (hr == HRESULT(VFW_E_SIZENOTSET)) {
                ALLOCATOR_PROPERTIES prop = getDefaultAllocatorProperties();
                prop.cbBuffer = qMax(prop.cbBuffer, length);
                ALLOCATOR_PROPERTIES actual;
                //we just try to set the properties...
                alloc->SetProperties(&prop, &actual);
                hr = alloc->Commit();
            }

            Q_ASSERT(SUCCEEDED(hr));

            IMediaSample *out;
            hr = alloc->GetBuffer(&out, 0, 0, AM_GBF_NOTASYNCPOINT);
            Q_ASSERT(SUCCEEDED(hr));

            //let's copy the sample
            {
                REFERENCE_TIME start, end;
                sample->GetTime(&start, &end);
                out->SetTime(&start, &end);
            }

            hr = out->SetActualDataLength(length);
            Q_ASSERT(SUCCEEDED(hr));
            hr = out->SetDiscontinuity(sample->IsDiscontinuity());
            Q_ASSERT(SUCCEEDED(hr));

            {
                LONGLONG start, end;
                hr = sample->GetMediaTime(&start, &end);
                if (hr != HRESULT(VFW_E_MEDIA_TIME_NOT_SET)) {
                    hr = out->SetMediaTime(&start, &end);
                    Q_ASSERT(SUCCEEDED(hr));
                }
            }

            AM_MEDIA_TYPE *type = 0;
            hr = sample->GetMediaType(&type);
            Q_ASSERT(SUCCEEDED(hr));
            hr = out->SetMediaType(type);
            Q_ASSERT(SUCCEEDED(hr));

            hr = out->SetPreroll(sample->IsPreroll());
            Q_ASSERT(SUCCEEDED(hr));
            hr = out->SetSyncPoint(sample->IsSyncPoint());
            Q_ASSERT(SUCCEEDED(hr));

            BYTE *dest = 0, *src = 0;
            hr = out->GetPointer(&dest);
            Q_ASSERT(SUCCEEDED(hr));
            hr = sample->GetPointer(&src);
            Q_ASSERT(SUCCEEDED(hr));

            qMemCopy(dest, src, sample->GetActualDataLength());

            return out;
        }
    }
}

QT_END_NAMESPACE
