#include "stdafx.h"
#include "etradeclient/browser/main_view_browser_handler.h"

#include <stdio.h>
#include <algorithm>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include "include/cef_browser.h"
#include "include/cef_frame.h"
#include "include/cef_path_util.h"
#include "include/cef_process_util.h"
#include "include/cef_runnable.h"
#include "include/cef_trace.h"
#include "include/base/cef_lock.h"
#include "include/wrapper/cef_helpers.h"

#include "etradeclient/browser/browser_util.h"
#include "etradeclient/browser/render_delegate.h"
#include "etradeclient/browser/session.h"
#include "etradeclient/browser/error_page.h"
#include "etradeclient/browser/async_js_callback_handler.h"
#include "etradeclient/utility/win_msg_define.h"
#include "etradeclient/utility/string_converter.h"

MainViewBrowserHandler::MainViewBrowserHandler()
{}

MainViewBrowserHandler::~MainViewBrowserHandler()
{}

//@{ CefClient methods
bool MainViewBrowserHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
	CEF_REQUIRE_UI_THREAD();
	if(m_browser_msg_router->OnProcessMessageReceived(browser, source_process, message)) 
		return true;
	return false;
}
//@}

//@{ CefLifeSpanHandler methods
void MainViewBrowserHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
	CEF_REQUIRE_UI_THREAD();

	if(!m_browser_msg_router)
	{
		// Create the browser-side router for query handling.
		CefMessageRouterConfig config;
		m_browser_msg_router = CefMessageRouterBrowserSide::Create(config);

		//Create other message handlers here.
		AsyncJSCallbackHandler::HW_Create(m_msg_handlers);

		// Register handlers with the router.
		for (const auto& handler : m_msg_handlers)
			m_browser_msg_router->AddHandler(handler, false);
	}

	auto is_popup = browser->IsPopup(); // @todo remove
	// get browser ID
	INT nBrowserId = browser->GetIdentifier();
	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle());

	// Send msg to View to hold reference to this browser
	::SendMessage(hWindow, WM_CEF_NEW_BROWSER, (WPARAM)nBrowserId, (LPARAM)browser.get());

	// call parent
	CefLifeSpanHandler::OnAfterCreated(browser);
}

bool MainViewBrowserHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
	CEF_REQUIRE_UI_THREAD();
	HWND hwnd = GetParent(browser->GetHost()->GetWindowHandle()); // The frame window will be the parent of the browser window
	::SendMessage(hwnd, WM_CEF_CLOSE_BROWSER, (WPARAM)browser.get(), (LPARAM)NULL); // send close browser notification.
	// Allow the close. For windowed browsers this will result in the OS close
	// event being sent.
	return false;
}

void MainViewBrowserHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
	CEF_REQUIRE_UI_THREAD();

	m_browser_msg_router->OnBeforeClose(browser);

	// All browser windows have closed.
	// Remove and delete message router handlers.
	for (const auto& handler : m_msg_handlers)
		m_browser_msg_router->RemoveHandler(handler);

	m_msg_handlers.clear();
	m_browser_msg_router = NULL;
	
	CefLifeSpanHandler::OnBeforeClose(browser); // call parent
}

