/*
-----------------------------------------------------------------------------
This source file is part of OpenSpace3D
For the latest info, see http://www.openspace3d.com

Copyright (c) 2011 I-maginer

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt
-----------------------------------------------------------------------------
*/

#include "embeddedWebNavigator.h"
#include "embeddedWebNavigatorThread.h"

namespace Scol
{
  namespace EmbeddedWebNavigator
  {

WebNavigator::WebNavigator(const unsigned long navigatorUniqueId, const ScolWindowHandle& scolMainWindow, const ScolWindowHandle& parentWindowHandleInstance, int xPosition, int yPosition, int width, int height, std::string url) : uniqueId(navigatorUniqueId),
																																																									  scolMainWindowHandle(scolMainWindow),
																																																									  parentWindowHandle(parentWindowHandleInstance),
																																																									  offscreen(false)
{
  CommonConstructorSequence(std::pair<int, int>(xPosition, yPosition), std::pair<int, int>(width, height), url);
}

WebNavigator::WebNavigator(const unsigned long navigatorUniqueId, const ScolWindowHandle& scolMainWindow, const ScolWindowHandle& parentWindowHandleInstance, int width, int height, std::string url, bool transparency) : uniqueId(navigatorUniqueId),
																																																						   scolMainWindowHandle(scolMainWindow),
																																																						   parentWindowHandle(parentWindowHandleInstance),
																																																						   offscreen(true)
{
  CommonConstructorSequence(std::pair<int, int>(0, 0), std::pair<int, int>(width, height), url, transparency);
}

void WebNavigator::CommonConstructorSequence(std::pair<int, int> newWebNavigatorPosition, std::pair<int, int> newWebNavigatorSize, std::string url, bool transparency)
{
  // Init member variables
  actualPosition = newWebNavigatorPosition;
  actualSize = newWebNavigatorSize;
  inputEnable = true;

  // Offscreen render critical section
  offscreenBufferCriticalSection = 0;
  if(offscreen)
    offscreenBufferCriticalSection = new WebNavigatorCriticalSection();

  // Create the CEF client instance
  webNavigatorClient = new WebNavigatorClient(this, scolMainWindowHandle, offscreen, offscreenBufferCriticalSection);

  // CEF browser init structures
  CefWindowInfo windowInfo;
  CefBrowserSettings settings;

  // Setting up windows hierarchy
  if(offscreen)
  {
    // Set as off-screen rendering window.
    windowInfo.SetAsOffScreen(parentWindowHandle);
  }
  else
  {
    // Set as child browser window
    RECT clientArea;
    GetClientRect(parentWindowHandle, &clientArea);
    windowInfo.SetAsChild(parentWindowHandle, clientArea);
  }

  // Enable transparency
  windowInfo.SetTransparentPainting(transparency);

  // Create synchronisation objects
  synchroEventCanGoBack = CreateEvent(NULL, TRUE, FALSE, NULL);
  synchroEventCanGoForward = CreateEvent(NULL, TRUE, FALSE, NULL);
  synchroEventGetZoomLevel = CreateEvent(NULL, TRUE, FALSE, NULL);
  synchroEventGetSource = CreateEvent(NULL, TRUE, FALSE, NULL);
  synchroEventGetURL = CreateEvent(NULL, TRUE, FALSE, NULL);
  synchroEventGetText = CreateEvent(NULL, TRUE, FALSE, NULL);

  // Create the browser. Will call WebNavigatorClient::OnAfterCreated
  CefBrowser::CreateBrowser(windowInfo, static_cast<CefRefPtr<CefClient> >(webNavigatorClient), url, settings);
}

WebNavigator::WebNavigator() : uniqueId(0),
                               scolMainWindowHandle(0),
                               parentWindowHandle(0),
                               offscreen(false)
{
  // Forbiden constructor
}

WebNavigator::~WebNavigator()
{
  // Delete CEF client instance
  webNavigatorClient->_Cleanup();
  webNavigatorClient = 0;

  // Delete synchronisation objects
  CloseHandle(synchroEventCanGoBack);
  CloseHandle(synchroEventCanGoForward);
  CloseHandle(synchroEventGetZoomLevel);
  CloseHandle(synchroEventGetSource);
  CloseHandle(synchroEventGetURL);
  CloseHandle(synchroEventGetText);
}

unsigned long WebNavigator::GetUniqueId()
{
  return uniqueId;
}

CefRefPtr<WebNavigatorClient>& WebNavigator::GetWebNavigatorClient()
{
  return webNavigatorClient;
}

ScolWindowHandle WebNavigator::GetParentWindowHandle()
{
  return parentWindowHandle;
}

void WebNavigator::LoadURL(const std::string& url, const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::LoadURL, uniqueId, url, frameName));
}

