/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/textconsole.h"
#include "bagel/mfc/afxwin.h"
#include "bagel/mfc/keyboard.h"

namespace Bagel {
namespace MFC {

IMPLEMENT_DYNAMIC(CDialog, CWnd)
BEGIN_MESSAGE_MAP(CDialog, CWnd)
	ON_COMMAND(IDOK, CDialog::OnOK)
	ON_COMMAND(IDCANCEL, CDialog::OnCancel)
	ON_MESSAGE(WM_INITDIALOG, CDialog::HandleInitDialog)
	ON_MESSAGE(WM_SETFONT, CDialog::HandleSetFont)
	ON_WM_SYSCHAR()
	ON_WM_CLOSE()
	ON_WM_ACTIVATE()
END_MESSAGE_MAP()

CDialog::CDialog(const char *lpszTemplateName, CWnd *pParentWnd) {
	SetParent(pParentWnd);
	m_lpszTemplateName = lpszTemplateName;

	if (HIWORD(m_lpszTemplateName) == 0)
		m_nIDHelp = LOWORD(m_lpszTemplateName);
}

CDialog::CDialog(unsigned int nIDTemplate, CWnd *pParentWnd) {
	SetParent(pParentWnd);
	m_lpszTemplateName = MAKEINTRESOURCE(nIDTemplate);
	m_nIDHelp = nIDTemplate;
}

bool CDialog::Create(const char *lpszTemplateName,
	CWnd *pParentWnd) {
	m_lpszTemplateName = lpszTemplateName;  // used for help
	SetParent(pParentWnd);

	if (HIWORD(m_lpszTemplateName) == 0 && m_nIDHelp == 0)
		m_nIDHelp = LOWORD(m_lpszTemplateName);

	HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DIALOG);
	HRSRC hResource = FindResource(hInst, lpszTemplateName, RT_DIALOG);
	HGLOBAL hTemplate = LoadResource(hInst, hResource);
	bool bResult = CreateIndirect(hTemplate, m_pParentWnd, hInst);
	FreeResource(hTemplate);

	return bResult;
}

bool CDialog::Create(unsigned int nIDTemplate, CWnd *pParentWnd) {
	return Create(MAKEINTRESOURCE(nIDTemplate), pParentWnd);
}

int CDialog::DoModal() {
	// can be constructed with a resource template or InitModalIndirect
	assert(m_lpszTemplateName != nullptr || m_hDialogTemplate != nullptr ||
		m_lpDialogTemplate != nullptr);

	// Save the prior focus, if any
	CWnd *oldWin = AfxGetApp()->GetActiveWindow();
	CWnd *oldFocus = GetFocus();
	if (oldFocus)
		AfxGetApp()->SetFocus(nullptr);

	// load resource as necessary
	LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;
	HGLOBAL hDialogTemplate = m_hDialogTemplate;
	HINSTANCE hInst = AfxGetResourceHandle();

	if (m_lpszTemplateName != nullptr) {
		hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
		HRSRC hResource = FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
		assert(hResource);

		hDialogTemplate = LoadResource(hInst, hResource);
	}

	// Set up pointer to template data
	if (hDialogTemplate != nullptr)
		lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);

	// Ensure the template is presenet
	if (lpDialogTemplate == nullptr)
		return -1;

	// Set the dialog parent to be the app window
	if (!m_pParentWnd)
		m_pParentWnd = AfxGetApp()->m_pMainWnd;

	if (CreateDlgIndirect(lpDialogTemplate,
		this /*m_pParentWnd*/, hInst)) {
		AfxGetApp()->doModal(this);
	}

	// Finish the dialog
	PostModal();

	// unlock/free resources as necessary
	if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)
		UnlockResource(hDialogTemplate);
	if (m_lpszTemplateName != NULL)
		FreeResource(hDialogTemplate);

	// Restore any old focus
	if (oldFocus && AfxGetApp()->GetActiveWindow() == oldWin)
		oldFocus->SetFocus();

	if (_modalResult == DEFAULT_MODAL_RESULT)
		_modalResult = -1;

	return _modalResult;
}