bool MainViewBrowserHandler::OnBeforePopup(CefRefPtr<CefBrowser> browser,
									CefRefPtr<CefFrame> frame,
									const CefString& target_url,
									const CefString& target_frame_name,
									cef_window_open_disposition_t target_disposition,
									bool user_gesture,
									const CefPopupFeatures& popupFeatures,
									CefWindowInfo& windowInfo,
									CefRefPtr<CefClient>& client,
									CefBrowserSettings& settings,
									bool* no_javascript_access)
{
	CEF_REQUIRE_IO_THREAD();

	if(browser->GetHost()->IsWindowRenderingDisabled())
		return true; // Cancel popups in off-screen rendering mode.

	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent( browser->GetHost()->GetWindowHandle() );

	// send message
	LPCTSTR lpszURL(target_url.c_str());
	if( ::SendMessage( hWindow, WM_CEF_WINDOW_CHECK, (WPARAM)&popupFeatures, (LPARAM)lpszURL) == S_FALSE )
		return true;

	// send message
	if( ::SendMessage( hWindow, WM_CEF_NEW_WINDOW, (WPARAM)&popupFeatures, (LPARAM)&windowInfo) == S_FALSE )
		return true;
	
	// call parent, The |client| and |settings| values will default to the source browser's values.
	return CefLifeSpanHandler::OnBeforePopup(browser, frame, target_url, target_frame_name, target_disposition, user_gesture, popupFeatures, windowInfo, client, settings, no_javascript_access);
}
// @}

//@{ CefContextMenuHandler methods
void MainViewBrowserHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, CefRefPtr<CefMenuModel> model)
{
#ifndef _DEBUG
	// Block the context menu for safety consideration. !!!BUT we should still allow context menu for "selection" & "editable" items.
	if ((params->GetTypeFlags() & (CM_TYPEFLAG_SELECTION | CM_TYPEFLAG_EDITABLE)) == 0)
	{
		model->Clear();
		return;
	}
#endif
	// call parent to get the default behavior.
	CefContextMenuHandler::OnBeforeContextMenu(browser, frame, params, model);
}

bool MainViewBrowserHandler::OnContextMenuCommand(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, int command_id, EventFlags event_flags)
{
	// call parent to get the default behavior.
	return CefContextMenuHandler::OnContextMenuCommand(browser, frame, params, command_id, event_flags);
}
//@}

//@{ CefLoadHandler methods
void MainViewBrowserHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> browser, bool is_loading, bool can_go_back, bool can_go_forward)
{
	CEF_REQUIRE_UI_THREAD();

	INT state = 0;
	// set state
	if( is_loading )
		state |= CEF_BIT_IS_LOADING;
	if( can_go_back )
		state |= CEF_BIT_CAN_GO_BACK;
	if( can_go_forward )
		state |= CEF_BIT_CAN_GO_FORWARD;

	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle()); // The frame window will be the parent of the browser window.
	::SendMessage(hWindow, WM_CEF_STATE_CHANGE, (WPARAM)state, NULL); // send message.

	CefLoadHandler::OnLoadingStateChange(browser, is_loading, can_go_back, can_go_forward); // call parent.
}
//@}

//@{ CefDisplayHandler methods
void MainViewBrowserHandler::OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url)
{
	CEF_REQUIRE_UI_THREAD();

	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle()); // The frame window will be the parent of the browser window.
	LPCTSTR pszURL(url.c_str());
	::SendMessage( hWindow, WM_CEF_ADDRESS_CHANGE, (WPARAM)pszURL, NULL );
	
	CefDisplayHandler::OnAddressChange(browser, frame, url); // call parent.
}

void MainViewBrowserHandler::OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title)
{
	CEF_REQUIRE_UI_THREAD();

	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle()); // The frame window will be the parent of the browser window.
	LPCTSTR pszTitle(title.c_str());
	::SendMessage( hWindow, WM_CEF_TITLE_CHANGE, (WPARAM)pszTitle, NULL );

	CefDisplayHandler::OnTitleChange(browser, title); // call parent.
}

void MainViewBrowserHandler::OnStatusMessage(CefRefPtr<CefBrowser> browser, const CefString& value)
{
	CEF_REQUIRE_UI_THREAD();
#if _DEBUG
	SendStatusToMainWnd(browser, value);
#endif // _DEBUG
	CefDisplayHandler::OnStatusMessage(browser, value); // call parent.
}

bool MainViewBrowserHandler::OnConsoleMessage(CefRefPtr<CefBrowser> browser, const CefString& message, const CefString& source, int line)
{
	CEF_REQUIRE_UI_THREAD();
	return TRUE;
}
//@}

