#pragma once

#include <functional>
#include <vsstyle.h>
#include "WTL-PP.h"

typedef CWinTraits<WS_CHILD|WS_TABSTOP,0> CButtonLiteTraits;

class CButtonLite : public CWindowImpl<CButtonLite, CWindow, CButtonLiteTraits > {
public:
	BEGIN_MSG_MAP_EX(CButtonLite)
		MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, MousePassThru);
		MSG_WM_SETTEXT(OnSetText)
		MSG_WM_PAINT( OnPaint )
		MSG_WM_MOUSEMOVE(OnMouseMove)
		MSG_WM_LBUTTONDOWN(OnLButtonDown)
		MSG_WM_SETFOCUS(OnSetFocus)
		MSG_WM_KILLFOCUS(OnKillFocus)
		MSG_WM_KEYDOWN(OnKeyDown)
		MSG_WM_KEYUP(OnKeyUp)
		MSG_WM_CHAR(OnChar)
		MSG_WM_ENABLE(OnEnable)
		MESSAGE_HANDLER_EX(WM_GETDLGCODE, OnGetDlgCode)
		MSG_WM_SETFONT(OnSetFont)
		MSG_WM_GETFONT(OnGetFont)
		MSG_WM_CREATE(OnCreate)
	END_MSG_MAP()
	std::function<void () > ClickHandler;

	unsigned Measure() {
		auto font = myGetFont();
		LOGFONT lf;
		font.GetLogFont( lf );
		MakeBoldFont( lf );
		CFont bold;
		bold.CreateFontIndirect(&lf);
		CWindowDC dc(*this);
		auto oldFont = dc.SelectFont( bold );
		CSize size (0,0);
		CSize sizeSpace (0,0);


		dc.GetTextExtent(m_textDrawMe, m_textDrawMe.GetLength(), &size);
		dc.GetTextExtent(L" ", 1, &sizeSpace);

		dc.SelectFont( oldFont );

		return size.cx + sizeSpace.cx;
	}
	std::function< void (HWND) > TabCycleHandler;
	std::function< HBRUSH (CDCHandle) > CtlColorHandler;
	std::function< bool (HWND) > WantTabCheck;
	CWindow WndCtlColorTarget;

	// Rationale: sometimes you want a different text to be presented to accessibility APIs than actually drawn
	// For an example, a clear button looks best with a multiplication sign, but the narrator should say "clear" or so when focused
	void DrawAlternateText( const wchar_t * textDrawMe ) {
		m_textDrawMe = textDrawMe;
	}

protected:
	CFontHandle m_font;
	void OnSetFont(HFONT font, BOOL bRedraw) {
		m_font = font; if (bRedraw) Invalidate();
	}
	HFONT OnGetFont() {
		return m_font;
	}
	LRESULT OnGetDlgCode(UINT, WPARAM wp, LPARAM lp) {
		if ( wp == VK_TAB && TabCycleHandler != NULL) {
			if ( WantTabCheck == NULL || WantTabCheck(m_hWnd) ) {
				TabCycleHandler( m_hWnd );
				return DLGC_WANTTAB;
			}
		}
		SetMsgHandled(FALSE); return 0;
	}
	void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
	}
	void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
		switch(nChar) {
		case VK_SPACE:
		case VK_RETURN:
			TogglePressed(true); break;
		}
	}
	void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) {
		switch(nChar) {
		case VK_SPACE:
		case VK_RETURN:
			TogglePressed(false);
			OnClicked(); 
			break;
		}
	}
	void OnSetFocus(CWindow wndOld) {
		m_focused = true; Invalidate();
	}
	void OnKillFocus(CWindow wndFocus) {
		m_focused = false; Invalidate();
	}
	CFontHandle myGetFont() {
		auto f = GetFont();
		if ( f == NULL ) f = (HFONT) GetStockObject(DEFAULT_GUI_FONT);
		return f;
	}
	static void MakeBoldFont(LOGFONT & lf ) {
		lf.lfWeight += 300;
		if (lf.lfWeight > 1000 ) lf.lfWeight = 1000;
	}

	virtual void DrawBackground( CDCHandle dc, CRect rcClient ) {
		HBRUSH brush = NULL;
		if ( IsPressed() ) {
			CTheme theme;
			if (theme.OpenThemeData(*this, L"BUTTON" )) {
				DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_PRESSED, rcClient, rcClient );
			} else {
				DrawFrameControl( dc, rcClient, DFC_BUTTON, DFCS_PUSHED );
			}
		} else if (m_hot) {
			CTheme theme;
			if (theme.OpenThemeData(*this, L"BUTTON")) {
				DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_HOT, rcClient, rcClient);
			} else {
				DrawFrameControl(dc, rcClient, DFC_BUTTON, DFCS_HOT);
			}
		} else if ( CtlColorHandler ) {
			brush = CtlColorHandler( dc );
		} else {
			CWindow target = WndCtlColorTarget;
			if ( target == NULL ) target = GetParent();
			brush = (HBRUSH) target.SendMessage(WM_CTLCOLORBTN, (WPARAM) dc.m_hDC, (LPARAM) this->m_hWnd );
		}
		if ( brush != NULL ) {
			dc.FillRect( rcClient, brush );
			dc.SetBkMode( TRANSPARENT );
		}
	}

	virtual void OnPaint(CDCHandle) {
		CPaintDC pdc(*this);

		CRect rcClient;
		if (! GetClientRect( &rcClient ) ) return;

		auto font = myGetFont();
		/*
		CFont fontOverride;
		if ( IsPressed() ) {
			LOGFONT lf;
			font.GetLogFont( lf );
			MakeBoldFont( lf );
			fontOverride.CreateFontIndirect( & lf );
			font = fontOverride;
		}
		*/
		HFONT oldFont = pdc.SelectFont( font );

		DrawBackground( pdc.m_hDC, rcClient );

		pdc.SetBkMode( TRANSPARENT );
		if ( !IsWindowEnabled() ) {
			pdc.SetTextColor( ::GetSysColor(COLOR_GRAYTEXT) );
		} else if ( m_focused ) {
			pdc.SetTextColor( ::GetSysColor(COLOR_HIGHLIGHT) );
		}
		pdc.DrawText( m_textDrawMe, m_textDrawMe.GetLength(), &rcClient, DT_VCENTER | DT_CENTER | DT_SINGLELINE | DT_NOPREFIX );

		pdc.SelectFont( oldFont );
	}
	virtual void OnClicked() { 
		if ( ClickHandler ) {
			ClickHandler();
		} else {
			GetParent().PostMessage( WM_COMMAND, MAKEWPARAM( this->GetDlgCtrlID(), BN_CLICKED ), (LPARAM) m_hWnd );
		}
	}
	bool IsPressed() {return m_pressed; }
