#include "priv.h"
#pragma hdrstop


const GUID CLSID_CDocProp = {0x3EA48300L, 0x8CF6, 0x101B, 0x84, 0xFB, 0x66, 0x6C, 0xCB, 0x9B, 0xCD, 0x32};
HRESULT CDocProp_CreateInstance(IUnknown *punkOuter, REFIID riid, void **);

// Global variables

UINT g_cRefDll = 0;         // Reference count of this DLL.
HANDLE g_hmodThisDll = NULL;    // Handle to this DLL itself.

STDAPI_(BOOL) DllEntry(HANDLE hDll, DWORD dwReason, LPVOID lpReserved)
{
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hmodThisDll = hDll;
        DisableThreadLibraryCalls(hDll);
        SHFusionInitializeFromModule(hDll);
        break;

        case DLL_PROCESS_DETACH:
        SHFusionUninitialize();
        break;
    }
    return TRUE;
}

typedef struct {
    const IClassFactoryVtbl *cf;
    const CLSID *pclsid;
    HRESULT (*pfnCreate)(IUnknown *, REFIID, void **);
} OBJ_ENTRY;

extern const IClassFactoryVtbl c_CFVtbl;        // forward

//
// we always do a linear search here so put your most often used things first
//
const OBJ_ENTRY c_clsmap[] = {
    { &c_CFVtbl, &CLSID_CDocProp,   CDocProp_CreateInstance},
    // add more entries here
    { NULL, NULL, NULL}
};

// static class factory (no allocs!)

STDMETHODIMP CClassFactory_QueryInterface(IClassFactory *pcf, REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvObj = (void *)pcf;
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }
    DllAddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG) CClassFactory_AddRef(IClassFactory *pcf)
{
    DllAddRef();
    return 2;
}

STDMETHODIMP_(ULONG) CClassFactory_Release(IClassFactory *pcf)
{
    DllRelease();
    return 1;
}

STDMETHODIMP CClassFactory_CreateInstance(IClassFactory *pcf, IUnknown *punkOuter, REFIID riid, void **ppvObject)
{
    OBJ_ENTRY *this = IToClass(OBJ_ENTRY, cf, pcf);
    return this->pfnCreate(punkOuter, riid, ppvObject);
}

STDMETHODIMP CClassFactory_LockServer(IClassFactory *pcf, BOOL fLock)
{
    if (fLock)
        DllAddRef();
    else
        DllRelease();
    return S_OK;
}

const IClassFactoryVtbl c_CFVtbl = {
    CClassFactory_QueryInterface, CClassFactory_AddRef, CClassFactory_Release,
    CClassFactory_CreateInstance,
    CClassFactory_LockServer
};

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
    if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown))
    {
        const OBJ_ENTRY *pcls;
        for (pcls = c_clsmap; pcls->pclsid; pcls++)
        {
            if (IsEqualIID(rclsid, pcls->pclsid))
            {
                *ppv = (void *)&(pcls->cf);
                DllAddRef();    // Class Factory keeps dll in memory
                return NOERROR;
            }
        }
    }
    // failure
    *ppv = NULL;
    return CLASS_E_CLASSNOTAVAILABLE;;
}

STDAPI_(void) DllAddRef()
{
    InterlockedIncrement(&g_cRefDll);
}

STDAPI_(void) DllRelease()
{
    InterlockedDecrement(&g_cRefDll);
}

STDAPI DllCanUnloadNow(void)
{
    return g_cRefDll == 0 ? S_OK : S_FALSE;
}


typedef struct
{
    IShellExtInit           _ei;
    IShellPropSheetExt      _pse;
    int                     _cRef;                  // reference count
    IDataObject *           _pdtobj;                // data object
    TCHAR                   _szFile[MAX_PATH];
} CDocProp;


STDMETHODIMP_(UINT) CDocProp_PSE_AddRef(IShellPropSheetExt *pei)
{
    CDocProp *this = IToClass(CDocProp, _pse, pei);
    return ++this->_cRef;
}

STDMETHODIMP_(UINT) CDocProp_PSE_Release(IShellPropSheetExt *pei)
{
    CDocProp *this = IToClass(CDocProp, _pse, pei);

    if (--this->_cRef)
        return this->_cRef;

    if (this->_pdtobj)
        this->_pdtobj->lpVtbl->Release(this->_pdtobj);

    LocalFree((HLOCAL)this);
    DllRelease();
    return 0;
}

