/*  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 "qbasefilter.h"
#include "qpin.h"
#include "compointer.h"

#include <QtCore/QMutex>

QT_BEGIN_NAMESPACE

namespace Phonon
{
    namespace DS9
    {

        static const AM_MEDIA_TYPE defaultMediaType = { MEDIATYPE_NULL, MEDIASUBTYPE_NULL, TRUE, FALSE, 1, GUID_NULL, 0, 0, 0};

        class QEnumMediaTypes : public IEnumMediaTypes
        {
        public:
            QEnumMediaTypes(QPin *pin) :  m_refCount(1), m_pin(pin), m_index(0)
            {
                m_pin->AddRef();
            }

            ~QEnumMediaTypes()
            {
                m_pin->Release();
            }

            STDMETHODIMP QueryInterface(const IID &iid,void **out)
            {
                if (!out) {
                    return E_POINTER;
                }

                HRESULT hr = S_OK;
                if (iid == IID_IEnumMediaTypes) {
                    *out = static_cast<IEnumMediaTypes*>(this);
                } else if (iid == IID_IUnknown) {
                    *out = static_cast<IUnknown*>(this);
                } else {
                    *out = 0;
                    hr = E_NOINTERFACE;
                }

                if (hr == S_OK) {
                    AddRef();
                }
                return hr;
            }

            STDMETHODIMP_(ULONG) AddRef()
            {
                return InterlockedIncrement(&m_refCount);
            }

            STDMETHODIMP_(ULONG) Release()
            {
                ULONG refCount = InterlockedDecrement(&m_refCount);
                if (refCount == 0) {
                    delete this;
                }

                return refCount;
            }

            STDMETHODIMP Next(ULONG count, AM_MEDIA_TYPE **out, ULONG *fetched)
            {
                QMutexLocker locker(&m_mutex);
                if (!out) {
                    return E_POINTER;
                }

                if (!fetched && count > 1) {
                    return E_INVALIDARG;
                }

                uint nbFetched = 0;
                while (nbFetched < count && m_index < m_pin->mediaTypes().count()) {
                    //the caller will deallocate the memory
                    *out = static_cast<AM_MEDIA_TYPE *>(::CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)));
                    const AM_MEDIA_TYPE original = m_pin->mediaTypes().at(m_index);
                    **out = QPin::copyMediaType(original);
                    nbFetched++;
                    m_index++;
                    out++;
                }

                if (fetched) {
                    *fetched = nbFetched;
                }

                return nbFetched == count ? S_OK : S_FALSE;
            }

            STDMETHODIMP Skip(ULONG count)
            {
                QMutexLocker locker(&m_mutex);
                m_index = qMin(m_index + int(count), m_pin->mediaTypes().count());
                return  (m_index == m_pin->mediaTypes().count()) ? S_FALSE : S_OK;
            }

            STDMETHODIMP Reset()
            {
                QMutexLocker locker(&m_mutex);
                m_index = 0;
                return S_OK;
            }

            STDMETHODIMP Clone(IEnumMediaTypes **out)
            {
                QMutexLocker locker(&m_mutex);
                if (!out) {
                    return E_POINTER;
                }

                *out = new QEnumMediaTypes(m_pin);
                (*out)->Skip(m_index);
                return S_OK;
            }


        private:
            LONG m_refCount;
            QPin *m_pin;
            int m_index;
            QMutex m_mutex;
        };


        QPin::QPin(QBaseFilter *parent, PIN_DIRECTION dir, const QVector<AM_MEDIA_TYPE> &mt) :
            m_parent(parent), m_flushing(false), m_refCount(1),  m_connected(0),
            m_direction(dir), m_mediaTypes(mt), m_connectedType(defaultMediaType),
            m_memAlloc(0)
        {
            Q_ASSERT(m_parent);
            m_parent->addPin(this);
        }

        QPin::~QPin()
        {
            m_parent->removePin(this);
            setMemoryAllocator(0);
            freeMediaType(m_connectedType);
        }

        //reimplementation from IUnknown
        STDMETHODIMP QPin::QueryInterface(REFIID iid, void**out)
        {
            if (!out) {
                return E_POINTER;
            }

            HRESULT hr = S_OK;

            if (iid == IID_IPin) {
                *out = static_cast<IPin*>(this);
            } else if (iid == IID_IUnknown) {
                *out = static_cast<IUnknown*>(this);
            } else if (m_direction == PINDIR_OUTPUT && (iid == IID_IMediaSeeking || iid == IID_IMediaPosition)) {
                return m_parent->QueryInterface(iid, out);
            } else {
                *out = 0;
                hr = E_NOINTERFACE;
            }

            if (hr == S_OK) {
                AddRef();
            }
            return hr;
        }

        STDMETHODIMP_(ULONG) QPin::AddRef()
        {
            return InterlockedIncrement(&m_refCount);
        }

        STDMETHODIMP_(ULONG) QPin::Release()
        {
            ULONG refCount = InterlockedDecrement(&m_refCount);
            if (refCount == 0) {
                delete this;
            }

            return refCount;
        }

        //this is called on the input pins
        STDMETHODIMP QPin::ReceiveConnection(IPin *pin, const AM_MEDIA_TYPE *type)
        {
            if (!pin ||!type) {
                return E_POINTER;
            }

            if (connected()) {
                return VFW_E_ALREADY_CONNECTED;
            }

            if (filterState() != State_Stopped) {
                return VFW_E_NOT_STOPPED;
            }

            if (QueryAccept(type) != S_OK) {
                return VFW_E_TYPE_NOT_ACCEPTED;
            }

            setConnected(pin);
            setConnectedType(*type);

            return S_OK;
        }

        //this is called on the output pins
        STDMETHODIMP QPin::Connect(IPin *pin, const AM_MEDIA_TYPE *type)
        {
            if (!pin) {
                return E_POINTER;
            }

            if (connected()) {
                return VFW_E_ALREADY_CONNECTED;
            }

            if (filterState() != State_Stopped) {
                return VFW_E_NOT_STOPPED;
            }

            HRESULT hr = S_OK;

            setConnected(pin);
            if (!type) {

                //let(s first try the output pin's mediaTypes
                if (checkOutputMediaTypesConnection(pin) != S_OK &&
                    checkOwnMediaTypesConnection(pin) != S_OK) {
                        hr = VFW_E_NO_ACCEPTABLE_TYPES;
                }
            } else if (QueryAccept(type) == S_OK) {
                setConnectedType(*type);
                hr = pin->ReceiveConnection(this, type);
            } else {
                hr = VFW_E_TYPE_NOT_ACCEPTED;
            }

            if (FAILED(hr)) {
                setConnected(0);
                setConnectedType(defaultMediaType);
            } else {
                ComPointer<IMemInputPin> input(pin, IID_IMemInputPin);
                if (input) {
                    ComPointer<IMemAllocator> alloc;
                    input->GetAllocator(alloc.pparam());
                    if (alloc) {
                        //be default we take the allocator from the input pin
                        //we have no reason to force using our own
                        setMemoryAllocator(alloc);
                    }
                }
                if (memoryAllocator() == 0) {
                    ALLOCATOR_PROPERTIES prop;
                    if (input && input->GetAllocatorRequirements(&prop) == S_OK) {
                        createDefaultMemoryAllocator(&prop);
                    } else {
                        createDefaultMemoryAllocator();
                    }
                }

                Q_ASSERT(memoryAllocator() != 0);
                if (input) {
                    input->NotifyAllocator(memoryAllocator(), TRUE); //TRUE is arbitrarily chosen here
                }

            }

            return hr;
        }

        STDMETHODIMP QPin::Disconnect()
        {
            if (!connected()) {
                return S_FALSE;
            }

            if (filterState() != State_Stopped) {
                return VFW_E_NOT_STOPPED;
            }

            setConnected(0);
            setConnectedType(defaultMediaType);
            setMemoryAllocator(0);
            return S_OK;
        }

        STDMETHODIMP QPin::ConnectedTo(IPin **other)
        {
            if (!other) {
                return E_POINTER;
            }

            *other = connected(true);
            if (!(*other)) {
                return VFW_E_NOT_CONNECTED;
            }

            return S_OK;
        }

        STDMETHODIMP QPin::ConnectionMediaType(AM_MEDIA_TYPE *type)
        {
            QMutexLocker locker(&m_mutex);
            if (!type) {
                return E_POINTER;
            }

            *type = copyMediaType(m_connectedType);
            if (!m_connected) {
                return VFW_E_NOT_CONNECTED;
            } else {
                return S_OK;
            }
        }

        STDMETHODIMP QPin::QueryPinInfo(PIN_INFO *info)
        {
            if (!info) {
                return E_POINTER;
            }

            info->dir = m_direction;
            info->pFilter = m_parent;
            m_parent->AddRef();
            info->achName[0] = 0;
            return S_OK;
        }

        STDMETHODIMP QPin::QueryDirection(PIN_DIRECTION *dir)
        {
            if (!dir) {
                return E_POINTER;
            }

            *dir = m_direction;
            return S_OK;
        }

        STDMETHODIMP QPin::QueryId(LPWSTR *id)
        {
            if (!id) {
                return E_POINTER;
            }

            *id = static_cast<LPWSTR>(::CoTaskMemAlloc(2));
            *id[0] = 0;
            return S_OK;
        }

        STDMETHODIMP QPin::QueryAccept(const AM_MEDIA_TYPE *type)
        {
            QMutexLocker locker(&m_mutex);
            if (!type) {
                return E_POINTER;
            }

            for (int i = 0; i < m_mediaTypes.count(); ++i) {
                const AM_MEDIA_TYPE &current = m_mediaTypes.at(i);
                if ( (type->majortype == current.majortype) &&
                    (current.subtype == MEDIASUBTYPE_NULL || type->subtype == current.subtype) &&
                    (type->majortype == MEDIATYPE_Stream || type->formattype != GUID_NULL || current.formattype != GUID_NULL) &&
                    (current.formattype == GUID_NULL || type->formattype == current.formattype)
                    ) {
                        return S_OK;
                }
            }
            return S_FALSE;
        }


        STDMETHODIMP QPin::EnumMediaTypes(IEnumMediaTypes **emt)
        {
            if (!emt) {
                return E_POINTER;
            }

            *emt = new QEnumMediaTypes(this);
            return S_OK;
        }


        STDMETHODIMP QPin::EndOfStream()
        {
            return E_UNEXPECTED;
        }

        STDMETHODIMP QPin::BeginFlush()
        {
            return E_UNEXPECTED;
        }

        STDMETHODIMP QPin::EndFlush()
        {
            return E_UNEXPECTED;
        }

        STDMETHODIMP QPin::NewSegment(REFERENCE_TIME start, REFERENCE_TIME stop, double rate)
        {
            QMutexLocker locker(&m_mutex);
            if (m_direction == PINDIR_OUTPUT && m_connected) {
                //we deliver this downstream
                m_connected->NewSegment(start, stop, rate);
            }
            return S_OK;
        }

        STDMETHODIMP QPin::QueryInternalConnections(IPin **, ULONG*)
        {
            //this is not implemented on purpose (the input pins are connected to all the output pins)
            return E_NOTIMPL;
        }


        HRESULT QPin::checkOutputMediaTypesConnection(IPin *pin)
        {
            ComPointer<IEnumMediaTypes> emt;
            HRESULT hr = pin->EnumMediaTypes(emt.pparam());
            if (hr != S_OK) {
                return hr;
            }

            AM_MEDIA_TYPE *type = 0;
            while (emt->Next(1, &type, 0) == S_OK) {
                if (QueryAccept(type) == S_OK) {
                    setConnectedType(*type);
                    if (pin->ReceiveConnection(this, type) == S_OK) {
                        freeMediaType(type);
                        return S_OK;
                    } else {
                        setConnectedType(defaultMediaType);
                        freeMediaType(type);
                    }
                }
            }

            //we didn't find a suitable type
            return S_FALSE;
        }

        HRESULT QPin::checkOwnMediaTypesConnection(IPin *pin)
        {   
            for(int i = 0; i < m_mediaTypes.count(); ++i) {
                const AM_MEDIA_TYPE &current = m_mediaTypes.at(i);
                setConnectedType(current);
                HRESULT hr = pin->ReceiveConnection(this, &current);
                if (hr == S_OK) {
                    return S_OK;
                }
            }

            //we didn't find a suitable type
            return S_FALSE;
        }

        void QPin::freeMediaType(const AM_MEDIA_TYPE &type)
        {
            if (type.cbFormat) {
                ::CoTaskMemFree(type.pbFormat);
            }
            if (type.pUnk) {
                type.pUnk->Release();
            }
        }

        void QPin::freeMediaType(AM_MEDIA_TYPE *type)
        {
            freeMediaType(*type);
            ::CoTaskMemFree(type);
        }

        //addition

        PIN_DIRECTION QPin::direction() const
        {
            return m_direction;
        }

        void QPin::setConnectedType(const AM_MEDIA_TYPE &type)
        {
            QMutexLocker locker(&m_mutex);

            //1st we free memory
            freeMediaType(m_connectedType);

            m_connectedType = copyMediaType(type);
        }

        const AM_MEDIA_TYPE &QPin::connectedType() const 
        {
            QMutexLocker locker(&m_mutex);
            return m_connectedType;
        }

        void QPin::setConnected(IPin *pin)
        {
            QMutexLocker locker(&m_mutex);
            if (pin) {
                pin->AddRef();
            }
            if (m_connected) {
                m_connected->Release();
            }
            m_connected = pin;
        }

        IPin *QPin::connected(bool addref) const
        {
            QMutexLocker locker(&m_mutex);
            if (addref && m_connected) {
                m_connected->AddRef();
            }
            return m_connected;
        }

        bool QPin::isFlushing() const
        {
            QMutexLocker locker(&m_mutex);
            return m_flushing;
        }

        FILTER_STATE QPin::filterState() const
        {
            FILTER_STATE fstate = State_Stopped;
            m_parent->GetState(0, &fstate);
            return fstate;
        }

        QVector<AM_MEDIA_TYPE> QPin::mediaTypes() const
        {
            QMutexLocker locker(&m_mutex);
            return m_mediaTypes;
        }

        HRESULT QPin::setAcceptedMediaType(const AM_MEDIA_TYPE &mt)
        {
            const QVector<AM_MEDIA_TYPE> oldMediaTypes = m_mediaTypes;
            m_mediaTypes = QVector<AM_MEDIA_TYPE>() << mt;

            HRESULT hr = S_OK;

            IPin *conn = connected();
            if (conn) {
                //try to reconnect to redefine the media type
                conn->Disconnect();
                Disconnect();
                hr = Connect(conn, 0);
                if (FAILED(hr)) {
                    m_mediaTypes = oldMediaTypes;
                    Connect(conn, 0); //just redo the connection with the old media types
                }
            }
            return hr;
        }

        void QPin::createDefaultMemoryAllocator(ALLOCATOR_PROPERTIES *prop)
        {
            ComPointer<IMemAllocator> alloc(CLSID_MemoryAllocator, IID_IMemAllocator);
            if (prop) {
                alloc->SetProperties(prop, 0);
            }
            setMemoryAllocator(alloc);
        }

        void QPin::setMemoryAllocator(IMemAllocator *alloc)
        {
            QMutexLocker locker(&m_mutex);
            if (alloc) {
                alloc->AddRef();
            }
            if (m_memAlloc) {
                m_memAlloc->Release();
            }
            m_memAlloc = alloc;
        }

        IMemAllocator *QPin::memoryAllocator(bool addref) const
        {
            QMutexLocker locker(&m_mutex);
            if (addref && m_memAlloc) {
                m_memAlloc->AddRef();
            }
            return m_memAlloc;
        }

        AM_MEDIA_TYPE QPin::copyMediaType(const AM_MEDIA_TYPE &type)
        {
            AM_MEDIA_TYPE ret = type;

            //make a deep copy here
            if (ret.cbFormat == 0 || ret.pbFormat == 0) {
                ret.cbFormat = 0;
                ret.pbFormat = 0;
                ret.formattype = GUID_NULL;
            } else {
                ret.pbFormat = reinterpret_cast<BYTE*>(::CoTaskMemAlloc(type.cbFormat));
                qMemCopy(ret.pbFormat, type.pbFormat, type.cbFormat);
            }

            if (type.pUnk) {
                type.pUnk->AddRef();
            }
            return ret;
        }


    }
}

QT_END_NAMESPACE
