#include "stdafx.h"
#include "cef_control.h"
#include "include/cef_browser.h"
#include "include/cef_frame.h"
#include "include/cef_runnable.h"
#include "cef_control/handler/browser_handler.h"
#include "cef_control/manager/cef_manager.h"
#include "cef_control/app/cef_js_bridge.h"

namespace nim_comp {

namespace {
	#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))
	#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))
}

CefControl::CefControl(void)
{

}

CefControl::~CefControl(void)
{
	if (browser_handler_.get() && browser_handler_->GetBrowser().get())
	{
		// Request that the main browser close.
		browser_handler_->CloseAllBrowser();
		browser_handler_->SetHostWindow(NULL);
		browser_handler_->SetHandlerDelegate(NULL);	

		m_pWindow->RemoveMessageFilter(this);
	}
}

void CefControl::Init()
{
	if (browser_handler_.get() == nullptr)
	{
		m_pWindow->AddMessageFilter(this);
		
		browser_handler_ = new nim_comp::BrowserHandler;
		browser_handler_->SetHostWindow(m_pWindow->GetHWND());
		browser_handler_->SetHandlerDelegate(this);
		ReCreateBrowser();
	}

	if (!js_bridge_.get())
	{
		js_bridge_.reset(new nim_comp::CefJSBridge);
	}

	__super::Init();
}

void CefControl::ReCreateBrowser()
{
	if (browser_handler_->GetBrowser() == nullptr)
	{
		// 使用无窗模式，离屏渲染
		CefWindowInfo window_info;
		window_info.SetAsWindowless(m_pWindow->GetHWND(), false);
		CefBrowserSettings browser_settings;
		//browser_settings.file_access_from_file_urls = STATE_ENABLED;
		//browser_settings.universal_access_from_file_urls = STATE_ENABLED;
		CefBrowserHost::CreateBrowser(window_info, browser_handler_, L"", browser_settings, NULL);
	}	
}

void CefControl::SetPos(UiRect rc)
{
	__super::SetPos(rc);

	if (browser_handler_.get())
	{
		browser_handler_->SetViewRect(rc);
	}
}

void CefControl::HandleMessage(EventArgs& event)
{
	if (browser_handler_.get() && browser_handler_->GetBrowser().get() == NULL)
		return __super::HandleMessage(event);

	else if (event.Type == kEventInternalSetFocus)
	{
		if (browser_handler_->GetBrowserHost().get())
		{
			browser_handler_->GetBrowserHost()->SendFocusEvent(true);
		}
	}
	else if (event.Type == kEventInternalKillFocus)
	{
		if (browser_handler_->GetBrowserHost().get())
		{
			browser_handler_->GetBrowserHost()->SendFocusEvent(false);
		}
	}

	__super::HandleMessage(event);
}

void CefControl::SetVisible(bool bVisible /*= true*/)
{
	__super::SetVisible(bVisible);
	if (browser_handler_.get() && browser_handler_->GetBrowserHost().get())
	{
		browser_handler_->GetBrowserHost()->WasHidden(!bVisible);
	}
}

void CefControl::SetInternVisible(bool bVisible)
{
	__super::SetInternVisible(bVisible);
	if (browser_handler_.get() && browser_handler_->GetBrowserHost().get())
	{
		browser_handler_->GetBrowserHost()->WasHidden(!bVisible);
	}	
}

void CefControl::Paint(IRenderContext* pRender, const UiRect& rcPaint)
{
	__super::Paint(pRender, rcPaint);

	if (dc_cef_.IsValid() && browser_handler_.get() && browser_handler_->GetBrowser().get())
	{
		// 绘制cef PET_VIEW类型的位图
		BitBlt(pRender->GetDC(), m_rcItem.left, m_rcItem.top, m_rcItem.GetWidth(), m_rcItem.GetHeight(), dc_cef_.GetDC(), 0, 0, SRCCOPY);

		// 绘制cef PET_POPUP类型的位图
		if (!rect_popup_.IsEmpty() && dc_cef_popup_.IsValid())
		{
			// 假如popup窗口位置在控件的范围外，则修正到控件范围内，指绘制控件范围内的popup窗口
			int paint_x = rect_popup_.x;
			int paint_y = rect_popup_.y;
			int paint_buffer_x = 0;
			int paint_buffer_y = 0;
			if (rect_popup_.x < 0)
			{
				paint_x = 0;
				paint_buffer_x = -rect_popup_.x;
			}
			if (rect_popup_.y < 0)
			{
				paint_y = 0;
				paint_buffer_y = -rect_popup_.y;
			}

			BitBlt(pRender->GetDC(), m_rcItem.left + paint_x, m_rcItem.top + paint_y, rect_popup_.width, rect_popup_.height, dc_cef_popup_.GetDC(), paint_buffer_x, paint_buffer_y, SRCCOPY);
		}
	}
}

void CefControl::SetWindow(ui::Window* pManager, ui::Box* pParent, bool bInit)
{
	if (!browser_handler_)
	{
		__super::SetWindow(pManager, pParent, bInit);
		return;
	}

	if (m_pWindow)
	{
		m_pWindow->RemoveMessageFilter(this);
		__super::SetWindow(pManager, pParent, bInit);
		pManager->AddMessageFilter(this);
	}

	browser_handler_->SetHostWindow(pManager->GetHWND());
}

LRESULT CefControl::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	if (!IsVisible() || !IsEnabled())
	{
		bHandled = false;
		return 0;
	}