//@{ CefDownloadHandler methods
void MainViewBrowserHandler::OnBeforeDownload(CefRefPtr<CefBrowser> browser, CefRefPtr<CefDownloadItem> download_item, const CefString& suggested_name, CefRefPtr<CefBeforeDownloadCallback> callback)
{
	CEF_REQUIRE_UI_THREAD();

	// Continue the download and show the "Save As" dialog.
	callback->Continue(GetDownloadPath(suggested_name), true);
}

void MainViewBrowserHandler::OnDownloadUpdated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefDownloadItem> download_item, CefRefPtr<CefDownloadItemCallback> callback)
{
	CEF_REQUIRE_UI_THREAD();

	CEFDownloadItemValues values;

	values.bIsValid = download_item->IsValid();
	values.bIsInProgress = download_item->IsInProgress();
	values.bIsComplete = download_item->IsComplete();
	values.bIsCanceled = download_item->IsCanceled();
	values.nProgress = download_item->GetPercentComplete();
	values.nSpeed = download_item->GetCurrentSpeed();
	values.nReceived = download_item->GetReceivedBytes();
	values.nTotal = download_item->GetTotalBytes();

	CefString& szDispo = download_item->GetContentDisposition();

	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent( browser->GetHost()->GetWindowHandle() );

	// send message
	::SendMessage( hWindow, WM_CEF_DOWNLOAD_UPDATE, (WPARAM)&values, NULL );
}
//@}

//@{ CefLoadHandler methods
void MainViewBrowserHandler::OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame)
{
	CEF_REQUIRE_UI_THREAD();

	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent( browser->GetHost()->GetWindowHandle() );
	::SendMessage(hWindow, WM_CEF_LOAD_START, NULL, NULL);
	SendStatusToMainWnd(browser, L"页面加载中...");

	CefLoadHandler::OnLoadStart(browser, frame); // call parent
}

void MainViewBrowserHandler::OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int http_status_code)
{
	CEF_REQUIRE_UI_THREAD();
	static const int kHttpCode_OK = 200; // @TODO 统一定义这些值
	static const int kFileCode_OK = 0; // code of loading local file

	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent( browser->GetHost()->GetWindowHandle() );
	CefString url = frame->GetURL();
	LPCTSTR url_(url.c_str()); // WARNNING:LPCTSTR url_( frame->GetURL().c_str()) will get an incorrect string, don't do this way!
	::SendMessage(hWindow, WM_CEF_LOAD_END, http_status_code, (LPARAM)url_);

	if (kHttpCode_OK == http_status_code || kFileCode_OK == http_status_code)
		SendStatusToMainWnd(browser, L"页面加载完成!");
	else
		SendStatusToMainWnd(browser, L"页面加载出错!");
	
	CefLoadHandler::OnLoadEnd(browser, frame, http_status_code); // call parent
}

void MainViewBrowserHandler::OnLoadError(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, ErrorCode error_code, const CefString& error_text, const CefString& failed_url)
{
	CEF_REQUIRE_UI_THREAD();

	SendStatusToMainWnd(browser, L"页面加载结束!");

	// Don't display an error for if request aborted.
	if (error_code == ERR_ABORTED)
		return;

	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle());
	LPCTSTR failed_url_(failed_url.c_str());
	CefString error_code_str = ErrorPage::ToErrorString(error_code);
	::SendMessage(hWindow, WM_CEF_LOAD_ERROR, (WPARAM)failed_url_, (LPARAM)error_code_str.c_str());

	// Don't display an error for external protocols that we allow the OS to handle. See OnProtocolExecution().
	if (error_code == ERR_UNKNOWN_URL_SCHEME)
	{
		std::string urlStr = frame->GetURL();
		if (urlStr.find("spotify:") == 0)
			return;
	}
	frame->LoadURL(ErrorPage::Url(failed_url, error_code, error_text)); // Load the error page.
}
//@}


