// MOleHost.cpp --- OLE Host
//////////////////////////////////////////////////////////////////////////////
// RisohEditor --- Another free Win32 resource editor
// Copyright (C) 2026 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
// License: GPL-3 or later

#include "MWindowBase.hpp"
#include "MOleHost.hpp"
#include <oledlg.h>

#define ID_VERBMIN 10000
#define ID_VERBMAX 10031

static const CLSID CLSID_SampleFile =
	{0x9f05f2d2, 0x9ec3, 0x53ac, {0xc6, 0xff, 0x20, 0x7e, 0x3f, 0x8a, 0x97, 0xff}};

static MOleHost *s_pHost = NULL;

MOleHost *DoGetActiveOleHost(void)
{
	assert(s_pHost != NULL);
	return s_pHost;
}

void DoSetActiveOleHost(MOleHost *pHost)
{
	s_pHost = pHost;
}

IMPLEMENT_DYNAMIC(MOleSite)

//////////////////////////////////////////////////////////////////////////////
// MOleSite

MOleSite::MOleSite()
	: m_pNext(NULL)
	, m_cRef(1)
	, m_dwConnection(0)
	, m_bEnableMenu(FALSE)
	, m_pOleObject(NULL)
	, m_pStorage(NULL)
	, m_pHost(NULL)
{
	::SetRectEmpty(&m_rc);
}

MOleSite::~MOleSite()
{
	DoDestroy();
}

BOOL MOleSite::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
	MStringW strText = MWindowBase::GetWindowText(hwnd);

	CLSID clsid;
	if (strText.empty() || strText[0] != L'{' || ::CLSIDFromString(strText.c_str(), &clsid) != S_OK)
		return FALSE;

	m_pHost = DoGetActiveOleHost();
	if (m_pHost == NULL)
		return FALSE;

	return m_pHost->DoInsertObject(this, clsid);
}

void MOleSite::DoDestroy()
{
	if (m_pOleObject)
	{
		DoCloseObject();
		m_pOleObject->Release();
		m_pOleObject = NULL;
	}

	if (m_pStorage)
	{
		m_pStorage->Release();
		m_pStorage = NULL;
	}
}

void MOleSite::OnDestroy(HWND hwnd)
{
	DoDestroy();
}

void MOleSite::DoEnableMenu(BOOL bEnable)
{
	m_bEnableMenu = bEnable;
}

void MOleSite::OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
	if (fDoubleClick)
	{
		if (m_bEnableMenu)
			DoRunObject(OLEIVERB_PRIMARY);
	}
}

void MOleSite::OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
	if (!m_bEnableMenu)
		return;

	HMENU hMenu = CreatePopupMenu(), hmenuTmp;
	::OleUIAddVerbMenu(m_pOleObject, NULL, hMenu, 0, ID_VERBMIN, ID_VERBMAX, FALSE, 0, &hmenuTmp);

	POINT pt = { x, y };
	::ClientToScreen(hwnd, &pt);

	SetForegroundWindow(hwnd);
	INT nCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
	DestroyMenu(hMenu);
	PostMessageW(hwnd, WM_NULL, 0, 0);

	PostMessageW(hwnd, WM_COMMAND, nCmd, 0);
}

void MOleSite::OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
	if (!m_bEnableMenu)
		return;

	if (ID_VERBMIN <= id && id <= ID_VERBMAX)
		DoRunObject(id);
}

void MOleSite::OnMove(HWND hwnd, int x, int y)
{
	RECT rc;
	::GetClientRect(hwnd, &rc);
	m_rc = rc;

	if (!m_pOleObject)
		return;

	IOleInPlaceObject *pipo = NULL;
	m_pOleObject->QueryInterface(IID_IOleInPlaceObject, (void **)&pipo);
	if (pipo)
	{
		pipo->SetObjectRects(&m_rc, &m_rc);
		pipo->Release();
	}

	DoUpdateRect();
}

void MOleSite::OnSize(HWND hwnd, UINT state, int cx, int cy)
{
	RECT rc;
	::GetClientRect(hwnd, &rc);
	m_rc = rc;

	if (!m_pOleObject)
		return;

	IOleInPlaceObject *pipo = NULL;
	m_pOleObject->QueryInterface(IID_IOleInPlaceObject, (void **)&pipo);
	if (pipo)
	{
		pipo->SetObjectRects(&m_rc, &m_rc);
		pipo->Release();
	}

	SIZEL sizel = { rc.right - rc.left, rc.bottom - rc.top };
	DoDPtoHIMETRIC(&sizel);
	m_pOleObject->SetExtent(DVASPECT_CONTENT, &sizel);
}