	bHandled = false;
	if (browser_handler_.get() == nullptr || browser_handler_->GetBrowser().get() == nullptr)
		return 0;

	switch (uMsg)
	{
	case WM_MOUSEMOVE:
	{
		return SendMouseMoveEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_SETCURSOR:
	{
		// 这里拦截WM_SETCURSOR消息，不让duilib处理（duilib会改变光标样式），否则会影响Cef中的鼠标光标
		POINT pt = { 0 };
		::GetCursorPos(&pt);
		::ScreenToClient(m_pWindow->GetHWND(), &pt);
		if (!m_rcItem.IsPointIn(pt))
			return 0;

		m_pWindow->CallWindowProc(uMsg, wParam, lParam);
		bHandled = true;
		return 0;
	}
	break;

	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
	{
		return SendButtonDownEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	{
		return SendButtonUpEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_SYSCHAR:
	case WM_SYSKEYDOWN:
	case WM_SYSKEYUP:
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_CHAR:
	{
		if (IsFocused())
		{
			return SendKeyEvent(uMsg, wParam, lParam, bHandled);
		}
	}
	break;

	case WM_LBUTTONDBLCLK:
	case WM_MBUTTONDBLCLK:
	case WM_RBUTTONDBLCLK:
	{
		return SendButtonDoubleDownEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_CAPTURECHANGED:
	case WM_CANCELMODE:
	{
		return SendCaptureLostEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_MOUSEWHEEL:
	{
		return SendMouseWheelEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	case WM_MOUSELEAVE:
	{
		return SendMouseLeaveEvent(uMsg, wParam, lParam, bHandled);
	}
	break;

	default:
		bHandled = false;
	}

	return 0;
}

bool CefControl::AttachDevTools(Control* control)
{
	CefControl *view = dynamic_cast<CefControl*>(control);
	if (devtool_attached_ || !view)
		return true;

	auto browser = browser_handler_->GetBrowser();
	auto view_browser = view->browser_handler_->GetBrowser();
	if (browser == nullptr || view_browser == nullptr)
	{
		auto weak = view->GetWeakFlag();
		auto task = [this, weak, view](){
			nbase::ThreadManager::PostTask(kThreadUI, ToWeakCallback([this, weak, view](){
				if (weak.expired())
					return;
				AttachDevTools(view);
			}));
		};
		view->browser_handler_->AddAfterCreateTask(task);
	}
	else
	{
		CefWindowInfo windowInfo;
		windowInfo.SetAsWindowless(GetWindow()->GetHWND(), false);
		CefBrowserSettings settings;
		browser->GetHost()->ShowDevTools(windowInfo, view_browser->GetHost()->GetClient(), settings, CefPoint());
		devtool_attached_ = true;
		if (cb_devtool_visible_change_ != nullptr)
			cb_devtool_visible_change_(devtool_attached_);
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////////////
LRESULT CefControl::SendButtonDownEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
	if (!m_rcItem.IsPointIn(pt))
		return 0;

	this->SetFocus();
	CefMouseEvent mouse_event;
	mouse_event.x = pt.x - m_rcItem.left;
	mouse_event.y = pt.y - m_rcItem.top;
	mouse_event.modifiers = GetCefMouseModifiers(wParam);

	CefBrowserHost::MouseButtonType btnType =
		(uMsg == WM_LBUTTONDOWN ? MBT_LEFT : (
		uMsg == WM_RBUTTONDOWN ? MBT_RIGHT : MBT_MIDDLE));

	host->SendMouseClickEvent(mouse_event, btnType, false, 1);

	bHandled = false;
	return 0;
}

LRESULT CefControl::SendButtonDoubleDownEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
	if (!m_rcItem.IsPointIn(pt))
		return 0;

	CefMouseEvent mouse_event;
	mouse_event.x = pt.x - m_rcItem.left;
	mouse_event.y = pt.y - m_rcItem.top;
	mouse_event.modifiers = GetCefMouseModifiers(wParam);

	CefBrowserHost::MouseButtonType btnType = MBT_LEFT;

	host->SendMouseClickEvent(mouse_event, btnType, false, 2);

	bHandled = true;
	return 0;
}

LRESULT CefControl::SendButtonUpEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

	if (!m_rcItem.IsPointIn(pt) && !m_pWindow->IsCaptured())
		return 0;

	CefMouseEvent mouse_event;
	if (uMsg == WM_RBUTTONUP)
	{
		mouse_event.x = pt.x/* - m_rcItem.left*/;	// 这里不进行坐标转换，否则右键菜单位置不正确
		mouse_event.y = pt.y/* - m_rcItem.top*/;
	}
	else
	{
		mouse_event.x = pt.x - m_rcItem.left;
		mouse_event.y = pt.y - m_rcItem.top;
	}

	mouse_event.modifiers = GetCefMouseModifiers(wParam);

	CefBrowserHost::MouseButtonType btnType =
		(uMsg == WM_LBUTTONUP ? MBT_LEFT : (
		uMsg == WM_RBUTTONUP ? MBT_RIGHT : MBT_MIDDLE));

	host->SendMouseClickEvent(mouse_event, btnType, true, 1);

	bHandled = false;
	return 0;
}

LRESULT CefControl::SendMouseMoveEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
	if (!m_rcItem.IsPointIn(pt) && !m_pWindow->IsCaptured())
		return 0;

	CefMouseEvent mouse_event;
	mouse_event.x = pt.x - m_rcItem.left;
	mouse_event.y = pt.y - m_rcItem.top;
	mouse_event.modifiers = GetCefMouseModifiers(wParam);
	host->SendMouseMoveEvent(mouse_event, false);

	bHandled = false;
	return 0;
}

LRESULT CefControl::SendMouseWheelEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
	HWND scrolled_wnd = ::WindowFromPoint(pt);
	if (scrolled_wnd != m_pWindow->GetHWND())
		return 0;

	ScreenToClient(m_pWindow->GetHWND(), &pt);
	if (!m_rcItem.IsPointIn(pt))
		return 0;

	int delta = GET_WHEEL_DELTA_WPARAM(wParam);

	CefMouseEvent mouse_event;
	mouse_event.x = pt.x - m_rcItem.left;
	mouse_event.y = pt.y - m_rcItem.top;
	mouse_event.modifiers = GetCefMouseModifiers(wParam);
	host->SendMouseWheelEvent(mouse_event, IsKeyDown(VK_SHIFT) ? delta : 0, !IsKeyDown(VK_SHIFT) ? delta : 0);

	bHandled = true;
	return 0;
}

LRESULT CefControl::SendMouseLeaveEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
	if (!m_rcItem.IsPointIn(pt))
		return 0;

	CefMouseEvent mouse_event;
	mouse_event.x = pt.x - m_rcItem.left;
	mouse_event.y = pt.y - m_rcItem.top;
	mouse_event.modifiers = GetCefMouseModifiers(wParam);

	host->SendMouseMoveEvent(mouse_event, true);

	bHandled = true;
	return 0;
}

LRESULT CefControl::SendKeyEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();
	
	CefKeyEvent event;
	event.windows_key_code = wParam;
	event.native_key_code = lParam;
	event.is_system_key = uMsg == WM_SYSCHAR || uMsg == WM_SYSKEYDOWN || uMsg == WM_SYSKEYUP;

	if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)
		event.type = KEYEVENT_RAWKEYDOWN;
	else if (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)
		event.type = KEYEVENT_KEYUP;
	else
		event.type = KEYEVENT_CHAR;
	event.modifiers = GetCefKeyboardModifiers(wParam, lParam);