bool CDialog::CreateDlgIndirect(LPCDLGTEMPLATE lpDialogTemplate,
	CWnd *pParentWnd, HINSTANCE hInst) {
	assert(lpDialogTemplate != nullptr);

	if (hInst == nullptr)
		hInst = AfxGetInstanceHandle();

	HGLOBAL hTemplate = nullptr;

#if 0
	// If no font specified, set the system font.
	CString strFace;
	uint16 wSize = 0;
	bool bSetSysFont = !CDialogTemplate::GetFont(lpDialogTemplate, strFace,
		wSize);
	if (bSetSysFont) {
		CDialogTemplate dlgTemp(lpDialogTemplate);
		dlgTemp.SetSystemFont(wSize);
		hTemplate = dlgTemp.Detach();
	}
#endif
	if (hTemplate != nullptr)
		lpDialogTemplate = (LPDLGTEMPLATE)GlobalLock(hTemplate);

	// setup for modal loop and creation
	_modalResult = DEFAULT_MODAL_RESULT;
	m_nFlags |= WF_CONTINUEMODAL;

	// Create modeless dialog
	createDialogIndirect(lpDialogTemplate);

	if (hTemplate != nullptr) {
		GlobalUnlock(hTemplate);
		GlobalFree(hTemplate);
	}

	return true;
}

void CDialog::SetParent(CWnd *wnd) {
	m_pParentWnd = wnd;
}

uint32 CDialog::GetDefID() const {
	return _defaultId;
}

void CDialog::SetDefID(unsigned int nID) {
	CWnd *oldBtn;

	if (_defaultId && (oldBtn = GetDlgItem(_defaultId)) != nullptr) {
		uint32 style = oldBtn->GetStyle();

		if ((style & 0xf) != BS_OWNERDRAW) {
			style &= ~BS_DEFPUSHBUTTON;
			oldBtn->SetStyle(style);
		}
	}

	_defaultId = nID;

	// Set new default
	CWnd *newBtn = GetDlgItem(nID);
	if (newBtn) {
		uint32 style = newBtn->GetStyle();

		if ((style & 0xf) != BS_OWNERDRAW) {
			style = (style & ~0xF) | BS_DEFPUSHBUTTON;
			newBtn->SetStyle(style);
		}
	}
}

LRESULT CDialog::HandleInitDialog(WPARAM, LPARAM) {
	PreInitDialog();

	OnInitDialog();
	return 0;
}

LRESULT CDialog::HandleSetFont(WPARAM wParam, LPARAM) {
	// Allow the CWnd code to set the _hFont field
	CWnd::OnSetFont((HFONT)wParam, true);

	// Virtual method dialog descendants can override
	OnSetFont(CFont::FromHandle((HFONT)wParam));
	return 0;
}

CButton *CDialog::GetDefaultPushButton() const {
	for (auto &child : _children) {
		CButton *pChild = dynamic_cast<CButton *>(child._value);

		if (pChild) {
			uint32 style = pChild->GetStyle();
			if ((style & BS_DEFPUSHBUTTON) && pChild->GetDlgCtrlID() == IDOK) {
				return pChild;
			}
		}
	}

	return nullptr;
}

void CDialog::DDX_Control(CDataExchange *pDX, int nIDC, CWnd &rControl) {
	rControl.SubclassDlgItem(nIDC, this);
}

void CDialog::DDX_Radio(CDataExchange *pDX,
	int nIDCButton1, int &value) {
	error("TODO: CDialog::DDX_Radio");
}

void CDialog::DDX_Text(CDataExchange *pDX, int nIDC, int &value) {
	error("TODO: CDialog::DDX_Text");
}

void CDialog::DDX_Text(CDataExchange *pDX, int nIDC, CString &value) {
	CEdit *edit = dynamic_cast<CEdit *>(GetDlgItem(nIDC));
	assert(edit);

	if (pDX->m_bSaveAndValidate) {
		edit->GetWindowText(value);
	} else {
		edit->SetWindowText(value.c_str());
	}
}

void CDialog::DDX_Text(CDataExchange *pDX, int nIDC, unsigned int &value) {
	error("TODO: CDialog::DDX_Text");
}
void CDialog::DDX_Text(CDataExchange *pDX, int nIDC, long &value) {
	error("TODO: CDialog::DDX_Text");
}
void CDialog::DDX_Text(CDataExchange *pDX, int nIDC, double &value) {
	error("TODO: CDialog::DDX_Text");
}

