// builds an ocx out of the embedding in shembed.c

#include "priv.h"
#include "sccls.h"
#include "olectl.h"
#include "stdenum.h"
#include "shocx.h"
#include "resource.h"

LCID g_lcidLocale = MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT);

#define SUPERCLASS CShellEmbedding

CShellOcx::CShellOcx(IUnknown* punkOuter, LPCOBJECTINFO poi, const OLEVERB* pverbs, const OLEVERB* pdesignverbs) :
                        CShellEmbedding(punkOuter, poi, pverbs),
                        _pDesignVerbs(pdesignverbs),
                        CImpIDispatch(LIBID_SHDocVw, 1, 1, *(poi->piid))
{
    // CShellEmbedding class handles the DllAddRef / DllRelease

    m_cpEvents.SetOwner(_GetInner(), poi->piidEvents);
    m_cpPropNotify.SetOwner(_GetInner(), &IID_IPropertyNotifySink);

    _nDesignMode = MODE_UNKNOWN;
}

CShellOcx::~CShellOcx()
{
    // Should have been released when cllient site was set to NULL.... Don't release
    // it here as this will cause some applications like VC5 to fault...
    ASSERT(_pDispAmbient==NULL);

    if (_pClassTypeInfo)
        _pClassTypeInfo->Release();
}

//
// We have a different set of verbs in design mode
//
HRESULT CShellOcx::EnumVerbs(IEnumOLEVERB **ppEnumOleVerb)
{
    TraceMsg(TF_SHDCONTROL, "sho: EnumVerbs");

    if (_IsDesignMode())
    {
        *ppEnumOleVerb = new CSVVerb(_pDesignVerbs);
        if (*ppEnumOleVerb)
            return S_OK;
    }

    return SUPERCLASS::EnumVerbs(ppEnumOleVerb);
}


//
// For the interfaces we support here
//
HRESULT CShellOcx::v_InternalQueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CShellOcx, IDispatch),
        QITABENT(CShellOcx, IOleControl),
        QITABENT(CShellOcx, IConnectionPointContainer),
        QITABENT(CShellOcx, IPersistStreamInit),
        QITABENTMULTI(CShellOcx, IPersistStream, IPersistStreamInit),
        QITABENT(CShellOcx, IPersistPropertyBag),
        QITABENT(CShellOcx, IProvideClassInfo2),
        QITABENTMULTI(CShellOcx, IProvideClassInfo, IProvideClassInfo2),
        { 0 },
    };

    HRESULT hr = QISearch(this, qit, riid, ppvObj);
    if (FAILED(hr))
        hr = SUPERCLASS::v_InternalQueryInterface(riid, ppvObj);
    return hr;
}

//
// On a SetClientSite, we need to discard everything created from _pcli
// because shembed frees _pcli
//
HRESULT CShellOcx::SetClientSite(IOleClientSite *pClientSite)
{
    if (_pDispAmbient)
    {
        _pDispAmbient->Release();
        _pDispAmbient = NULL;
    }

    return SUPERCLASS::SetClientSite(pClientSite);
}


HRESULT CShellOcx::Draw(
    DWORD dwDrawAspect,
    LONG lindex,
    void *pvAspect,
    DVTARGETDEVICE *ptd,
    HDC hdcTargetDev,
    HDC hdcDraw,
    LPCRECTL lprcBounds,
    LPCRECTL lprcWBounds,
    BOOL ( __stdcall *pfnContinue )(ULONG_PTR dwContinue),
    ULONG_PTR dwContinue)
{
    if (_IsDesignMode())
    {
        HBRUSH hbrOld = (HBRUSH)SelectObject(hdcDraw, (HBRUSH)GetStockObject(WHITE_BRUSH));
        HPEN hpenOld = (HPEN)SelectObject(hdcDraw, (HPEN)GetStockObject(BLACK_PEN));
        Rectangle(hdcDraw, lprcBounds->left, lprcBounds->top, lprcBounds->right, lprcBounds->bottom);
        MoveToEx(hdcDraw, lprcBounds->left, lprcBounds->top, NULL);
        LineTo(hdcDraw, lprcBounds->right, lprcBounds->bottom);
        MoveToEx(hdcDraw, lprcBounds->left, lprcBounds->bottom, NULL);
        LineTo(hdcDraw, lprcBounds->right, lprcBounds->top);
        SelectObject(hdcDraw, hbrOld);
        SelectObject(hdcDraw, hpenOld);
        return S_OK;
    }

    return SUPERCLASS::Draw(dwDrawAspect, lindex, pvAspect, ptd, hdcTargetDev, hdcDraw,
                            lprcBounds, lprcWBounds, pfnContinue, dwContinue);
}