	host->SendKeyEvent(event);

	bHandled = true;
	return 0;
}

LRESULT CefControl::SendCaptureLostEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CefRefPtr<CefBrowserHost> host = browser_handler_->GetBrowserHost();

	host->SendCaptureLostEvent();
	bHandled = true;
	return 0;
}

bool CefControl::IsKeyDown(WPARAM wparam) 
{
	return (GetKeyState(wparam) & 0x8000) != 0;
}

int CefControl::GetCefMouseModifiers(WPARAM wparam)
{
	int modifiers = 0;
	if (wparam & MK_CONTROL)
		modifiers |= EVENTFLAG_CONTROL_DOWN;
	if (wparam & MK_SHIFT)
		modifiers |= EVENTFLAG_SHIFT_DOWN;
	if (IsKeyDown(VK_MENU))
		modifiers |= EVENTFLAG_ALT_DOWN;
	if (wparam & MK_LBUTTON)
		modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON;
	if (wparam & MK_MBUTTON)
		modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON;
	if (wparam & MK_RBUTTON)
		modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON;

	// Low bit set from GetKeyState indicates "toggled".
	if (::GetKeyState(VK_NUMLOCK) & 1)
		modifiers |= EVENTFLAG_NUM_LOCK_ON;
	if (::GetKeyState(VK_CAPITAL) & 1)
		modifiers |= EVENTFLAG_CAPS_LOCK_ON;
	return modifiers;
}