void CDialog::DDX_Check(CDataExchange *pDX,
	int nIDC, bool value) {
	error("CDialog::DDX_Check");
}

void CDialog::DDV_MinMaxInt(CDataExchange *pDX,
	int value, int nMin, int nMax) {
	error("TODO: CDialog::DDV_MinMaxInt");
}

void CDialog::EndDialog(int nResult) {
	_modalResult = nResult;
}

bool CDialog::UpdateData(bool bSaveAndValidate) {
	if (bSaveAndValidate) {
		CDataExchange exchange = { true };
		DoDataExchange(&exchange);
	}

	return true;
}

void CDialog::OnOK() {
	if (!UpdateData(true))
		return;

	EndDialog(IDOK);
}

void CDialog::OnCancel() {
	EndDialog(IDCANCEL);
}

bool CDialog::CreateIndirect(LPCDLGTEMPLATE lpDialogTemplate,
	CWnd *pParentWnd, void *lpDialogInit, HINSTANCE hInst) {
	error("TODO: CDialog::CreateIndirect");
}

bool CDialog::CreateIndirect(HGLOBAL hDialogTemplate,
	CWnd *pParentWnd, HINSTANCE hInst) {
	error("TODO: CDialog::CreateIndirect");
}

void CDialog::OnSysChar(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags) {
	// Convert input character to uppercase for case-insensitive match
	char pressedChar = toupper(nChar);
	for (auto &child : _children) {
		CWnd *pChild = child._value;
		CString text;
		pChild->GetWindowText(text);

		// Check for ampersand for hotkey underlining
		uint idx = text.findFirstOf('&');

		if (idx != Common::String::npos &&
			pressedChar == toupper(text[idx + 1])) {
			// Found match - simulate button click
			pChild->SendMessage(BM_CLICK);
			return;
		}
	}

	// If no match found, pass to base class
	CWnd::OnSysChar(nChar, nRepCnt, nFlags);
}

void CDialog::OnActivate(unsigned int nState, CWnd *pWndOther, bool bMinimized) {
	if (nState != WA_INACTIVE) {
		// Invalidate the dialog and its children
		Invalidate(true);
		for (auto child : _children)
			child._value->Invalidate(true);
	}

	CWnd::OnActivate(nState, pWndOther, bMinimized);
}

bool CDialog::IsDialogMessage(LPMSG lpMsg) {
	if (lpMsg->message != WM_KEYDOWN)
		return false;

	switch (lpMsg->wParam) {
	case VK_RETURN:
		return handleEnterKey(lpMsg);

	case VK_ESCAPE:
		return handleEscapeKey(lpMsg);

	default:
		break;
	}

	return false;
}

bool CDialog::handleEnterKey(LPMSG lpMsg) {
	HWND hFocus = GetFocus();
	if (!hFocus)
		return false;

	// Ask control what it wants
	LRESULT dlgCode = MFC::SendMessage(hFocus, WM_GETDLGCODE,
		lpMsg->wParam, (LPARAM)lpMsg);

	// If control wants Enter, do nothing
	if (dlgCode & DLGC_WANTMESSAGE)
		return false;

	// Multiline edits implicitly want Enter
	if (dlgCode & DLGC_HASSETSEL)
		return false;

	// Find default push button
	CButton *pDefault = GetDefaultPushButton();
	if (!pDefault)
		return false;

	if (!pDefault->IsWindowEnabled())
		// consume Enter, do nothing
		return true;

	// Simulate button click
	sendButtonClicked(pDefault);
	return true;
}

bool CDialog::handleEscapeKey(LPMSG lpMsg) {
	CButton *pCancel = dynamic_cast<CButton *>(GetDlgItem(IDCANCEL));
	if (!pCancel || !pCancel->IsWindowEnabled())
		return true;

	sendButtonClicked(pCancel);
	return true;
}

void CDialog::sendButtonClicked(CButton *btn) {
	int id = btn->GetDlgCtrlID();
	SendMessage(WM_COMMAND, MAKEWPARAM(id, BN_CLICKED), (LPARAM)btn->m_hWnd);
}

} // namespace MFC
} // namespace Bagel