//@{ CefRequestHandler methods
CefRefPtr<CefResourceHandler> MainViewBrowserHandler::GetResourceHandler(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request)
{
	CEF_REQUIRE_IO_THREAD();
	return NULL;
}

bool MainViewBrowserHandler::GetAuthCredentials(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, CefRefPtr<CefAuthCallback> callback)
{
	// The frame window will be the parent of the browser window
	HWND hWindow = GetParent( browser->GetHost()->GetWindowHandle() );

	CEFAuthenticationValues values;
	values.lpszHost = host.c_str();
	values.lpszRealm = realm.c_str();

	// send info
	if(::SendMessage( hWindow, WM_CEF_AUTHENTICATE, (WPARAM)&values, (LPARAM)NULL ) == S_OK)
	{
		callback->Continue( values.szUserName, values.szUserPass );
		return TRUE;
	}
	// canceled
	return FALSE;
}

bool MainViewBrowserHandler::OnQuotaRequest(CefRefPtr<CefBrowser> browser, const CefString& origin_url, int64 new_size, CefRefPtr<CefRequestCallback> callback)
{
	static const int64 max_size = 1024 * 1024 * 20;  // 20mb.

	// Grant the quota request if the size is reasonable.
	callback->Continue(new_size <= max_size);

	// call parent
	return CefRequestHandler::OnQuotaRequest(browser, origin_url, new_size, callback);
}

void MainViewBrowserHandler::OnProtocolExecution(CefRefPtr<CefBrowser> browser, const CefString& url, bool& allow_os_execution)
{
	// do default
	CefRequestHandler::OnProtocolExecution(browser, url, allow_os_execution);
}

bool MainViewBrowserHandler::OnBeforeBrowse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request, bool is_redirect)
{
	CEF_REQUIRE_UI_THREAD();
	if (Session::Instance().IsExpired()) // Return true to cancel the navigation if the session is already expired.
		return true;

	m_browser_msg_router->OnBeforeBrowse(browser, frame);

	CefString url = request->GetURL(); // get URL requested
	LPCTSTR url_(url.c_str()); // WARNNING:LPCTSTR url_( frame->GetURL().c_str()) will get an incorrect string, don't do this way!
	HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle()); // The frame window will be the parent of the browser window。
	if (::SendMessage(hWindow, WM_CEF_BEFORE_BROWSE, (WPARAM)url_, (LPARAM)is_redirect) == S_FALSE)
	{
		// cancel navigation
		return true;
	}
	SendStatusToMainWnd(browser, L"开始加载页面...");
	return CefRequestHandler::OnBeforeBrowse(browser, frame, request, is_redirect); // call parent
}

bool MainViewBrowserHandler::OnCertificateError(CefRefPtr<CefBrowser> browser, cef_errorcode_t cert_error, const CefString& request_url, CefRefPtr<CefSSLInfo> ssl_info, CefRefPtr<CefRequestCallback> callback)
{
	std::wstringstream text;

	// no file, or empty, show the default
	text << "The site's security certificate is not trusted!\n\n";
	text << "You attempted to reach: " << request_url.c_str() << " \n";
	text << "But the server presented a certificate issued by an entity that is not trusted by your computer's operating system.";
	text << "This may mean that the server has generated its own security credentials, ";
	text << "which Chrome cannot rely on for identity information, or an attacker may be ";
	text << "trying to intercept your communications.\n\n";
	text << "You should not proceed, especially if you have never seen this warning before for this site.";

	if (MessageBox(NULL, text.str().c_str(), L"The site's security certificate is not trusted:", MB_YESNO) == IDNO)
		return FALSE;

	// continue
	callback->Continue(true);

	return TRUE;
}

CefRequestHandler::ReturnValue MainViewBrowserHandler::OnBeforeResourceLoad(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request, CefRefPtr<CefRequestCallback> callback)
{
	// do defulat
	return CefRequestHandler::OnBeforeResourceLoad(browser, frame, request, callback);
}

