/*
 * Copyright 1999 Corel Corporation
 * Sean Langley
 * Copyright 2010  Geoffrey Hausheer
 * Copyright 2010 Piotr Caban for CodeWeavers
 *
 * 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 of the License, or (at your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "ole2.h"
#include "olectl.h"
#include "oledlg.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ole);

typedef struct {
    IPropertyPageSite IPropertyPageSite_iface;
    LCID lcid;
    LONG ref;
} PropertyPageSite;

static inline PropertyPageSite *impl_from_IPropertyPageSite(IPropertyPageSite *iface)
{
    return CONTAINING_RECORD(iface, PropertyPageSite, IPropertyPageSite_iface);
}

static INT_PTR CALLBACK property_sheet_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    IPropertyPage *property_page = (IPropertyPage*)GetWindowLongPtrW(hwnd, DWLP_USER);

    switch(msg) {
    case WM_INITDIALOG: {
        RECT rect;

        property_page = (IPropertyPage*)((LPPROPSHEETPAGEW)lparam)->lParam;

        GetClientRect(hwnd, &rect);
        IPropertyPage_Activate(property_page, hwnd, &rect, TRUE);
        IPropertyPage_Show(property_page, SW_SHOW);

        SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)property_page);
        return FALSE;
    }
    case WM_DESTROY:
        IPropertyPage_Show(property_page, SW_HIDE);
        IPropertyPage_Deactivate(property_page);
        return FALSE;
    default:
        return FALSE;
    }
}

static HRESULT WINAPI PropertyPageSite_QueryInterface(IPropertyPageSite*  iface,
        REFIID  riid, void**  ppv)
{
    TRACE("(%p riid: %s)\n",iface, debugstr_guid(riid));

    if(IsEqualGUID(&IID_IUnknown, riid)
            || IsEqualGUID(&IID_IPropertyPageSite, riid))
        *ppv = iface;
    else {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI PropertyPageSite_AddRef(IPropertyPageSite* iface)
{
    PropertyPageSite *this = impl_from_IPropertyPageSite(iface);
    LONG ref = InterlockedIncrement(&this->ref);

    TRACE("(%p) ref=%d\n", this, ref);
    return ref;
}

static ULONG WINAPI PropertyPageSite_Release(IPropertyPageSite* iface)
{
    PropertyPageSite *this = impl_from_IPropertyPageSite(iface);
    LONG ref = InterlockedDecrement(&this->ref);

    TRACE("(%p) ref=%d\n", this, ref);
    if(!ref)
        HeapFree(GetProcessHeap(), 0, this);
    return ref;
}

static HRESULT WINAPI PropertyPageSite_OnStatusChange(
        IPropertyPageSite *iface, DWORD dwFlags)
{
    TRACE("(%p, %x)\n", iface, dwFlags);
    return S_OK;
}

static HRESULT WINAPI PropertyPageSite_GetLocaleID(
        IPropertyPageSite *iface, LCID *pLocaleID)
{
    PropertyPageSite *this = impl_from_IPropertyPageSite(iface);

    TRACE("(%p, %p)\n", iface, pLocaleID);
    *pLocaleID = this->lcid;
    return S_OK;
}

static HRESULT WINAPI PropertyPageSite_GetPageContainer(
        IPropertyPageSite* iface, IUnknown** ppUnk)
{
    FIXME("(%p, %p)\n", iface, ppUnk);
    return E_NOTIMPL;
}

static HRESULT WINAPI PropertyPageSite_TranslateAccelerator(
        IPropertyPageSite* iface, MSG *pMsg)
{
    FIXME("(%p, %p)\n", iface, pMsg);
    return E_NOTIMPL;
}

IPropertyPageSiteVtbl PropertyPageSiteVtbl = {
    PropertyPageSite_QueryInterface,
    PropertyPageSite_AddRef,
    PropertyPageSite_Release,
    PropertyPageSite_OnStatusChange,
    PropertyPageSite_GetLocaleID,
    PropertyPageSite_GetPageContainer,
    PropertyPageSite_TranslateAccelerator
};

/***********************************************************************
 * OleCreatePropertyFrameIndirect (OLEAUT32.416)
 */