// IPersistStream

HRESULT CShellOcx::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
    // REVIEW: this is overly large, I believe E_NOTIMPL is a valid
    // return from this and it tells the container that we don't know how big we are.

    ULARGE_INTEGER cbMax = { 1028 * 8, 0 }; // isn't this overly large?
    *pcbSize = cbMax;
    return S_OK;
}

// IOleControl
STDMETHODIMP CShellOcx::GetControlInfo(LPCONTROLINFO pCI)
{
    return E_NOTIMPL; // for mnemonics
}
STDMETHODIMP CShellOcx::OnMnemonic(LPMSG pMsg)
{
    return E_NOTIMPL; // for mnemonics
}
STDMETHODIMP CShellOcx::OnAmbientPropertyChange(DISPID dispid)
{
    switch (dispid)
    {
    case DISPID_AMBIENT_USERMODE:           // design mode  vs  run mode
    case DISPID_UNKNOWN:
        _nDesignMode = MODE_UNKNOWN;
        break;
    }

    return S_OK;
}

STDMETHODIMP CShellOcx::FreezeEvents(BOOL bFreeze)
{
    _fEventsFrozen = bFreeze;
    return S_OK;
}

HRESULT CShellOcx::GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
    // This is gross, for some reason from VBScript in a page can not get "Document" through so try "Doc" and map
    HRESULT hres = CImpIDispatch::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
    if (FAILED(hres) && (cNames == 1) && rgszNames)
    {
        OLECHAR const *c_pwszDocument = L"Document";

        if (StrCmpIW(*rgszNames, L"Doc") == 0)
            hres = CImpIDispatch::GetIDsOfNames(riid, (OLECHAR**)&c_pwszDocument, cNames, lcid, rgdispid);
    }
    return hres;
}


// ConnectionPointContainer
CConnectionPoint* CShellOcx::_FindCConnectionPointNoRef(BOOL fdisp, REFIID iid)
{
    CConnectionPoint* pccp;

    if (IsEqualIID(iid, EVENTIIDOFCONTROL(this)) ||
        (fdisp && IsEqualIID(iid, IID_IDispatch)))
    {
        pccp = &m_cpEvents;
    }
    else if (IsEqualIID(iid, IID_IPropertyNotifySink))
    {
        pccp = &m_cpPropNotify;
    }
    else
    {
        pccp = NULL;
    }

    return pccp;
}

STDMETHODIMP CShellOcx::EnumConnectionPoints(LPENUMCONNECTIONPOINTS * ppEnum)
{
    return CreateInstance_IEnumConnectionPoints(ppEnum, 2,
            m_cpEvents.CastToIConnectionPoint(),
            m_cpPropNotify.CastToIConnectionPoint());
}

// IProvideClassInfo2
STDMETHODIMP CShellOcx::GetClassInfo(LPTYPEINFO * ppTI)
{
    if (!_pClassTypeInfo) 
        GetTypeInfoFromLibId(LANGIDFROMLCID(g_lcidLocale),
            LIBID_SHDocVw, 1, 1, CLSIDOFOBJECT(this), &_pClassTypeInfo);

    if (_pClassTypeInfo) 
    {
        _pClassTypeInfo->AddRef();
        *ppTI = _pClassTypeInfo;
        return S_OK;
    }

    ppTI = NULL;
    return E_FAIL;
}

