#include <siadrivehandler.h>
#include <include/wrapper/cef_helpers.h>
#include <include/views/cef_browser_view.h>
#include <include/cef_app.h>
#include <include/base/cef_bind.h>
#include <include/wrapper/cef_closure_task.h>
#include <filepath.h>
#include <siadriveconfig.h>

using namespace Sia;
using namespace Sia::Api;

CSiaDriveHandler* g_instance = nullptr;

#ifdef _WIN32
#define WM_SIA_TRAY_ICON (WM_USER+2048)
#define MENU_ITEM_SHOW_HIDE 0x00010000
#define MENU_ITEM_EXIT 0x00010001

static HMENU g_trayMenu = nullptr;
static WNDPROC g_defProc;
static NOTIFYICONDATA g_notifyIcon = { 0 };
static bool g_trayClose = false;
static UINT g_trayCreatedId = 0;
static bool g_formingContracts = false;
static LRESULT CALLBACK SiaWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam);

static void CreateSystrayIcon(const HWND& hwnd)
{
  HINSTANCE hinstance = ::GetModuleHandle(nullptr);
  HICON icon = ::LoadIcon(hinstance, L"IDI_ICON");

  ::SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon));
  ::SendMessage(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(icon));
  ::SendMessage(GetWindow(hwnd, GW_OWNER), WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon));
  ::SendMessage(GetWindow(hwnd, GW_OWNER), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(icon));

  g_notifyIcon.cbSize = sizeof(g_notifyIcon);
  g_notifyIcon.hWnd = hwnd;
  g_notifyIcon.hIcon = icon;
  g_notifyIcon.uCallbackMessage = WM_SIA_TRAY_ICON;
  GUID g = { 0 };
  ::CoCreateGuid(&g);
  g_notifyIcon.guidItem = g;
  g_notifyIcon.uVersion = NOTIFYICON_VERSION_4;
  g_notifyIcon.uFlags = NIF_ICON | NIF_GUID | NIF_MESSAGE;
  ::Shell_NotifyIcon(NIM_ADD, &g_notifyIcon);
}

static void ToggleWindowVisible(const HWND& wnd)
{
  ::ShowWindow(wnd, ::IsWindowVisible(wnd) ? SW_HIDE : SW_RESTORE);
  if (::IsWindowVisible(wnd))
  {
    ::SetForegroundWindow(wnd);
  }

}

static LRESULT CALLBACK SiaWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
  case WM_GETMINMAXINFO: 
    {
      MINMAXINFO *pMinMax = reinterpret_cast<MINMAXINFO*>(lparam);
      pMinMax->ptMaxTrackSize.x = pMinMax->ptMinTrackSize.x = 800;
      pMinMax->ptMaxTrackSize.y = pMinMax->ptMinTrackSize.y = 690;
    }
    break;

  case WM_SIA_TRAY_ICON:
  {
    switch (lparam)
    {
    case WM_LBUTTONDBLCLK:
    {
      ToggleWindowVisible(wnd);
    }
    break;

    case WM_RBUTTONUP:
    {
      // Create menu
      if (g_trayMenu)
      {
        ::DestroyMenu(g_trayMenu);
        g_trayMenu = nullptr;
      }
      g_trayMenu = ::CreatePopupMenu();

      // Add menu items
      ::InsertMenu(g_trayMenu, 0, MF_BYPOSITION | MF_STRING | (::IsWindowVisible(wnd) ? MFS_CHECKED : MFS_UNCHECKED), MENU_ITEM_SHOW_HIDE, (::IsWindowVisible(wnd) ? L"Hide" : L"Show"));

      {
        MENUITEMINFO menuItem = { 0 };
        menuItem.cbSize = sizeof(menuItem);
        menuItem.fMask = MIIM_TYPE;
        menuItem.fType = MFT_SEPARATOR;
        ::InsertMenuItem(g_trayMenu, 1, TRUE, &menuItem);
      }

      ::InsertMenu(g_trayMenu, 2, MF_BYPOSITION | MF_STRING, MENU_ITEM_EXIT, L"Exit");

      // Display menu
      POINT pt = { 0 };
      ::GetCursorPos(&pt);
      ::SetForegroundWindow(wnd);
      ::TrackPopupMenu(g_trayMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, wnd, nullptr);
      ::PostMessage(wnd, WM_NULL, 0, 0);
    }
    break;
    }
  }
  break;

  case WM_COMMAND:
    {
      switch (wparam)
      {
      case MENU_ITEM_SHOW_HIDE:
        {
          ToggleWindowVisible(wnd);
        }
        break;

      case MENU_ITEM_EXIT:
        {
          if (!::IsWindowVisible(wnd))
          {
            ToggleWindowVisible(wnd);
          }
          g_trayClose = true;
          ::PostMessage(wnd, WM_CLOSE, 0, 0);
        }
        break;
      }
    }
    break;

  default:
    if (g_trayCreatedId && (g_trayCreatedId == msg))
    {
      CreateSystrayIcon(wnd);
    }
    break;
  }

  return ::CallWindowProc(g_defProc, wnd, msg, wparam, lparam);
}
#endif