int CefControl::GetCefKeyboardModifiers(WPARAM wparam, LPARAM lparam)
{
	int modifiers = 0;
	if (IsKeyDown(VK_SHIFT))
		modifiers |= EVENTFLAG_SHIFT_DOWN;
	if (IsKeyDown(VK_CONTROL))
		modifiers |= EVENTFLAG_CONTROL_DOWN;
	if (IsKeyDown(VK_MENU))
		modifiers |= EVENTFLAG_ALT_DOWN;

	// Low bit set from GetKeyState indicates "toggled".
	if (::GetKeyState(VK_NUMLOCK) & 1)
		modifiers |= EVENTFLAG_NUM_LOCK_ON;
	if (::GetKeyState(VK_CAPITAL) & 1)
		modifiers |= EVENTFLAG_CAPS_LOCK_ON;

	switch (wparam) {
	case VK_RETURN:
		if ((lparam >> 16) & KF_EXTENDED)
			modifiers |= EVENTFLAG_IS_KEY_PAD;
		break;
	case VK_INSERT:
	case VK_DELETE:
	case VK_HOME:
	case VK_END:
	case VK_PRIOR:
	case VK_NEXT:
	case VK_UP:
	case VK_DOWN:
	case VK_LEFT:
	case VK_RIGHT:
		if (!((lparam >> 16) & KF_EXTENDED))
			modifiers |= EVENTFLAG_IS_KEY_PAD;
		break;
	case VK_NUMLOCK:
	case VK_NUMPAD0:
	case VK_NUMPAD1:
	case VK_NUMPAD2:
	case VK_NUMPAD3:
	case VK_NUMPAD4:
	case VK_NUMPAD5:
	case VK_NUMPAD6:
	case VK_NUMPAD7:
	case VK_NUMPAD8:
	case VK_NUMPAD9:
	case VK_DIVIDE:
	case VK_MULTIPLY:
	case VK_SUBTRACT:
	case VK_ADD:
	case VK_DECIMAL:
	case VK_CLEAR:
		modifiers |= EVENTFLAG_IS_KEY_PAD;
		break;
	case VK_SHIFT:
		if (IsKeyDown(VK_LSHIFT))
			modifiers |= EVENTFLAG_IS_LEFT;
		else if (IsKeyDown(VK_RSHIFT))
			modifiers |= EVENTFLAG_IS_RIGHT;
		break;
	case VK_CONTROL:
		if (IsKeyDown(VK_LCONTROL))
			modifiers |= EVENTFLAG_IS_LEFT;
		else if (IsKeyDown(VK_RCONTROL))
			modifiers |= EVENTFLAG_IS_RIGHT;
		break;
	case VK_MENU:
		if (IsKeyDown(VK_LMENU))
			modifiers |= EVENTFLAG_IS_LEFT;
		else if (IsKeyDown(VK_RMENU))
			modifiers |= EVENTFLAG_IS_RIGHT;
		break;
	case VK_LWIN:
		modifiers |= EVENTFLAG_IS_LEFT;
		break;
	case VK_RWIN:
		modifiers |= EVENTFLAG_IS_RIGHT;
		break;
	}
	return modifiers;
}

//////////////////////////////////////////////////////////////////////////////////

}