// IProvideClassInfo2

STDMETHODIMP CShellOcx::GetGUID(DWORD dwGuidKind, GUID *pGUID)
{
    if (pGUID == NULL)
        return E_POINTER;
    
    if ( (dwGuidKind == GUIDKIND_DEFAULT_SOURCE_DISP_IID)
        && _pObjectInfo->piidEvents)
    {
        *pGUID = EVENTIIDOFCONTROL(this);
        return S_OK;
    }
    *pGUID = GUID_NULL;
    return E_FAIL;
}
 
// returns TRUE iff MODE_DESIGN

BOOL CShellOcx::_IsDesignMode(void)
{
    if (_nDesignMode == MODE_UNKNOWN)
    {
        VARIANT_BOOL fBool;

        if (_GetAmbientProperty(DISPID_AMBIENT_USERMODE, VT_BOOL, &fBool))
        {
            _nDesignMode = fBool ? MODE_FALSE : MODE_TRUE;
        }
        else
            _nDesignMode = MODE_FALSE;
    }
    return _nDesignMode == MODE_TRUE;
}

// this table is used for copying data around, and persisting properties.
// basically, it contains the size of a given data type
//
const BYTE g_rgcbDataTypeSize[] = {
    0,                      // VT_EMPTY = 0,
    0,                      // VT_NULL = 1,
    sizeof(short),          // VT_I2 = 2,
    sizeof(long),           // VT_I4 = 3,
    sizeof(float),          // VT_R4 = 4,
    sizeof(double),         // VT_R8= 5,
    sizeof(CURRENCY),       // VT_CY= 6,
    sizeof(DATE),           // VT_DATE = 7,
    sizeof(BSTR),           // VT_BSTR = 8,
    sizeof(IDispatch *),    // VT_DISPATCH = 9,
    sizeof(SCODE),          // VT_ERROR = 10,
    sizeof(VARIANT_BOOL),   // VT_BOOL = 11,
    sizeof(VARIANT),        // VT_VARIANT = 12,
    sizeof(IUnknown *),     // VT_UNKNOWN = 13,
};


// returns the value of an ambient property
//
// Parameters:
//    DISPID        - [in]  property to get
//    VARTYPE       - [in]  type of desired data
//    void *        - [out] where to put the data
//
// Output:
//    BOOL          - FALSE means didn't work.
//
// Notes:
//
BOOL CShellOcx::_GetAmbientProperty(DISPID dispid, VARTYPE vt, void *pData)
{
    // IE30's WebBrowser OC never requested ambient properties.
    // IE40's does and we're finding that apps implemented some of
    // the properties we care about incorrectly. Assume old classid
    // means this is an old app and fail. The code that calls this
    // is smart enough to deal with failure.
    //
    if (_pObjectInfo->pclsid == &CLSID_WebBrowser_V1)
        return FALSE;

    HRESULT hr = E_FAIL;

    if (!_pDispAmbient && _pcli)
        _pcli->QueryInterface(IID_PPV_ARG(IDispatch, &_pDispAmbient));

    if (_pDispAmbient)
    {
        DISPPARAMS dispparams = {0};
        VARIANT v;
        VariantInit(&v);
        hr = _pDispAmbient->Invoke(dispid, IID_NULL, 0, DISPATCH_PROPERTYGET, &dispparams, &v, NULL, NULL);
        if (SUCCEEDED(hr))
        {
            VARIANT vDest;
            VariantInit(&vDest);
            // we've got the variant, so now go an coerce it to the type
            // that the user wants.
            //
            hr = VariantChangeType(&vDest, &v, 0, vt);
            if (SUCCEEDED(hr))
            {
                // copy the data to where the user wants it
                //
                CopyMemory(pData, &vDest.lVal, g_rgcbDataTypeSize[vt]);
                VariantClear(&vDest);
            }
            VariantClear(&v);
        }
    }
    return SUCCEEDED(hr);
}