STDMETHODIMP CDocProp_PSE_QueryInterface(IShellPropSheetExt *pei, REFIID riid, void **ppvOut)
{
    CDocProp *this = IToClass(CDocProp, _pse, pei);

    if (IsEqualIID(riid, &IID_IShellPropSheetExt) ||
        IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvOut = (void *)pei;
    }
    else if (IsEqualIID(riid, &IID_IShellExtInit))
    {
        *ppvOut = (void *)&this->_ei;
    }
    else
    {
        *ppvOut = NULL;
        return E_NOINTERFACE;
    }

    this->_cRef++;
    return NOERROR;
}

#ifdef _ABBREVIATED_DOCPROP_
#define NUM_PAGES 1
#else  //_ABBREVIATED_DOCPROP_
#define NUM_PAGES 4
#endif //_ABBREVIATED_DOCPROP_

UINT CALLBACK PSPCallback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE psp)
{
    switch (uMsg) {
    case PSPCB_RELEASE:
        if (psp && psp->lParam)
        {
            LPALLOBJS lpallobjs = (LPALLOBJS)psp->lParam;
            if (0 == --lpallobjs->uPageRef)
            {
                if (lpallobjs->fOleInit)
                    CoUninitialize();

                // Free our structure so hope we don't get it again!
                FOfficeDestroyObjects(&lpallobjs->lpSIObj, &lpallobjs->lpDSIObj, &lpallobjs->lpUDObj);

                GlobalFree(lpallobjs);
            }
        }
        DllRelease();
        break;
    }
    return 1;
}

STDMETHODIMP CDocProp_PSE_AddPages(IShellPropSheetExt *ppse, LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
{
    CDocProp *this = IToClass(CDocProp, _pse, ppse);
    STGMEDIUM medium;
    FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    HRESULT hres = this->_pdtobj->lpVtbl->GetData(this->_pdtobj, &fmte, &medium);
    if (hres == S_OK && (DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, NULL, 0) == 1))
    {
        WCHAR wszPath[MAX_PATH];
        TCHAR szPath[MAX_PATH];
        DWORD grfStgMode;
        IStorage *pstg = NULL;

        DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath));

#ifdef UNICODE
        lstrcpyn(wszPath, szPath, MAX_PATH);
#else
        MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, ARRAYSIZE(wszPath));
#endif

        // Load the properties for this file
        grfStgMode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

#ifdef WINNT
        if( GetFileAttributes( szPath ) & FILE_ATTRIBUTE_OFFLINE )
        {
            ReleaseStgMedium(&medium);
            return HRESULT_FROM_WIN32(ERROR_FILE_OFFLINE);
        }

        hres = StgOpenStorageEx(wszPath, grfStgMode, STGFMT_STORAGE, 0, NULL, NULL, &IID_IStorage, (void**)&pstg);
#else
        hres = StgOpenStorage( wszPath, NULL, grfStgMode, NULL, 0L, &pstg ) ;
#endif
        if (FAILED(hres))
        {
            // if we failed to open the file, try w/READ ONLY access
            grfStgMode = STGM_SHARE_EXCLUSIVE | STGM_READ;
#ifdef WINNT
            hres = StgOpenStorageEx(wszPath, grfStgMode, STGFMT_STORAGE, 0, NULL, NULL, &IID_IStorage, (void**)&pstg);
#else
            hres = StgOpenStorage( wszPath, NULL, grfStgMode, NULL, 0L, &pstg ) ;
#endif
        }


        if (SUCCEEDED(hres))
        {
            int i;

            // Allocate our main structure and make sure it is zero filled!
            LPALLOBJS lpallobjs = (LPALLOBJS)GlobalAlloc(GPTR, sizeof(ALLOBJS));
            if (lpallobjs)
            {
                PROPSHEETPAGE psp[NUM_PAGES];

                lstrcpyn(lpallobjs->szPath, szPath, ARRAYSIZE(lpallobjs->szPath));

                // Initialize Office property code
#ifdef _ABBREVIATED_DOCPROP_
                FOfficeCreateAndInitObjects( NULL, NULL, &lpallobjs->lpUDObj);
#else  _ABBREVIATED_DOCPROP_
                FOfficeCreateAndInitObjects(&lpallobjs->lpSIObj, &lpallobjs->lpDSIObj, &lpallobjs->lpUDObj);
#endif _ABBREVIATED_DOCPROP_

                lpallobjs->lpfnDwQueryLinkData = NULL;
                lpallobjs->dwMask = 0;

                // Fill in some stuff for the Office code
                lpallobjs->fFiledataInit = FALSE;

                // Initialize OLE
                lpallobjs->fOleInit = SUCCEEDED(CoInitialize(0));

                // Initialize the PropertySheets we're going to add
                FOfficeInitPropInfo(psp, PSP_USECALLBACK, (LPARAM)lpallobjs, PSPCallback);
                FLoadTextStrings();

#ifdef _ABBREVIATED_DOCPROP_
                DwOfficeLoadProperties(pstg, NULL, NULL, lpallobjs->lpUDObj, 0, grfStgMode);
#else _ABBREVIATED_DOCPROP_
                DwOfficeLoadProperties(pstg, lpallobjs->lpSIObj, lpallobjs->lpDSIObj, lpallobjs->lpUDObj, 0, grfStgMode);
#endif _ABBREVIATED_DOCPROP_

                // Try to add our new property pages
                for (i = 0; i < NUM_PAGES; i++) 
                {
                    HPROPSHEETPAGE  hpage = CreatePropertySheetPage(&psp[i]);
                    if (hpage) 
                    {
                        DllAddRef();            // matched in PSPCB_RELEASE
                        if (lpfnAddPage(hpage, lParam))
                        {
                            FAttach( lpallobjs, psp + i, hpage );
                            lpallobjs->uPageRef++;
                        }
                        else 
                            DestroyPropertySheetPage(hpage);
                    }
                }

                if (lpallobjs->uPageRef == 0)
                {
                    if (lpallobjs->fOleInit)
                        CoUninitialize();

                    // Free our structures
                    FOfficeDestroyObjects(&lpallobjs->lpSIObj, &lpallobjs->lpDSIObj, &lpallobjs->lpUDObj);
                    GlobalFree(lpallobjs);
                }

            }   // if (lpallobjs)
        }   // StgOpenStorage ... if (SUCCEEDED(hres))

        if (NULL != pstg )
        {
            pstg->lpVtbl->Release(pstg);
            pstg = NULL;
        }
        ReleaseStgMedium(&medium);
    }
    return S_OK;
}