void MOleSite::OnPaint(HWND hwnd)
{
	PAINTSTRUCT ps;
	if (HDC hdc = ::BeginPaint(hwnd, &ps))
	{
		::OleDraw(m_pOleObject, DVASPECT_CONTENT, hdc, &m_rc);
		::EndPaint(hwnd, &ps);
	}
}

STDMETHODIMP MOleSite::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (riid == IID_IUnknown || riid == IID_IOleClientSite)
		*ppvObject = static_cast<IOleClientSite *>(this);
	else if (riid == IID_IAdviseSink)
		*ppvObject = static_cast<IAdviseSink *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	return S_OK;
}

STDMETHODIMP_(ULONG) MOleSite::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) MOleSite::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0)
	{
		delete this;
		return 0;
	}
	return m_cRef;
}

STDMETHODIMP MOleSite::SaveObject()
{
	DoSave();
	DoUpdateRect();
	InvalidateRect(GetParent(m_hwnd), NULL, TRUE);
	return S_OK;
}

STDMETHODIMP MOleSite::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)
{
	return E_NOTIMPL;
}

STDMETHODIMP MOleSite::GetContainer(IOleContainer **ppContainer)
{
	*ppContainer = NULL;
	return E_NOINTERFACE;
}

STDMETHODIMP MOleSite::ShowObject()
{
	return S_OK;
}

STDMETHODIMP MOleSite::OnShowWindow(BOOL fShow)
{
	return S_OK;
}

STDMETHODIMP MOleSite::RequestNewObjectLayout()
{
	return E_NOTIMPL;
}

STDMETHODIMP_(void) MOleSite::OnDataChange(FORMATETC *pFormatetc, STGMEDIUM *pStgmed)
{
}

STDMETHODIMP_(void) MOleSite::OnViewChange(DWORD dwAspect, LONG lindex)
{
	InvalidateRect(GetParent(m_hwnd), NULL, TRUE);
}

STDMETHODIMP_(void) MOleSite::OnRename(IMoniker *pmk)
{
}

STDMETHODIMP_(void) MOleSite::OnSave()
{
}

STDMETHODIMP_(void) MOleSite::OnClose()
{
}

void MOleSite::DoInit(MOleHost *pHost, IOleObject *pOleObject, IStorage *pStorage,
					  LPCWSTR lpszObjectName)
{
	m_pHost = pHost;
	m_pStorage = pStorage;
	m_pOleObject = pOleObject;
	m_pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));
	m_pOleObject->SetHostNames(L"RisohEditor", lpszObjectName);

	OnSize(m_hwnd, SIZE_RESTORED, 0, 0);
}

void MOleSite::DoSave()
{
	IPersistStorage *pPersistStorage = NULL;
	m_pOleObject->QueryInterface(IID_IPersistStorage, (void **)&pPersistStorage);
	if (pPersistStorage)
	{
		::OleSave(pPersistStorage, m_pStorage, TRUE);

		pPersistStorage->SaveCompleted(NULL);
		pPersistStorage->Release();
	}
}

void MOleSite::DoRunObject(LONG nVerb)
{
	m_pOleObject->Advise(static_cast<IAdviseSink *>(this), &m_dwConnection);

	IViewObject *pViewObject = NULL;
	m_pOleObject->QueryInterface(IID_IViewObject, (void **)&pViewObject);
	if (pViewObject)
	{
		pViewObject->SetAdvise(DVASPECT_CONTENT, 0, static_cast<IAdviseSink *>(this));
		pViewObject->Release();
	}

	m_pOleObject->DoVerb(nVerb, NULL, static_cast<IOleClientSite *>(this), 0, m_hwnd, &m_rc);
}

void MOleSite::DoCloseObject()
{
	if (m_pOleObject && ::OleIsRunning(m_pOleObject))
	{
		IOleInPlaceObject *pipo = NULL;
		m_pOleObject->QueryInterface(IID_IOleInPlaceObject, (void **)&pipo);
		if (pipo)
		{
			pipo->UIDeactivate();
			pipo->InPlaceDeactivate();
			pipo->Release();
		}

		m_pOleObject->Close(OLECLOSE_NOSAVE);
		m_pOleObject->Unadvise(m_dwConnection);
		m_pOleObject->SetClientSite(NULL);
	}
}