void WebNavigator::LoadFile(const std::string& file, const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::LoadFile, uniqueId, file, frameName));
}

void WebNavigator::LoadHTML(const std::string& html, const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::LoadFile, uniqueId, html, frameName));
}

bool WebNavigator::CanGoBack()
{
  ResetEvent(synchroEventCanGoBack);
  bool canGoBack = false;
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::CanGoBack, uniqueId, &canGoBack, synchroEventCanGoBack));
  WaitForSingleObject(synchroEventCanGoBack, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return canGoBack;
}

void WebNavigator::GoBack()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GoBack, uniqueId));
}

bool WebNavigator::CanGoForward()
{
  ResetEvent(synchroEventCanGoForward);
  bool canGoForward = false;
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::CanGoForward, uniqueId, &canGoForward, synchroEventCanGoForward));
  WaitForSingleObject(synchroEventCanGoForward, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return canGoForward;
}

void WebNavigator::GoForward()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GoForward, uniqueId));
}

void WebNavigator::Reload()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Reload, uniqueId));
}

void WebNavigator::ReloadIgnoreCache()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::ReloadIgnoreCache, uniqueId));
}

void WebNavigator::StopLoad()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::StopLoad, uniqueId));
}

void WebNavigator::Find(int identifier, const CefString& searchText, bool forward, bool matchCase, bool findNext)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Find, uniqueId, identifier, searchText, forward, matchCase, findNext));
}

void WebNavigator::StopFinding(bool clearSelection)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::StopFinding, uniqueId, clearSelection));
}

double WebNavigator::GetZoomLevel()
{
  ResetEvent(synchroEventGetZoomLevel);
  double zoomFactor = 0.0;
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GetZoomLevel, uniqueId, &zoomFactor, synchroEventGetZoomLevel));
  WaitForSingleObject(synchroEventGetZoomLevel, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return zoomFactor;
}

void WebNavigator::SetZoomLevel(double zoomLevel)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::SetZoomLevel, uniqueId, zoomLevel));
}

void WebNavigator::ClearHistory()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::ClearHistory, uniqueId));
}

void WebNavigator::ShowDevTools()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::ShowDevTools, uniqueId));
}

void WebNavigator::CloseDevTools()
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::CloseDevTools, uniqueId));
}

std::pair<int, int> WebNavigator::GetSize()
{
  return actualSize;
}

void WebNavigator::SetSize(std::pair<int, int> newSize)
{
  actualSize = newSize;
  ApplyPositionAndSize();
}

std::pair<int, int> WebNavigator::GetPosition()
{
  return actualPosition;
}

void WebNavigator::SetPosition(std::pair<int, int> newPosition)
{
  actualPosition = newPosition;
  ApplyPositionAndSize();
}

void WebNavigator::ApplyPositionAndSize()
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
  {
    if(!offscreen)
    {
      CefWindowHandle cefBrowserWindowHandle = cefBrowser->GetWindowHandle();
      if(cefBrowserWindowHandle)
      {
        RECT clientArea;
        GetClientRect(parentWindowHandle, &clientArea);
        SetWindowPos(cefBrowserWindowHandle, 0,
                     clientArea.left + actualPosition.first, clientArea.top + actualPosition.second, 
			               actualSize.first, actualSize.second, SWP_NOZORDER);
      }
    }
    else
    {
      // Set the view size to match the render size.
      cefBrowser->SetSize(PET_VIEW, actualSize.first, actualSize.second);
    }
  }
}

std::string WebNavigator::GetURL(const std::string& frameName)
{
  ResetEvent(synchroEventGetURL);
  std::string url = "";
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GetURL, uniqueId, &url, synchroEventGetURL, frameName));
  WaitForSingleObject(synchroEventGetURL, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return url;
}

void WebNavigator::Undo(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Undo, uniqueId, frameName));
}

void WebNavigator::Redo(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Redo, uniqueId, frameName));
}

void WebNavigator::Cut(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Cut, uniqueId, frameName));
}

void WebNavigator::Copy(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Copy, uniqueId, frameName));
}

void WebNavigator::Paste(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Paste, uniqueId, frameName));
}