STDMETHODIMP CDocProp_SEI_Initialize(IShellExtInit *pei, LPCITEMIDLIST pidlFolder, LPDATAOBJECT pdtobj, HKEY hkeyProgID)
{
    CDocProp *this = IToClass(CDocProp, _ei, pei);

    // Initialize can be called more than once.
    if (this->_pdtobj)
        this->_pdtobj->lpVtbl->Release(this->_pdtobj);

    // Duplicate the pdtobj pointer
    if (pdtobj) 
    {
        this->_pdtobj = pdtobj;
        pdtobj->lpVtbl->AddRef(pdtobj);
    }

    return NOERROR;
}

STDMETHODIMP_(UINT) CDocProp_SEI_AddRef(IShellExtInit *pei)
{
    CDocProp *this = IToClass(CDocProp, _ei, pei);
    return CDocProp_PSE_AddRef(&this->_pse);
}

STDMETHODIMP_(UINT) CDocProp_SEI_Release(IShellExtInit *pei)
{
    CDocProp *this = IToClass(CDocProp, _ei, pei);
    return CDocProp_PSE_Release(&this->_pse);
}

STDMETHODIMP CDocProp_SEI_QueryInterface(IShellExtInit *pei, REFIID riid, void **ppv)
{
    CDocProp *this = IToClass(CDocProp, _ei, pei);
    return CDocProp_PSE_QueryInterface(&this->_pse, riid, ppv);
}


extern IShellExtInitVtbl           c_CDocProp_SXIVtbl;
extern IShellPropSheetExtVtbl      c_CDocProp_SPXVtbl;

HRESULT CDocProp_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
{
    CDocProp *pdp;

    if (punkOuter)
        return CLASS_E_NOAGGREGATION;

    pdp = LocalAlloc(LPTR, sizeof(CDocProp));
    if (pdp)
    {
        HRESULT hres;

        DllAddRef();

        pdp->_ei.lpVtbl = &c_CDocProp_SXIVtbl;
        pdp->_pse.lpVtbl = &c_CDocProp_SPXVtbl;
        pdp->_cRef = 1;

        hres = CDocProp_PSE_QueryInterface(&pdp->_pse, riid, ppvOut);
        CDocProp_PSE_Release(&pdp->_pse);

        return hres;        // S_OK or E_NOINTERFACE
    }
    return E_OUTOFMEMORY;
}

IShellPropSheetExtVtbl c_CDocProp_SPXVtbl = {
    CDocProp_PSE_QueryInterface, CDocProp_PSE_AddRef, CDocProp_PSE_Release,
    CDocProp_PSE_AddPages
};

IShellExtInitVtbl c_CDocProp_SXIVtbl = {
    CDocProp_SEI_QueryInterface, CDocProp_SEI_AddRef, CDocProp_SEI_Release,
    CDocProp_SEI_Initialize
};