HRESULT WINAPI OleCreatePropertyFrameIndirect(LPOCPFIPARAMS lpParams)
{
    static const WCHAR comctlW[] = { 'c','o','m','c','t','l','3','2','.','d','l','l',0 };

    PROPSHEETHEADERW property_sheet;
    PROPSHEETPAGEW property_sheet_page;
    struct {
        DLGTEMPLATE template;
        WORD menu;
        WORD class;
        WORD title;
    } *dialogs;
    IPropertyPage **property_page;
    PropertyPageSite *property_page_site;
    HRESULT res;
    int i;
    HMODULE hcomctl;
    HRSRC property_sheet_dialog_find = NULL;
    HGLOBAL property_sheet_dialog_load = NULL;
    WCHAR *property_sheet_dialog_data = NULL;
    HDC hdc;
    LOGFONTW font_desc;
    HFONT hfont;
    LONG font_width = 4, font_height = 8;

    if(!lpParams)
        return E_POINTER;

    TRACE("(%d %p %d %d %s %d %p %d %p %d %d)\n", lpParams->cbStructSize,
            lpParams->hWndOwner, lpParams->x, lpParams->y,
            debugstr_w(lpParams->lpszCaption), lpParams->cObjects,
            lpParams->lplpUnk, lpParams->cPages, lpParams->lpPages,
            lpParams->lcid, lpParams->dispidInitialProperty);

    if(!lpParams->lplpUnk || !lpParams->lpPages)
        return E_POINTER;

    if(lpParams->cbStructSize != sizeof(OCPFIPARAMS)) {
        WARN("incorrect structure size\n");
        return E_INVALIDARG;
    }

    if(lpParams->dispidInitialProperty)
        FIXME("dispidInitialProperty not yet implemented\n");

    hdc = GetDC(NULL);
    hcomctl = LoadLibraryW(comctlW);
    if(hcomctl)
        property_sheet_dialog_find = FindResourceW(hcomctl,
                MAKEINTRESOURCEW(1006 /*IDD_PROPSHEET*/), (LPWSTR)RT_DIALOG);
    if(property_sheet_dialog_find)
        property_sheet_dialog_load = LoadResource(hcomctl, property_sheet_dialog_find);
    if(property_sheet_dialog_load)
        property_sheet_dialog_data = LockResource(property_sheet_dialog_load);

    if(property_sheet_dialog_data) {
        if(property_sheet_dialog_data[1] == 0xffff) {
            ERR("Expected DLGTEMPLATE structure\n");
            return E_OUTOFMEMORY;
        }

        property_sheet_dialog_data += sizeof(DLGTEMPLATE)/sizeof(WCHAR);
        /* Skip menu, class and title */
        property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data)+1;
        property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data)+1;
        property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data)+1;

        memset(&font_desc, 0, sizeof(LOGFONTW));
        /* Calculate logical height */
        font_desc.lfHeight = -MulDiv(property_sheet_dialog_data[0],
                GetDeviceCaps(hdc, LOGPIXELSY), 72);
        font_desc.lfCharSet = DEFAULT_CHARSET;
        memcpy(font_desc.lfFaceName, property_sheet_dialog_data+1,
                sizeof(WCHAR)*(lstrlenW(property_sheet_dialog_data+1)+1));
        hfont = CreateFontIndirectW(&font_desc);

        if(hfont) {
            hfont = SelectObject(hdc, hfont);
            font_width = GdiGetCharDimensions(hdc, NULL, &font_height);
            SelectObject(hdc, hfont);
        }
    }
    if(hcomctl)
        FreeLibrary(hcomctl);
    ReleaseDC(NULL, hdc);

    memset(&property_sheet, 0, sizeof(property_sheet));
    property_sheet.dwSize = sizeof(property_sheet);
    if(lpParams->lpszCaption) {
        property_sheet.dwFlags = PSH_PROPTITLE;
        property_sheet.pszCaption = lpParams->lpszCaption;
    }

    property_sheet.u3.phpage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
            lpParams->cPages*sizeof(HPROPSHEETPAGE));
    property_page = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
            lpParams->cPages*sizeof(IPropertyPage*));
    dialogs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
            lpParams->cPages*sizeof(*dialogs));
    if(!property_sheet.u3.phpage || !property_page || !dialogs) {
        HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage);
        HeapFree(GetProcessHeap(), 0, property_page);
        HeapFree(GetProcessHeap(), 0, dialogs);
        return E_OUTOFMEMORY;
    }

    memset(&property_sheet_page, 0, sizeof(PROPSHEETPAGEW));
    property_sheet_page.dwSize = sizeof(PROPSHEETPAGEW);
    property_sheet_page.dwFlags = PSP_DLGINDIRECT|PSP_USETITLE;
    property_sheet_page.pfnDlgProc = property_sheet_proc;

    for(i=0; i<lpParams->cPages; i++) {
        PROPPAGEINFO page_info;

        res = CoCreateInstance(&lpParams->lpPages[i], NULL, CLSCTX_INPROC_SERVER,
                &IID_IPropertyPage, (void**)&property_page[i]);
        if(FAILED(res))
            continue;

        property_page_site = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyPageSite));
        if(!property_page_site)
            continue;
        property_page_site->IPropertyPageSite_iface.lpVtbl = &PropertyPageSiteVtbl;
        property_page_site->ref = 1;
        property_page_site->lcid = lpParams->lcid;

        res = IPropertyPage_SetPageSite(property_page[i],
                &property_page_site->IPropertyPageSite_iface);
        IPropertyPageSite_Release(&property_page_site->IPropertyPageSite_iface);
        if(FAILED(res))
            continue;

        res = IPropertyPage_SetObjects(property_page[i],
                lpParams->cObjects, lpParams->lplpUnk);
        if(FAILED(res))
            continue;

        res = IPropertyPage_GetPageInfo(property_page[i], &page_info);
        if(FAILED(res))
            continue;

        dialogs[i].template.cx = MulDiv(page_info.size.cx, 4, font_width);
        dialogs[i].template.cy = MulDiv(page_info.size.cy, 8, font_height);

        property_sheet_page.u.pResource = &dialogs[i].template;
        property_sheet_page.lParam = (LPARAM)property_page[i];
        property_sheet_page.pszTitle = page_info.pszTitle;

        property_sheet.u3.phpage[property_sheet.nPages++] =
            CreatePropertySheetPageW(&property_sheet_page);
    }

    PropertySheetW(&property_sheet);

    for(i=0; i<lpParams->cPages; i++) {
        if(property_page[i]) {
            IPropertyPage_SetPageSite(property_page[i], NULL);
            IPropertyPage_Release(property_page[i]);
        }
    }

    HeapFree(GetProcessHeap(), 0, dialogs);
    HeapFree(GetProcessHeap(), 0, property_page);
    HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage);
    return S_OK;
}

/***********************************************************************
 * OleCreatePropertyFrame (OLEAUT32.417)
 */
HRESULT WINAPI OleCreatePropertyFrame(
        HWND hwndOwner, UINT x, UINT y, LPCOLESTR lpszCaption, ULONG cObjects,
        LPUNKNOWN* ppUnk, ULONG cPages, LPCLSID pPageClsID, LCID lcid,
        DWORD dwReserved, LPVOID pvReserved)
{
    OCPFIPARAMS ocpf;

    ocpf.cbStructSize =  sizeof(OCPFIPARAMS);
    ocpf.hWndOwner    = hwndOwner;
    ocpf.x            = x;
    ocpf.y            = y;
    ocpf.lpszCaption  = lpszCaption;
    ocpf.cObjects     = cObjects;
    ocpf.lplpUnk      = ppUnk;
    ocpf.cPages       = cPages;
    ocpf.lpPages      = pPageClsID;
    ocpf.lcid         = lcid;
    ocpf.dispidInitialProperty = 0;

    return OleCreatePropertyFrameIndirect(&ocpf);
}