/*****************************************************************************
 * connectioncontainer.cpp: ActiveX control for VLC
 *****************************************************************************
 * Copyright (C) 2005 VideoLAN
 *
 * Authors: Damien Fouilleul <Damien.Fouilleul@laposte.net>
 *
 * 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, USA.
 *****************************************************************************/

#include "plugin.h"
#include "connectioncontainer.h"

#include "utils.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////

class VLCEnumConnections : public IEnumConnections
{
public:
    VLCEnumConnections(vector<CONNECTDATA> &v) :
        e(VLCEnum<CONNECTDATA>(IID_IEnumConnections, v))
    { e.setRetainOperation((VLCEnum<CONNECTDATA>::retainer)&retain); };

    VLCEnumConnections(const VLCEnumConnections &vlcEnum) : e(vlcEnum.e) {};

    virtual ~VLCEnumConnections() {};

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
        { return e.QueryInterface(riid, ppv); };
    STDMETHODIMP_(ULONG) AddRef(void)
        { return e.AddRef(); };
    STDMETHODIMP_(ULONG) Release(void)
        {return e.Release(); };

    //IEnumConnectionPoints
    STDMETHODIMP Next(ULONG celt, LPCONNECTDATA rgelt, ULONG *pceltFetched)
        { return e.Next(celt, rgelt, pceltFetched); };
    STDMETHODIMP Skip(ULONG celt)
        { return e.Skip(celt);};
    STDMETHODIMP Reset(void)
        { return e.Reset();};
    STDMETHODIMP Clone(LPENUMCONNECTIONS *ppenum)
        { if( NULL == ppenum ) return E_POINTER;
          *ppenum = dynamic_cast<LPENUMCONNECTIONS>(new VLCEnumConnections(*this));
          return (NULL != *ppenum) ? S_OK : E_OUTOFMEMORY;
        };

private:

    static void retain(CONNECTDATA cd)
    {
        cd.pUnk->AddRef();
    };

    VLCEnum<CONNECTDATA> e;
};

////////////////////////////////////////////////////////////////////////////////////////////////

STDMETHODIMP VLCConnectionPoint::GetConnectionInterface(IID *iid)
{
    if( NULL == iid )
        return E_POINTER;

    *iid = _iid;
    return S_OK;
};

STDMETHODIMP VLCConnectionPoint::GetConnectionPointContainer(LPCONNECTIONPOINTCONTAINER *ppCPC)
{
    if( NULL == ppCPC )
        return E_POINTER;

    _p_cpc->AddRef();
    *ppCPC = _p_cpc;
    return S_OK;
};

STDMETHODIMP VLCConnectionPoint::Advise(IUnknown *pUnk, DWORD *pdwCookie)
{
    if( (NULL == pUnk) || (NULL == pdwCookie) )
        return E_POINTER;

    CONNECTDATA cd;

    pUnk->AddRef();
    cd.pUnk = pUnk;
    *pdwCookie = cd.dwCookie = _connections.size();

    _connections.push_back(cd);

    return S_OK;
};

STDMETHODIMP VLCConnectionPoint::Unadvise(DWORD pdwCookie)
{
    if( pdwCookie < _connections.size() )
    {
        CONNECTDATA cd = _connections[pdwCookie];
        if( NULL != cd.pUnk )
        {
            cd.pUnk->Release();
            cd.pUnk = NULL;
            return S_OK;
        }
    }
    return CONNECT_E_NOCONNECTION;
};

STDMETHODIMP VLCConnectionPoint::EnumConnections(IEnumConnections **ppEnum)
{
    if( NULL == ppEnum )
        return E_POINTER;

    *ppEnum = dynamic_cast<LPENUMCONNECTIONS>(new VLCEnumConnections(_connections));

    return (NULL != *ppEnum ) ? S_OK : E_OUTOFMEMORY;
};

void VLCConnectionPoint::fireEvent(DISPID dispId, LCID lcid, DISPPARAMS* pDispParams)
{
    vector<CONNECTDATA>::iterator end = _connections.end();
    vector<CONNECTDATA>::iterator iter = _connections.begin();

    while( iter != end )
    {
        CONNECTDATA cd = *iter;
        if( NULL != cd.pUnk )
        {
            IDispatch *pDisp;
            if( SUCCEEDED(cd.pUnk->QueryInterface(IID_IDispatch, (LPVOID *)&pDisp)) )
            {
                unsigned int puArgErr;
                VARIANT vRes;

                if( SUCCEEDED(pDisp->Invoke(dispId, IID_NULL, lcid, DISPATCH_METHOD, pDispParams, &vRes, NULL, &puArgErr)) )
                {
                    VariantClear(&vRes);
                }
                pDisp->Release();
            }
        }
        ++iter;
    }
};