bool MainViewBrowserHandler::OnResourceResponse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request, CefRefPtr<CefResponse> response)
{
	CEF_REQUIRE_IO_THREAD();

	const CefString kTimeoutHeader("sessionStatus");
	const CefString kTimeout("0"); //超时为0,正常为1

	auto& url = request->GetURL();
	CefRequest::HeaderMap headerMap;
	response->GetHeaderMap(headerMap);


	if (0 == response->GetHeader(kTimeoutHeader).compare(kTimeout)) // If timeout.
	{
		if (!Session::Instance().IsExpired())// Avoid repeating post message to handle the session expired.
		{
			Session::Instance().OnExpired();
			// AfxGetMainWnd() will return NULL if called from an other thread (has to do with thread local storage).
			// That's why we use AfxGetApp()->GetMainWnd() instead of AfxGetMainWnd().
			PostMessage(AfxGetApp()->GetMainWnd()->GetSafeHwnd(), WM_CEF_SESSION_EXPIRED, NULL, NULL); // Post message to CMainFrame to try re-login.
		}
		
		if (RT_XHR != request->GetResourceType()) // RT_XHR means "XMLHttpRequest", don't redirect request if it is "XMLHttpRequest".
		{
			request->Set("about:blank", "GET", nullptr, CefRequest::HeaderMap()); // Redirect the URL to trigger another "OnBeforeBrowse".
			return true; // Return true to redirect.
		}
	}
	return false;
}

void MainViewBrowserHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser, TerminationStatus status)
{
	CEF_REQUIRE_UI_THREAD();

	m_browser_msg_router->OnRenderProcessTerminated(browser);

	return CefRequestHandler::OnRenderProcessTerminated(browser, status);
}
void MainViewBrowserHandler::OnPluginCrashed(CefRefPtr<CefBrowser> browser, const CefString& plugin_path)
{
	return CefRequestHandler::OnPluginCrashed(browser, plugin_path);
}
//@}

//@{ CefJSDialogHandler methods
bool MainViewBrowserHandler::OnBeforeUnloadDialog(CefRefPtr<CefBrowser> browser, const CefString& message_text, bool is_reload, CefRefPtr<CefJSDialogCallback> callback)
{
	// do defulat
	return FALSE;
}

void MainViewBrowserHandler::OnDialogClosed(CefRefPtr<CefBrowser> browser)
{
}

bool MainViewBrowserHandler::OnJSDialog(CefRefPtr<CefBrowser> browser, const CefString& origin_url, const CefString& accept_lang, CefJSDialogHandler::JSDialogType dialog_type, const CefString& message_text, const CefString& default_prompt_text, CefRefPtr<CefJSDialogCallback> callback, bool& suppress_message)
{
	// do default
	suppress_message = FALSE;
	return FALSE;
}

void MainViewBrowserHandler::OnResetDialogState(CefRefPtr<CefBrowser> browser)
{
}

//@}

std::string MainViewBrowserHandler::GetDownloadPath(const std::string& file_name)
{
	TCHAR szFolderPath[MAX_PATH];
	std::string path;

	// Save the file in the user's "My Documents" folder.
	if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE,
		NULL, 0, szFolderPath))) {
			path = CefString(szFolderPath);
			path += "\\" + file_name;
	}
	return path;
}

void MainViewBrowserHandler::SendStatusToMainWnd(CefRefPtr<CefBrowser> browser, const std::wstring& status)
{
	if (!status.empty())
	{
		// The frame window will be the parent of the browser window
		HWND hWindow = GetParent(browser->GetHost()->GetWindowHandle());
		LPCTSTR status_(status.c_str());
		::SendMessage(hWindow, WM_CEF_STATUS_MESSAGE, (WPARAM)status_, NULL);
	}
}