CSiaDriveHandler::CSiaDriveHandler( ) :
	_isClosing(false),
  _active(true),
  _siaDriveConfig(new CSiaDriveConfig(false)),
  _closeConfirmed(false),
  _waitingConfirmation(false),
  _allowClose(false)
{
	DCHECK(!g_instance);
	g_instance = this;
}

CSiaDriveHandler::~CSiaDriveHandler() 
{
	g_instance = nullptr;
}

CSiaDriveHandler* CSiaDriveHandler::GetInstance() 
{
	return g_instance;
}

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

	PlatformTitleChange(browser, title);
}

bool CSiaDriveHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
  if (message->GetName() == "shutdownServicesComplete")
  {
    _active = false;
    browser->GetHost()->CloseBrowser(false);
    return true;
  }
  else if (message->GetName() == "closeToTray")
  {
#ifdef _WIN32
    ::ShowWindow(browser->GetHost()->GetWindowHandle(), SW_HIDE);
#else
    a
#endif
  }
  else if (message->GetName() == "settingsUpdated")
  {
    json configDoc = json::parse(message->GetArgumentList()->GetString(0).ToString().c_str());
    _siaDriveConfig.reset(new CSiaDriveConfig(configDoc));
    return true;
  }
  else if (message->GetName() == "notifyAllowClose")
  {
    _allowClose = true;
    return true;
  }
  else if (message->GetName() == "setFormingContracts")
  {
    g_formingContracts = message->GetArgumentList()->GetBool(0);
  }

  return false;
}

void CSiaDriveHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) 
{
	CEF_REQUIRE_UI_THREAD();
#ifdef _WIN32
  HWND hwnd = browser->GetHost()->GetWindowHandle();
  auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX;
  ::SetWindowLongPtr(hwnd, GWL_STYLE, style);

  g_defProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtr(hwnd, GWLP_WNDPROC));
  ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(SiaWndProc));

  g_trayCreatedId = ::RegisterWindowMessage(TEXT("TaskbarCreated"));

  CreateSystrayIcon(hwnd);
#else
  a
#endif

	_browserList.push_back(browser);
}

bool CSiaDriveHandler::DoClose(CefRefPtr<CefBrowser> browser) 
{
	CEF_REQUIRE_UI_THREAD();

  bool preventClose = true;
  if (_allowClose)
  {
    if (_active)
    {
      CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("handleCloseRequest");
      msg->GetArgumentList()->SetBool(0, g_trayClose);
      browser->SendProcessMessage(PID_RENDERER, msg);
      g_trayClose = false;
    }
    else
    {
      preventClose = false;
    }
  }

  return preventClose;
}

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

	BrowserList::iterator bit = _browserList.begin();
	for (; bit != _browserList.end(); ++bit) 
	{
		if ((*bit)->IsSame(browser)) 
		{
			_browserList.erase(bit);
			break;
		}
	}

	if (_browserList.size() == 0) 
	{
    ::Shell_NotifyIcon(NIM_DELETE, &g_notifyIcon);
    if (g_trayMenu)
    {
      ::DestroyMenu(g_trayMenu);
      g_trayMenu = nullptr;
    }
    CefQuitMessageLoop();
	}
}

void CSiaDriveHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	ErrorCode errorCode,
	const CefString& errorText,
	const CefString& failedUrl) 
{
	CEF_REQUIRE_UI_THREAD();

  if (errorCode != ERR_ABORTED)
  {
    std::stringstream ss;
    ss << "<html><body bgcolor=\"white\">"
      "<h2>Failed to load URL " << std::string(failedUrl) <<
      " with error " << std::string(errorText) << " (" << errorCode <<
      ").</h2></body></html>";
    frame->LoadString(ss.str(), failedUrl);
  }
}

void CSiaDriveHandler::CloseAllBrowsers(bool forceClose) 
{
	if (CefCurrentlyOn(TID_UI))
	{
    BrowserList::const_iterator it = _browserList.begin();
    for (; it != _browserList.end(); ++it)
      (*it)->GetHost()->CloseBrowser(forceClose);
	}
  else if (!_browserList.empty())
  {
		CefPostTask(TID_UI, base::Bind(&CSiaDriveHandler::CloseAllBrowsers, this, forceClose));
  }
}

#ifdef _WIN32
void CSiaDriveHandler::PlatformTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title)
{
	CefWindowHandle hwnd = browser->GetHost()->GetWindowHandle();
	SetWindowText(hwnd, std::wstring(title).c_str());
}
#endif