void MOleSite::DoUpdateRect()
{
	SIZEL sizel;
	m_pOleObject->GetExtent(DVASPECT_CONTENT, &sizel);

	DoHIMETRICtoDP(&sizel);
	m_rc.right = m_rc.left + sizel.cx;
	m_rc.bottom = m_rc.top + sizel.cy;
}

void MOleSite::DoDPtoHIMETRIC(LPSIZEL lpSizel)
{
	const INT HIMETRIC_INCH = 2540;

	HDC hdc = GetDC(NULL);
	lpSizel->cx = lpSizel->cx * HIMETRIC_INCH / GetDeviceCaps(hdc, LOGPIXELSX);
	lpSizel->cy = lpSizel->cy * HIMETRIC_INCH / GetDeviceCaps(hdc, LOGPIXELSY);
	ReleaseDC(NULL, hdc);
}

void MOleSite::DoHIMETRICtoDP(LPSIZEL lpSizel)
{
	const INT HIMETRIC_INCH = 2540;

	HDC hdc = GetDC(NULL);
	lpSizel->cx = lpSizel->cx * GetDeviceCaps(hdc, LOGPIXELSX) / HIMETRIC_INCH;
	lpSizel->cy = lpSizel->cy * GetDeviceCaps(hdc, LOGPIXELSY) / HIMETRIC_INCH;
	ReleaseDC(NULL, hdc);
}

//////////////////////////////////////////////////////////////////////////////
// MOleHost

MOleHost::MOleHost()
	: m_cObjects(0)
	, m_pRootStorage(NULL)
	, m_pStream(NULL)
	, m_pSite(NULL)
{
	DoCreateRootStorage(NULL);
}

MOleHost::~MOleHost()
{
	DoDestroy();
}

BOOL MOleHost::DoCreateRootStorage(LPCWSTR lpszFileName)
{
	static const WCHAR szStreamName[] = L"stream";
	if (lpszFileName == NULL)
	{
		StgCreateDocfile(NULL, STGM_READWRITE | STGM_TRANSACTED, 0, &m_pRootStorage);
		if (m_pRootStorage == NULL)
			return FALSE;

		WriteClassStg(m_pRootStorage, CLSID_SampleFile);

		m_pRootStorage->CreateStream(szStreamName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStream);
		if (m_pStream == NULL)
			return FALSE;

		return TRUE;
	}

	IStorage *pStorage = NULL;
	StgOpenStorage(lpszFileName, NULL, STGM_READWRITE | STGM_TRANSACTED, NULL, 0, &pStorage);
	if (pStorage == NULL)
		return FALSE;

	CLSID clsid;
	ReadClassStg(pStorage, &clsid);
	if (!IsEqualCLSID(clsid, CLSID_SampleFile))
	{
		pStorage->Release();
		return FALSE;
	}

	IStream *pStream = NULL;
	pStorage->OpenStream(szStreamName, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pStream);
	if (pStream == NULL)
	{
		pStorage->Release();
		return FALSE;
	}

	DoDestroy();

	m_pRootStorage = pStorage;
	m_pStream = pStream;

	return TRUE;
}

BOOL MOleHost::DoInsertObject(MOleSite *pSite, REFCLSID clsid)
{
	WCHAR szName[MAX_PATH];
	StringCbPrintfW(szName, sizeof(szName), L"Object %d", m_cObjects + 1);

	IStorage *pStorage = NULL;
	m_pRootStorage->CreateStorage(szName, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStorage);
	if (pStorage == NULL)
		return FALSE;

	IOleObject *pOleObject = NULL;
	HRESULT hr = ::OleCreate(clsid, IID_IOleObject, OLERENDER_DRAW, NULL, NULL,
							 pStorage, (void **)&pOleObject);
	if (FAILED(hr))
	{
		pStorage->Release();
		return FALSE;
	}

	m_cObjects++;
	pSite->DoInit(this, pOleObject, pStorage, szName);

	if (m_pSite)
		pSite->m_pNext = m_pSite;
	m_pSite = pSite;

	return TRUE;
}

void MOleHost::DoEnableMenu(BOOL bEnable)
{
	MOleSite *pSite = m_pSite;
	while (pSite)
	{
		pSite->DoEnableMenu(bEnable);
		pSite = pSite->m_pNext;
	}
}

void MOleHost::DoDestroy()
{
	if (m_pStream)
	{
		m_pStream->Release();
		m_pStream = NULL;
	}
	if (m_pRootStorage)
	{
		m_pRootStorage->Release();
		m_pRootStorage = NULL;
	}
}