private:
	int OnCreate(LPCREATESTRUCT lpCreateStruct) {
		if ( lpCreateStruct->lpszName != nullptr ) this->m_textDrawMe = lpCreateStruct->lpszName;
		SetMsgHandled(FALSE); return 0;
	}
	void OnEnable(BOOL bEnable) {
		Invalidate(); SetMsgHandled(FALSE);
	}
	void ToggleHot( bool bHot ) {
		if ( bHot != m_hot ) {
			m_hot = bHot; Invalidate();
		}
	}
	void OnMouseMove(UINT nFlags, CPoint point) {
		const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
		if ((nFlags & maskButtons) != 0) return;
		CRect rcClient;
		if (!GetClientRect(rcClient)) return;
		if (!rcClient.PtInRect( point ) ) return;
		ToggleHot( true );
		SetCaptureEx([=](UINT cMsg, DWORD cFlags, CPoint cPoint) {
			CRect rcClient; 
			if (!GetClientRect(rcClient)) return false;
			if ( cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL || (cFlags & maskButtons) != 0 || !rcClient.PtInRect(cPoint) ) {
				ToggleHot(false);
				SetMsgHandled( FALSE );
				return false;
			}
			return true;
		} );
	}
	void OnLButtonDown(UINT nFlags, CPoint point) {
		const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
		if ( ( nFlags & maskButtons ) != MK_LBUTTON ) return;
		TogglePressed( true );
		SetCaptureEx([=] (UINT cMsg, DWORD cFlags, CPoint cPoint) {
			if (cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL) {
				TogglePressed(false);
				SetMsgHandled(FALSE);
				return false;
			}
			if ( cMsg == WM_LBUTTONUP ) {
				if ( m_pressed ) OnClicked();
				TogglePressed(false);
				return false;
			}
			CRect rcClient; 
			if (!GetClientRect( rcClient )) return false;
			if ( (cFlags & maskButtons) != (nFlags & maskButtons ) || ! rcClient.PtInRect( cPoint ) ) {
				TogglePressed(false);
				SetMsgHandled( FALSE ); return false;
			}
			return true;
		} );
	}
	void TogglePressed( bool bPressed ) {
		if ( bPressed != m_pressed ) {
			m_pressed = bPressed; Invalidate();
		}
	}
	int OnSetText(LPCTSTR lpstrText) {
		m_textDrawMe = lpstrText;
		Invalidate(); SetMsgHandled(FALSE);
		return 0;
	}
	typedef std::function< bool(UINT, DWORD, CPoint) > CaptureProc_t;
	void SetCaptureEx( CaptureProc_t proc ) {
		m_captureProc = proc; SetCapture();
	}
	LRESULT MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
		auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
		if (p) {
			CPoint pt(lp);
			if (!p(msg, (DWORD)wp, pt)) {
				::ReleaseCapture();
				m_captureProc = nullptr;
			}
			return 0;
		}

		SetMsgHandled(FALSE);
		return 0;
	}

	CaptureProc_t m_captureProc;

	bool m_pressed = false, m_focused = false, m_hot = false;
	CString m_textDrawMe;
};