void WebNavigator::Delete(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Delete, uniqueId, frameName));
}

void WebNavigator::SelectAll(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::SelectAll, uniqueId, frameName));
}

void WebNavigator::Print(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::Print, uniqueId, frameName));
}

void WebNavigator::ViewSource(const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::ViewSource, uniqueId, frameName));
}

std::string WebNavigator::GetSource(const std::string& frameName)
{
  ResetEvent(synchroEventGetSource);
  std::string sourceText = "";
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GetSource, uniqueId, &sourceText, synchroEventGetSource, frameName));
  WaitForSingleObject(synchroEventGetSource, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return sourceText;
}

std::string WebNavigator::GetText(const std::string& frameName)
{
  ResetEvent(synchroEventGetText);
  std::string text = "";
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::GetText, uniqueId, &text, synchroEventGetText, frameName));
  WaitForSingleObject(synchroEventGetText, WEB_NAVIGATOR_SYNCHRO_DEFAULT_WAITING_TIME);
  return text;
}

void WebNavigator::ExecuteJavaScript(const std::string& jsCode,  const std::string& scriptUrl, int startLine, const std::string& frameName)
{
  CefPostTask(TID_UI, NewCefRunnableFunction(&WebNavigatorThread::ExecuteJavaScript, uniqueId, jsCode, scriptUrl, startLine, frameName));
}

bool WebNavigator::IsOffscreen() const
{
  return offscreen;
}

void WebNavigator::InjectKeyMsg(int msg, int lparam, int wparam)
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
  {
    CefBrowser::KeyType kmode = KT_KEYUP;
    bool syskey = false;
    bool imekey = false;

    if ((msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN))
      kmode = KT_KEYDOWN;
    else if ((msg == WM_KEYUP) || (msg == WM_SYSKEYUP))
      kmode = KT_KEYUP;
    else if (msg == WM_CHAR)
      kmode = KT_CHAR;
    
    if ((msg == WM_SYSKEYDOWN) || (msg == WM_SYSKEYUP))
      syskey = true;

    if ((msg == WM_IME_KEYDOWN) || (msg == WM_IME_KEYUP))
      imekey = true;

    cefBrowser->SendKeyEvent(kmode, wparam, lparam, syskey, imekey);
  }
}

void WebNavigator::InjectKey(int scanCode, int keydata, bool down)
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
    if(down)
      cefBrowser->SendKeyEvent(KT_KEYDOWN, keydata, MAKELONG(1, scanCode), false, false);
    else
      cefBrowser->SendKeyEvent(KT_KEYUP, MapVirtualKey(scanCode, 1), MAKELONG(1, scanCode+0xC000), false, false);
}

void WebNavigator::InjectChar(int scanCode, int keydata)
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
    cefBrowser->SendKeyEvent(KT_CHAR, keydata, MAKELONG(1, scanCode), false, false);
}

void WebNavigator::InjectMouseClick(int x, int y, int buttonType, bool mouseUp, int clickCount)
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
    cefBrowser->SendMouseClickEvent(x, y, ConvertMouseButton(buttonType), mouseUp, clickCount);
}

void WebNavigator::InjectMouseMove(int x, int y, bool mouseLeave)
{
  if(inputEnable)
  {
    CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
    if(cefBrowser.get())
    {
      cefBrowser->SendMouseMoveEvent(x, y, mouseLeave);
      cefBrowser->Invalidate(CefRect(x, y, 1, 1));
      inputEnable = false;
    }
  }
}

void WebNavigator::InjectMouseWheel(int x, int y, int delta)
{    
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
    cefBrowser->SendMouseWheelEvent(x, y, delta * 100);
}

void WebNavigator::InjectFocus(bool setFocus)
{
  CefRefPtr<CefBrowser> cefBrowser = webNavigatorClient->GetBrowser();
  if(cefBrowser.get())
    cefBrowser->SendFocusEvent(setFocus);
}

CefBrowser::MouseButtonType WebNavigator::ConvertMouseButton(int scolButton)
{
  // Cef button type values are 0/1/2 and scol are 1/16/2
  switch(scolButton)
  {
  case 1:
    return MBT_LEFT;
    break;
  case 16:
    return MBT_MIDDLE;
    break;
  case 2:
    return MBT_RIGHT;
    break;
  default:
    return MBT_LEFT;
  }
}

void WebNavigator::ResetInput()
{
  inputEnable = true;
}

  }
}