void VLCConnectionPoint::firePropChangedEvent(DISPID dispId)
{
    vector<CONNECTDATA>::iterator end = _connections.end();
    vector<CONNECTDATA>::iterator iter = _connections.begin();

    while( iter != end )
    {
        CONNECTDATA cd = *iter;
        if( NULL != cd.pUnk )
        {
            IPropertyNotifySink *pPropSink;
            if( SUCCEEDED(cd.pUnk->QueryInterface(IID_IPropertyNotifySink, (LPVOID *)&pPropSink)) )
            {
                pPropSink->OnChanged(dispId);
                pPropSink->Release();
            }
        }
        ++iter;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////

class VLCEnumConnectionPoints : public IEnumConnectionPoints
{
public:
    VLCEnumConnectionPoints(vector<LPCONNECTIONPOINT> &v) :
        e(VLCEnum<LPCONNECTIONPOINT>(IID_IEnumConnectionPoints, v))
    { e.setRetainOperation((VLCEnum<LPCONNECTIONPOINT>::retainer)&retain); };

    VLCEnumConnectionPoints(const VLCEnumConnectionPoints &vlcEnum) : e(vlcEnum.e) {};

    virtual ~VLCEnumConnectionPoints() {};

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
        { return e.QueryInterface(riid, ppv); };
    STDMETHODIMP_(ULONG) AddRef(void)
        { return e.AddRef(); };
    STDMETHODIMP_(ULONG) Release(void)
        {return e.Release(); };

    //IEnumConnectionPoints
    STDMETHODIMP Next(ULONG celt, LPCONNECTIONPOINT *rgelt, ULONG *pceltFetched)
        { return e.Next(celt, rgelt, pceltFetched); };
    STDMETHODIMP Skip(ULONG celt)
        { return e.Skip(celt);};
    STDMETHODIMP Reset(void)
        { return e.Reset();};
    STDMETHODIMP Clone(LPENUMCONNECTIONPOINTS *ppenum)
        { if( NULL == ppenum ) return E_POINTER;
          *ppenum = dynamic_cast<LPENUMCONNECTIONPOINTS>(new VLCEnumConnectionPoints(*this));
          return (NULL != *ppenum) ? S_OK : E_OUTOFMEMORY;
        };

private:

    static void retain(LPCONNECTIONPOINT cp)
    {
        cp->AddRef();
    };

    VLCEnum<LPCONNECTIONPOINT> e;
};

////////////////////////////////////////////////////////////////////////////////////////////////

VLCConnectionPointContainer::VLCConnectionPointContainer(VLCPlugin *p_instance) :
    _p_instance(p_instance)
{
    _p_events = new VLCConnectionPoint(dynamic_cast<LPCONNECTIONPOINTCONTAINER>(this),
            _p_instance->getDispEventID());

    _v_cps.push_back(dynamic_cast<LPCONNECTIONPOINT>(_p_events));

    _p_props = new VLCConnectionPoint(dynamic_cast<LPCONNECTIONPOINTCONTAINER>(this),
            IID_IPropertyNotifySink);

    _v_cps.push_back(dynamic_cast<LPCONNECTIONPOINT>(_p_props));
};

VLCConnectionPointContainer::~VLCConnectionPointContainer()
{
    _v_cps.clear();
    delete _p_props;
    delete _p_events;
};

STDMETHODIMP VLCConnectionPointContainer::EnumConnectionPoints(LPENUMCONNECTIONPOINTS *ppEnum)
{
    if( NULL == ppEnum )
        return E_POINTER;

    *ppEnum = dynamic_cast<LPENUMCONNECTIONPOINTS>(new VLCEnumConnectionPoints(_v_cps));

    return (NULL != *ppEnum ) ? S_OK : E_OUTOFMEMORY;
};

STDMETHODIMP VLCConnectionPointContainer::FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP)
{
    if( NULL == ppCP )
        return E_POINTER;

    *ppCP = NULL;

    if( IID_IPropertyNotifySink == riid )
    {
        _p_props->AddRef();
        *ppCP = dynamic_cast<LPCONNECTIONPOINT>(_p_props);
    }
    else if( _p_instance->getDispEventID() == riid )
    {
        _p_events->AddRef();
        *ppCP = dynamic_cast<LPCONNECTIONPOINT>(_p_events);
    }
    else
        return CONNECT_E_NOCONNECTION;

    return NOERROR;
};

void VLCConnectionPointContainer::fireEvent(DISPID dispId, LCID lcid, DISPPARAMS* pDispParams)
{
    _p_events->fireEvent(dispId,lcid, pDispParams);
};

void VLCConnectionPointContainer::firePropChangedEvent(DISPID dispId)
{
    _p_props->firePropChangedEvent(dispId);
};

