// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "win8/metro_driver/stdafx.h"
#include "win8/metro_driver/chrome_app_view.h"

#include <corewindow.h>
#include <windows.applicationModel.datatransfer.h>
#include <windows.foundation.h>

#include <algorithm>

#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/win/metro.h"
// This include allows to send WM_SYSCOMMANDs to chrome.
#include "chrome/app/chrome_command_ids.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/metro_viewer/metro_viewer_messages.h"
#include "win8/metro_driver/metro_driver.h"
#include "win8/metro_driver/winrt_utils.h"

typedef winfoundtn::ITypedEventHandler<
    winapp::Core::CoreApplicationView*,
    winapp::Activation::IActivatedEventArgs*> ActivatedHandler;

typedef winfoundtn::ITypedEventHandler<
    winui::Core::CoreWindow*,
    winui::Core::WindowSizeChangedEventArgs*> SizeChangedHandler;

typedef winfoundtn::ITypedEventHandler<
    winui::Input::EdgeGesture*,
    winui::Input::EdgeGestureEventArgs*> EdgeEventHandler;

typedef winfoundtn::ITypedEventHandler<
    winapp::DataTransfer::DataTransferManager*,
    winapp::DataTransfer::DataRequestedEventArgs*> ShareDataRequestedHandler;

typedef winfoundtn::ITypedEventHandler<
    winui::ViewManagement::InputPane*,
    winui::ViewManagement::InputPaneVisibilityEventArgs*>
    InputPaneEventHandler;

typedef winfoundtn::ITypedEventHandler<
    winui::Core::CoreWindow*,
    winui::Core::PointerEventArgs*> PointerEventHandler;

typedef winfoundtn::ITypedEventHandler<
    winui::Core::CoreWindow*,
    winui::Core::KeyEventArgs*> KeyEventHandler;

struct Globals globals;

// TODO(ananta)
// Remove this once we consolidate metro driver with chrome.
const wchar_t kMetroGetCurrentTabInfoMessage[] =
    L"CHROME_METRO_GET_CURRENT_TAB_INFO";

static const int kFlipWindowsHotKeyId = 0x0000baba;

static const int kAnimateWindowTimeoutMs = 200;

static const int kCheckOSKDelayMs = 300;

const wchar_t kOSKClassName[] = L"IPTip_Main_Window";

static const int kOSKAdjustmentOffset = 20;

const wchar_t kChromeSubclassWindowProp[] = L"MetroChromeWindowProc";

namespace {

enum Modifier {
  NONE,
  SHIFT = 1,
  CONTROL = 2,
  ALT = 4
};

// Helper function to send keystrokes via the SendInput function.
// Params:
// mnemonic_char: The keystroke to be sent.
// modifiers: Combination with Alt, Ctrl, Shift, etc.
// extended: whether this is an extended key.
// unicode: whether this is a unicode key.
void SendMnemonic(WORD mnemonic_char, Modifier modifiers, bool extended,
                  bool unicode) {
  INPUT keys[4] = {0};  // Keyboard events
  int key_count = 0;  // Number of generated events

  if (modifiers & SHIFT) {
    keys[key_count].type = INPUT_KEYBOARD;
    keys[key_count].ki.wVk = VK_SHIFT;
    keys[key_count].ki.wScan = MapVirtualKey(VK_SHIFT, 0);
    key_count++;
  }

  if (modifiers & CONTROL) {
    keys[key_count].type = INPUT_KEYBOARD;
    keys[key_count].ki.wVk = VK_CONTROL;
    keys[key_count].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
    key_count++;
  }

  if (modifiers & ALT) {
    keys[key_count].type = INPUT_KEYBOARD;
    keys[key_count].ki.wVk = VK_MENU;
    keys[key_count].ki.wScan = MapVirtualKey(VK_MENU, 0);
    key_count++;
  }

  keys[key_count].type = INPUT_KEYBOARD;
  keys[key_count].ki.wVk = mnemonic_char;
  keys[key_count].ki.wScan = MapVirtualKey(mnemonic_char, 0);

  if (extended)
    keys[key_count].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
  if (unicode)
    keys[key_count].ki.dwFlags |= KEYEVENTF_UNICODE;
  key_count++;

  bool should_sleep = key_count > 1;

  // Send key downs
  for (int i = 0; i < key_count; i++) {
    SendInput(1, &keys[ i ], sizeof(keys[0]));
    keys[i].ki.dwFlags |= KEYEVENTF_KEYUP;
    if (should_sleep) {
      Sleep(10);
    }
  }

  // Now send key ups in reverse order
  for (int i = key_count; i; i--) {
    SendInput(1, &keys[ i - 1 ], sizeof(keys[0]));
    if (should_sleep) {
      Sleep(10);
    }
  }
}

// Helper function to Exit metro chrome cleanly. If we are in the foreground
// then we try and exit by sending an Alt+F4 key combination to the core
// window which ensures that the chrome application tile does not show up in
// the running metro apps list on the top left corner. We have seen cases
// where this does work. To workaround that we invoke the
// ICoreApplicationExit::Exit function in a background delayed task which
// ensures that chrome exits.
void MetroExit(bool send_alt_f4_mnemonic) {
  if (send_alt_f4_mnemonic && globals.view &&
      globals.view->core_window_hwnd() == ::GetForegroundWindow()) {
    DVLOG(1) << "We are in the foreground. Exiting via Alt F4";
    SendMnemonic(VK_F4, ALT, false, false);
    DWORD core_window_process_id = 0;
    DWORD core_window_thread_id = GetWindowThreadProcessId(
        globals.view->core_window_hwnd(), &core_window_process_id);
    if (core_window_thread_id != ::GetCurrentThreadId()) {
      globals.appview_task_runner->PostDelayedTask(
        FROM_HERE,
        base::Bind(&MetroExit, false),
        base::TimeDelta::FromMilliseconds(100));
    }
  } else {
    globals.app_exit->Exit();
  }
}

void AdjustToFitWindow(HWND hwnd, int flags) {
  RECT rect = {0};
  ::GetWindowRect(globals.view->core_window_hwnd() , &rect);
  int cx = rect.right - rect.left;
  int cy = rect.bottom - rect.top;

  ::SetWindowPos(hwnd, HWND_TOP,
                 rect.left, rect.top, cx, cy,
                 SWP_NOZORDER | flags);
}

LRESULT CALLBACK ChromeWindowProc(HWND window,
                                  UINT message,
                                  WPARAM wp,
                                  LPARAM lp) {
  if (message == WM_SETCURSOR) {
    // Override the WM_SETCURSOR message to avoid showing the resize cursor
    // as the mouse moves to the edge of the screen.
    switch (LOWORD(lp)) {
      case HTBOTTOM:
      case HTBOTTOMLEFT:
      case HTBOTTOMRIGHT:
      case HTLEFT:
      case HTRIGHT:
      case HTTOP:
      case HTTOPLEFT:
      case HTTOPRIGHT:
        lp = MAKELPARAM(HTCLIENT, HIWORD(lp));
        break;
      default:
        break;
    }
  }

  WNDPROC old_proc = reinterpret_cast<WNDPROC>(
      ::GetProp(window, kChromeSubclassWindowProp));
  DCHECK(old_proc);
  return CallWindowProc(old_proc, window, message, wp, lp);
}

void AdjustFrameWindowStyleForMetro(HWND hwnd) {
  DVLOG(1) << __FUNCTION__;
  // Ajust the frame so the live preview works and the frame buttons dissapear.
  ::SetWindowLong(hwnd, GWL_STYLE,
                  WS_POPUP | (::GetWindowLong(hwnd, GWL_STYLE) &
                  ~(WS_MAXIMIZE | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU)));
  ::SetWindowLong(hwnd, GWL_EXSTYLE,
                  ::GetWindowLong(hwnd, GWL_EXSTYLE) & ~(WS_EX_DLGMODALFRAME |
                  WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));

  // Subclass the wndproc of the frame window, if it's not already there.
  if (::GetProp(hwnd, kChromeSubclassWindowProp) == NULL) {
    WNDPROC old_chrome_proc =
        reinterpret_cast<WNDPROC>(::SetWindowLongPtr(
            hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ChromeWindowProc)));
    ::SetProp(hwnd, kChromeSubclassWindowProp, old_chrome_proc);
  }
  AdjustToFitWindow(hwnd, SWP_FRAMECHANGED | SWP_NOACTIVATE);
}

void SetFrameWindowInternal(HWND hwnd) {
  DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd);

  HWND current_top_frame =
      !globals.host_windows.empty() ? globals.host_windows.front().first : NULL;
  if (hwnd != current_top_frame && IsWindow(current_top_frame)) {
    DVLOG(1) << "Hiding current top window, hwnd="
               << LONG_PTR(current_top_frame);
    ::ShowWindow(current_top_frame, SW_HIDE);
  }

  // Visible frame windows always need to be at the head of the list.
  // Check if the window being shown already exists in our global list.
  // If no then add it at the head of the list.
  // If yes, retrieve the osk window scrolled state, remove the window from the
  // list and readd it at the head.
  std::list<std::pair<HWND, bool> >::iterator index =
      std::find_if(globals.host_windows.begin(), globals.host_windows.end(),
              [hwnd](std::pair<HWND, bool>& item) {
    return (item.first == hwnd);
  });

  bool window_scrolled_state = false;
  bool new_window = (index == globals.host_windows.end());
  if (!new_window) {
    window_scrolled_state = index->second;
    globals.host_windows.erase(index);
  }

  globals.host_windows.push_front(std::make_pair(hwnd, window_scrolled_state));

  if (new_window) {
    AdjustFrameWindowStyleForMetro(hwnd);
  } else {
    DVLOG(1) << "Adjusting new top window to core window size";
    AdjustToFitWindow(hwnd, 0);
  }
  if (globals.view->GetViewState() ==
      winui::ViewManagement::ApplicationViewState_Snapped) {
    DVLOG(1) << "Enabling Metro snap state on new window: " << hwnd;
    ::PostMessageW(hwnd, WM_SYSCOMMAND, IDC_METRO_SNAP_ENABLE, 0);
  }
}

void CloseFrameWindowInternal(HWND hwnd) {
  DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd);

  globals.host_windows.remove_if([hwnd](std::pair<HWND, bool>& item) {
    return (item.first == hwnd);
  });

  if (globals.host_windows.size() > 0) {
    DVLOG(1) << "Making new top frame window visible:"
            << reinterpret_cast<int>(globals.host_windows.front().first);
    AdjustToFitWindow(globals.host_windows.front().first, SWP_SHOWWINDOW);
  } else {
    // time to quit
    DVLOG(1) << "Last host window closed. Calling Exit().";
    MetroExit(true);
  }
}

void FlipFrameWindowsInternal() {
  DVLOG(1) << __FUNCTION__;
  // Get the first window in the frame windows queue and push it to the end.
  // Metroize the next window in the queue.
  if (globals.host_windows.size() > 1) {
    std::pair<HWND, bool> current_top_window = globals.host_windows.front();
    globals.host_windows.pop_front();

    DVLOG(1) << "Making new top frame window visible:"
            <<  reinterpret_cast<int>(globals.host_windows.front().first);

    AdjustToFitWindow(globals.host_windows.front().first, SWP_SHOWWINDOW);

    DVLOG(1) << "Hiding current top window:"
            << reinterpret_cast<int>(current_top_window.first);
    AnimateWindow(current_top_window.first, kAnimateWindowTimeoutMs,
                  AW_HIDE | AW_HOR_POSITIVE | AW_SLIDE);

    globals.host_windows.push_back(current_top_window);
  }
}

}  // namespace

void ChromeAppView::DisplayNotification(
    const ToastNotificationHandler::DesktopNotification& notification) {
  DVLOG(1) << __FUNCTION__;

  if (IsValidNotification(notification.id)) {
    NOTREACHED() << "Duplicate notification id passed in.";
    return;
  }

  base::AutoLock lock(notification_lock_);

  ToastNotificationHandler* notification_handler =
      new ToastNotificationHandler;

  notification_map_[notification.id].reset(notification_handler);
  notification_handler->DisplayNotification(notification);
}

void ChromeAppView::CancelNotification(const std::string& notification) {
  DVLOG(1) << __FUNCTION__;

  base::AutoLock lock(notification_lock_);

  NotificationMap::iterator index = notification_map_.find(notification);
  if (index == notification_map_.end()) {
    NOTREACHED() << "Invalid notification:" << notification.c_str();
    return;
  }

  scoped_ptr<ToastNotificationHandler> notification_handler(
      index->second.release());

  notification_map_.erase(index);

  notification_handler->CancelNotification();
}

// Returns true if the notification passed in is valid.
bool ChromeAppView::IsValidNotification(const std::string& notification) {
  DVLOG(1) << __FUNCTION__;

  base::AutoLock lock(notification_lock_);
  return notification_map_.find(notification) != notification_map_.end();
}

void ChromeAppView::ShowDialogBox(
    const MetroDialogBox::DialogBoxInfo& dialog_box_info) {
  VLOG(1) << __FUNCTION__;
  dialog_box_.Show(dialog_box_info);
}

void ChromeAppView::DismissDialogBox() {
  VLOG(1) << __FUNCTION__;
  dialog_box_.Dismiss();
}

// static
HRESULT ChromeAppView::Unsnap() {
  mswr::ComPtr<winui::ViewManagement::IApplicationViewStatics> view_statics;
  HRESULT hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_UI_ViewManagement_ApplicationView,
      view_statics.GetAddressOf());
  CheckHR(hr);

  winui::ViewManagement::ApplicationViewState state =
      winui::ViewManagement::ApplicationViewState_FullScreenLandscape;
  hr = view_statics->get_Value(&state);
  CheckHR(hr);

  if (state == winui::ViewManagement::ApplicationViewState_Snapped) {
    boolean success = FALSE;
    hr = view_statics->TryUnsnap(&success);

    if (FAILED(hr) || !success) {
      LOG(ERROR) << "Failed to unsnap. Error 0x" << hr;
      if (SUCCEEDED(hr))
        hr = E_UNEXPECTED;
    }
  }
  return hr;
}

void ChromeAppView::SetFullscreen(bool fullscreen) {
  VLOG(1) << __FUNCTION__;

  if (osk_offset_adjustment_) {
    VLOG(1) << "Scrolling the window down by: "
            << osk_offset_adjustment_;

    ::ScrollWindowEx(globals.host_windows.front().first,
                     0,
                     osk_offset_adjustment_,
                     NULL,
                     NULL,
                     NULL,
                     NULL,
                     SW_INVALIDATE | SW_SCROLLCHILDREN);
    osk_offset_adjustment_ = 0;
  }
}

winui::ViewManagement::ApplicationViewState ChromeAppView::GetViewState() {
  winui::ViewManagement::ApplicationViewState view_state =
      winui::ViewManagement::ApplicationViewState_FullScreenLandscape;
  app_view_->get_Value(&view_state);
  return view_state;
}

void UnsnapHelper() {
  ChromeAppView::Unsnap();
}

extern "C" __declspec(dllexport)
void MetroUnsnap() {
  DVLOG(1) << __FUNCTION__;
  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(&UnsnapHelper));
}

extern "C" __declspec(dllexport)
HWND GetRootWindow() {
  DVLOG(1) << __FUNCTION__;
  return globals.view->core_window_hwnd();
}

extern "C" __declspec(dllexport)
void SetFrameWindow(HWND hwnd) {
  DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd);
  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(&SetFrameWindowInternal, hwnd));
}

// TODO(ananta)
// Handle frame window close by deleting it from the window list and making the
// next guy visible.
extern "C" __declspec(dllexport)
  void CloseFrameWindow(HWND hwnd) {
  DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd);

  // This is a hack to ensure that the BrowserViewLayout code layout happens
  // just at the right time to hide the switcher button if it is visible.
  globals.appview_task_runner->PostDelayedTask(
    FROM_HERE, base::Bind(&CloseFrameWindowInternal, hwnd),
        base::TimeDelta::FromMilliseconds(50));
}

// Returns the initial url. This returns a valid url only if we were launched
// into metro via a url navigation.
extern "C" __declspec(dllexport)
const wchar_t* GetInitialUrl() {
  DVLOG(1) << __FUNCTION__;
  bool was_initial_activation = globals.is_initial_activation;
  globals.is_initial_activation = false;
  if (!was_initial_activation || globals.navigation_url.empty())
    return L"";

  const wchar_t* initial_url = globals.navigation_url.c_str();
  DVLOG(1) << initial_url;
  return initial_url;
}

// Returns the initial search string. This returns a valid url only if we were
// launched into metro via the search charm
extern "C" __declspec(dllexport)
const wchar_t* GetInitialSearchString() {
  DVLOG(1) << __FUNCTION__;
  bool was_initial_activation = globals.is_initial_activation;
  globals.is_initial_activation = false;
  if (!was_initial_activation || globals.search_string.empty())
    return L"";

  const wchar_t* initial_search_string = globals.search_string.c_str();
  DVLOG(1) << initial_search_string;
  return initial_search_string;
}

// Returns the launch type.
extern "C" __declspec(dllexport)
base::win::MetroLaunchType GetLaunchType(
    base::win::MetroPreviousExecutionState* previous_state) {
  if (previous_state) {
    *previous_state = static_cast<base::win::MetroPreviousExecutionState>(
        globals.previous_state);
  }
  return static_cast<base::win::MetroLaunchType>(
      globals.initial_activation_kind);
}

extern "C" __declspec(dllexport)
void FlipFrameWindows() {
  DVLOG(1) << __FUNCTION__;
  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(&FlipFrameWindowsInternal));
}

extern "C" __declspec(dllexport)
void DisplayNotification(const char* origin_url, const char* icon_url,
                         const wchar_t* title, const wchar_t* body,
                         const wchar_t* display_source,
                         const char* notification_id,
                         base::win::MetroNotificationClickedHandler handler,
                         const wchar_t* handler_context) {
  // TODO(ananta)
  // Needs implementation.
  DVLOG(1) << __FUNCTION__;

  ToastNotificationHandler::DesktopNotification notification(origin_url,
                                                             icon_url,
                                                             title,
                                                             body,
                                                             display_source,
                                                             notification_id,
                                                             handler,
                                                             handler_context);
  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(&ChromeAppView::DisplayNotification,
                            globals.view, notification));
}

extern "C" __declspec(dllexport)
bool CancelNotification(const char* notification_id) {
  // TODO(ananta)
  // Needs implementation.
  DVLOG(1) << __FUNCTION__;

  if (!globals.view->IsValidNotification(notification_id)) {
    NOTREACHED() << "Invalid notification id :" << notification_id;
    return false;
  }

  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(&ChromeAppView::CancelNotification,
                            globals.view, std::string(notification_id)));
  return true;
}

// Returns command line switches if any to be used by metro chrome.
extern "C" __declspec(dllexport)
const wchar_t* GetMetroCommandLineSwitches() {
  DVLOG(1) << __FUNCTION__;
  // The metro_command_line_switches field should be filled up once.
  // ideally in ChromeAppView::Activate.
  return globals.metro_command_line_switches.c_str();
}

// Provides functionality to display a metro style dialog box with two buttons.
// Only one dialog box can be displayed at any given time.
extern "C" __declspec(dllexport)
void ShowDialogBox(
    const wchar_t* title,
    const wchar_t* content,
    const wchar_t* button1_label,
    const wchar_t* button2_label,
    base::win::MetroDialogButtonPressedHandler button1_handler,
    base::win::MetroDialogButtonPressedHandler button2_handler) {
  VLOG(1) << __FUNCTION__;

  DCHECK(title);
  DCHECK(content);
  DCHECK(button1_label);
  DCHECK(button2_label);
  DCHECK(button1_handler);
  DCHECK(button2_handler);

  MetroDialogBox::DialogBoxInfo dialog_box_info;
  dialog_box_info.title = title;
  dialog_box_info.content = content;
  dialog_box_info.button1_label = button1_label;
  dialog_box_info.button2_label = button2_label;
  dialog_box_info.button1_handler = button1_handler;
  dialog_box_info.button2_handler = button2_handler;

  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(
          &ChromeAppView::ShowDialogBox, globals.view, dialog_box_info));
}

// Provides functionality to dismiss the previously displayed metro style
// dialog box.
extern "C" __declspec(dllexport)
void DismissDialogBox() {
  VLOG(1) << __FUNCTION__;

  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(
          &ChromeAppView::DismissDialogBox,
          globals.view));
}

extern "C" __declspec(dllexport)
void SetFullscreen(bool fullscreen) {
  VLOG(1) << __FUNCTION__;

  globals.appview_task_runner->PostTask(
      FROM_HERE, base::Bind(
          &ChromeAppView::SetFullscreen,
          globals.view, fullscreen));
}

template <typename ContainerT>
void CloseSecondaryWindows(ContainerT& windows) {
  DVLOG(1) << "Closing secondary windows", windows.size();
  std::for_each(windows.begin(), windows.end(), [](HWND hwnd) {
    ::PostMessageW(hwnd, WM_CLOSE, 0, 0);
  });
  windows.clear();
}

void EndChromeSession() {
  DVLOG(1) << "Sending chrome WM_ENDSESSION window message.";
  ::SendMessage(globals.host_windows.front().first, WM_ENDSESSION, FALSE,
                ENDSESSION_CLOSEAPP);
}

DWORD WINAPI HostMainThreadProc(void*) {
  // Test feature - devs have requested the ability to easily add metro-chrome
  // command line arguments. This is hard since shortcut arguments are ignored,
  // by Metro, so we instead read them directly from the pinned taskbar
  // shortcut. This may call Coinitialize and there is tell of badness
  // occurring if CoInitialize is called on a metro thread.
  globals.metro_command_line_switches =
      winrt_utils::ReadArgumentsFromPinnedTaskbarShortcut();

  globals.g_core_proc =
      reinterpret_cast<WNDPROC>(::SetWindowLongPtr(
          globals.view->core_window_hwnd(), GWLP_WNDPROC,
          reinterpret_cast<LONG_PTR>(ChromeAppView::CoreWindowProc)));
  DWORD exit_code = globals.host_main(globals.host_context);

  DVLOG(1) << "host thread done, exit_code=" << exit_code;
  MetroExit(true);
  return exit_code;
}

ChromeAppView::ChromeAppView()
    : osk_visible_notification_received_(false),
      osk_offset_adjustment_(0),
      core_window_hwnd_(NULL) {
  globals.previous_state =
      winapp::Activation::ApplicationExecutionState_NotRunning;
}

ChromeAppView::~ChromeAppView() {
  DVLOG(1) << __FUNCTION__;
}

IFACEMETHODIMP
ChromeAppView::Initialize(winapp::Core::ICoreApplicationView* view) {
  view_ = view;
  DVLOG(1) << __FUNCTION__;

  HRESULT hr = view_->add_Activated(mswr::Callback<ActivatedHandler>(
      this, &ChromeAppView::OnActivate).Get(),
      &activated_token_);
  CheckHR(hr);
  return hr;
}

IFACEMETHODIMP
ChromeAppView::SetWindow(winui::Core::ICoreWindow* window) {
  window_ = window;
  DVLOG(1) << __FUNCTION__;

  // Retrieve the native window handle via the interop layer.
  mswr::ComPtr<ICoreWindowInterop> interop;
  HRESULT hr = window->QueryInterface(interop.GetAddressOf());
  CheckHR(hr);
  hr = interop->get_WindowHandle(&core_window_hwnd_);
  CheckHR(hr);

  hr = url_launch_handler_.Initialize();
  CheckHR(hr, "Failed to initialize url launch handler.");
  // Register for size notifications.
  hr = window_->add_SizeChanged(mswr::Callback<SizeChangedHandler>(
      this, &ChromeAppView::OnSizeChanged).Get(),
      &sizechange_token_);
  CheckHR(hr);

  // Register for edge gesture notifications.
  mswr::ComPtr<winui::Input::IEdgeGestureStatics> edge_gesture_statics;
  hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_UI_Input_EdgeGesture,
      edge_gesture_statics.GetAddressOf());
  CheckHR(hr, "Failed to activate IEdgeGestureStatics.");

  mswr::ComPtr<winui::Input::IEdgeGesture> edge_gesture;
  hr = edge_gesture_statics->GetForCurrentView(&edge_gesture);
  CheckHR(hr);

  hr = edge_gesture->add_Completed(mswr::Callback<EdgeEventHandler>(
      this, &ChromeAppView::OnEdgeGestureCompleted).Get(),
      &edgeevent_token_);
  CheckHR(hr);

  // Register for share notifications.
  mswr::ComPtr<winapp::DataTransfer::IDataTransferManagerStatics>
      data_mgr_statics;
  hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager,
      data_mgr_statics.GetAddressOf());
  CheckHR(hr, "Failed to activate IDataTransferManagerStatics.");

  mswr::ComPtr<winapp::DataTransfer::IDataTransferManager> data_transfer_mgr;
  hr = data_mgr_statics->GetForCurrentView(&data_transfer_mgr);
  CheckHR(hr, "Failed to get IDataTransferManager for current view.");

  hr = data_transfer_mgr->add_DataRequested(
      mswr::Callback<ShareDataRequestedHandler>(
          this, &ChromeAppView::OnShareDataRequested).Get(),
      &share_data_requested_token_);
  CheckHR(hr);

  // TODO(ananta)
  // The documented InputPane notifications don't fire on Windows 8 in metro
  // chrome. Uncomment this once we figure out why they don't fire.
  // RegisterInputPaneNotifications();
  hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_UI_ViewManagement_ApplicationView,
      app_view_.GetAddressOf());
  CheckHR(hr);

  DVLOG(1) << "Created appview instance.";

  hr = devices_handler_.Initialize(window);
  // Don't check or return the failure here, we need to let the app
  // initialization succeed. Even if we won't be able to access devices
  // we still want to allow the app to start.
  LOG_IF(ERROR, FAILED(hr)) << "Failed to initialize devices handler.";
  return S_OK;
}

IFACEMETHODIMP
ChromeAppView::Load(HSTRING entryPoint) {
  DVLOG(1) << __FUNCTION__;
  return S_OK;
}

void RunMessageLoop(winui::Core::ICoreDispatcher* dispatcher) {
  // We're entering a nested message loop, let's allow dispatching
  // tasks while we're in there.
  base::MessageLoop::current()->SetNestableTasksAllowed(true);

  // Enter main core message loop. There are several ways to exit it
  // Nicely:
  // 1 - User action like ALT-F4.
  // 2 - Calling ICoreApplicationExit::Exit().
  // 3-  Posting WM_CLOSE to the core window.
  HRESULT hr = dispatcher->ProcessEvents(
      winui::Core::CoreProcessEventsOption
          ::CoreProcessEventsOption_ProcessUntilQuit);

  // Wind down the thread's chrome message loop.
  base::MessageLoop::current()->QuitWhenIdle();
}

void ChromeAppView::CheckForOSKActivation() {
  // Hack for checking if the OSK was displayed while we are in the foreground.
  // The input pane notifications which are supposed to fire when the OSK is
  // shown and hidden don't seem to be firing in Windows 8 metro for us.
  // The current hack is supposed to workaround that issue till we figure it
  // out. Logic is to find the OSK window and see if we are the foreground
  // process. If yes then fire the notification once for when the OSK is shown
  // and once for when it is hidden.
  // TODO(ananta)
  // Take this out when the documented input pane notification issues are
  // addressed.
  HWND osk = ::FindWindow(kOSKClassName, NULL);
  if (::IsWindow(osk)) {
    HWND foreground_window = ::GetForegroundWindow();
    if (globals.host_windows.size() > 0 &&
        foreground_window == globals.host_windows.front().first) {
      RECT osk_rect = {0};
      ::GetWindowRect(osk, &osk_rect);

      if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) {
        if (!globals.view->osk_visible_notification_received()) {
          DVLOG(1) << "Found KB window while we are in the forground.";
          HandleInputPaneVisible(osk_rect);
        }
      } else if (osk_visible_notification_received()) {
        DVLOG(1) << "KB window hidden while we are in the foreground.";
        HandleInputPaneHidden(osk_rect);
      }
    }
  }
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&ChromeAppView::CheckForOSKActivation, base::Unretained(this)),
      base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
}

IFACEMETHODIMP
ChromeAppView::Run() {
  DVLOG(1) << __FUNCTION__;
  mswr::ComPtr<winui::Core::ICoreDispatcher> dispatcher;
  HRESULT hr = window_->get_Dispatcher(&dispatcher);
  CheckHR(hr, "Dispatcher failed.");

  hr = window_->Activate();
  if (SUCCEEDED(hr)) {
    // TODO(cpu): Draw something here.
  } else {
    DVLOG(1) << "Activate failed, hr=" << hr;
  }

  // Create a message loop to allow message passing into this thread.
  base::MessageLoopForUI msg_loop;

  // Announce our message loop task runner to the world.
  globals.appview_task_runner = msg_loop.task_runner();

  // And post the task that'll do the inner Metro message pumping to it.
  msg_loop.PostTask(FROM_HERE, base::Bind(&RunMessageLoop, dispatcher.Get()));

  // Post the recurring task which checks for OSK activation in metro chrome.
  // Please refer to the comments in the CheckForOSKActivation function for why
  // this is needed.
  // TODO(ananta)
  // Take this out when the documented OSK notifications start working.
  msg_loop.PostDelayedTask(
      FROM_HERE,
      base::Bind(&ChromeAppView::CheckForOSKActivation,
                 base::Unretained(this)),
      base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));

  msg_loop.Run();

  globals.appview_task_runner = NULL;

  DVLOG(0) << "ProcessEvents done, hr=" << hr;

  // We join here with chrome's main thread so that the chrome is not killed
  // while a critical operation is still in progress. Now, if there are host
  // windows active it is possible we end up stuck on the wait below therefore
  // we tell chrome to close its windows.
  if (!globals.host_windows.empty()) {
    DVLOG(1) << "Chrome still has windows open!";
    EndChromeSession();
  }
  DWORD wr = ::WaitForSingleObject(globals.host_thread, INFINITE);
  if (wr != WAIT_OBJECT_0) {
    DVLOG(1) << "Waiting for host thread failed : " << wr;
  }

  DVLOG(1) << "Host thread exited";

  ::CloseHandle(globals.host_thread);
  globals.host_thread = NULL;
  return hr;
}

IFACEMETHODIMP
ChromeAppView::Uninitialize() {
  DVLOG(1) << __FUNCTION__;
  window_ = nullptr;
  view_ = nullptr;
  base::AutoLock lock(notification_lock_);
  notification_map_.clear();
  return S_OK;
}

HRESULT ChromeAppView::RegisterInputPaneNotifications() {
  DVLOG(1) << __FUNCTION__;

  mswr::ComPtr<winui::ViewManagement::IInputPaneStatics>
      input_pane_statics;
  HRESULT hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_UI_ViewManagement_InputPane,
      input_pane_statics.GetAddressOf());
  CheckHR(hr);

  hr = input_pane_statics->GetForCurrentView(&input_pane_);
  CheckHR(hr);
  DVLOG(1) << "Got input pane.";

  hr = input_pane_->add_Showing(
      mswr::Callback<InputPaneEventHandler>(
          this, &ChromeAppView::OnInputPaneVisible).Get(),
      &input_pane_visible_token_);
  CheckHR(hr);

  DVLOG(1) << "Added showing event handler for input pane",
      input_pane_visible_token_.value;

  hr = input_pane_->add_Hiding(
      mswr::Callback<InputPaneEventHandler>(
          this, &ChromeAppView::OnInputPaneHiding).Get(),
      &input_pane_hiding_token_);
  CheckHR(hr);

  DVLOG(1) << "Added hiding event handler for input pane, value="
          << input_pane_hiding_token_.value;
  return hr;
}

HRESULT ChromeAppView::OnActivate(winapp::Core::ICoreApplicationView*,
    winapp::Activation::IActivatedEventArgs* args) {
  DVLOG(1) << __FUNCTION__;

  args->get_PreviousExecutionState(&globals.previous_state);
  DVLOG(1) << "Previous Execution State: " << globals.previous_state;

  window_->Activate();
  url_launch_handler_.Activate(args);

  if (globals.previous_state ==
      winapp::Activation::ApplicationExecutionState_Running &&
      globals.host_thread) {
    DVLOG(1) << "Already running. Skipping rest of OnActivate.";
    return S_OK;
  }

  if (!globals.host_thread) {
    DWORD chrome_ui_thread_id = 0;
    globals.host_thread =
        ::CreateThread(NULL, 0, HostMainThreadProc, NULL, 0,
                      &chrome_ui_thread_id);

    if (!globals.host_thread) {
      NOTREACHED() << "thread creation failed.";
      return E_UNEXPECTED;
    }
  }

  if (RegisterHotKey(core_window_hwnd_, kFlipWindowsHotKeyId,
                     MOD_CONTROL, VK_F12)) {
    DVLOG(1) << "Registered flip window hotkey.";
  } else {
    VPLOG(1) << "Failed to register flip window hotkey.";
  }
  HRESULT hr = settings_handler_.Initialize();
  CheckHR(hr,"Failed to initialize settings handler.");
  return hr;
}

// We subclass the core window for moving the associated chrome window when the
// core window is moved around, typically in the snap view operation. The
// size changes are handled in the documented size changed event.
LRESULT CALLBACK ChromeAppView::CoreWindowProc(
    HWND window, UINT message, WPARAM wp, LPARAM lp) {

  static const UINT kBrowserClosingMessage =
      ::RegisterWindowMessage(L"DefaultBrowserClosing");

  if (message == WM_WINDOWPOSCHANGED) {
    WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lp);
    if (!(pos->flags & SWP_NOMOVE)) {
      DVLOG(1) << "WM_WINDOWPOSCHANGED. Moving the chrome window.";
      globals.view->OnPositionChanged(pos->x, pos->y);
    }
  } else if (message == WM_HOTKEY && wp == kFlipWindowsHotKeyId) {
    FlipFrameWindows();
  } else if (message == kBrowserClosingMessage) {
    DVLOG(1) << "Received DefaultBrowserClosing window message.";
    // Ensure that the view is uninitialized. The kBrowserClosingMessage
    // means that the app is going to be terminated, i.e. the proper
    // uninitialization sequence does not occur.
    globals.view->Uninitialize();
    if (!globals.host_windows.empty()) {
      EndChromeSession();
    }
  }
  return CallWindowProc(globals.g_core_proc, window, message, wp, lp);
}

HRESULT ChromeAppView::OnSizeChanged(winui::Core::ICoreWindow* sender,
    winui::Core::IWindowSizeChangedEventArgs* args) {
  if (!globals.host_windows.size()) {
    return S_OK;
  }

  winfoundtn::Size size;
  args->get_Size(&size);

  int cx = static_cast<int>(size.Width);
  int cy = static_cast<int>(size.Height);

  if (!::SetWindowPos(globals.host_windows.front().first, NULL, 0, 0, cx, cy,
                      SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED)) {
    DVLOG(1) << "SetWindowPos failed.";
  }
  DVLOG(1) << "size changed cx=" << cx;
  DVLOG(1) << "size changed cy=" << cy;

  winui::ViewManagement::ApplicationViewState view_state =
      winui::ViewManagement::ApplicationViewState_FullScreenLandscape;
  app_view_->get_Value(&view_state);

  HWND top_level_frame = globals.host_windows.front().first;
  if (view_state == winui::ViewManagement::ApplicationViewState_Snapped) {
    DVLOG(1) << "Enabling metro snap mode.";
    ::PostMessageW(top_level_frame, WM_SYSCOMMAND, IDC_METRO_SNAP_ENABLE, 0);
  } else {
    ::PostMessageW(top_level_frame, WM_SYSCOMMAND, IDC_METRO_SNAP_DISABLE, 0);
  }
  return S_OK;
}

HRESULT ChromeAppView::OnPositionChanged(int x, int y) {
  DVLOG(1) << __FUNCTION__;

  ::SetWindowPos(globals.host_windows.front().first, NULL, x, y, 0, 0,
                 SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOSIZE);
  return S_OK;
}

HRESULT ChromeAppView::OnEdgeGestureCompleted(
    winui::Input::IEdgeGesture* gesture,
    winui::Input::IEdgeGestureEventArgs* args) {
  DVLOG(1) << "edge gesture completed.";

  winui::ViewManagement::ApplicationViewState view_state =
      winui::ViewManagement::ApplicationViewState_FullScreenLandscape;
  app_view_->get_Value(&view_state);
  // We don't want fullscreen chrome unless we are fullscreen metro.
  if ((view_state == winui::ViewManagement::ApplicationViewState_Filled) ||
      (view_state == winui::ViewManagement::ApplicationViewState_Snapped)) {
    DVLOG(1) << "No full screen in snapped view state:" << view_state;
    return S_OK;
  }

  // Deactivate anything pending, e.g., the wrench or a context menu.
  BOOL success = ::ReleaseCapture();
  DCHECK(success) << "Couldn't ReleaseCapture() before going full screen";

  DVLOG(1) << "Going full screen.";
  ::PostMessageW(globals.host_windows.front().first, WM_SYSCOMMAND,
                 IDC_FULLSCREEN, 0);
  return S_OK;
}

HRESULT ChromeAppView::OnShareDataRequested(
  winapp::DataTransfer::IDataTransferManager* data_transfer_mgr,
  winapp::DataTransfer::IDataRequestedEventArgs* event_args) {

  DVLOG(1) << "Share data requested.";

  // The current tab info is retrieved from Chrome via a registered window
  // message.

  static const UINT get_current_tab_info =
      RegisterWindowMessage(kMetroGetCurrentTabInfoMessage);

  static const int kGetTabInfoTimeoutMs = 1000;

  mswr::ComPtr<winapp::DataTransfer::IDataRequest> data_request;
  HRESULT hr = event_args->get_Request(&data_request);
  CheckHR(hr);

  mswr::ComPtr<winapp::DataTransfer::IDataPackage> data_package;
  hr = data_request->get_Data(&data_package);
  CheckHR(hr);

  base::win::CurrentTabInfo current_tab_info;
  current_tab_info.title = NULL;
  current_tab_info.url = NULL;

  DWORD_PTR result = 0;

  if (!SendMessageTimeout(globals.host_windows.front().first,
                          get_current_tab_info,
                          reinterpret_cast<WPARAM>(&current_tab_info),
                          0,
                          SMTO_ABORTIFHUNG,
                          kGetTabInfoTimeoutMs,
                          &result)) {
    VPLOG(1) << "Failed to retrieve tab info from chrome.";
    return E_FAIL;
  }

  if (!current_tab_info.title || !current_tab_info.url) {
    DVLOG(1) << "Failed to retrieve tab info from chrome.";
    return E_FAIL;
  }

  base::string16 current_title(current_tab_info.title);
  base::string16 current_url(current_tab_info.url);

  LocalFree(current_tab_info.title);
  LocalFree(current_tab_info.url);

  mswr::ComPtr<winapp::DataTransfer::IDataPackagePropertySet> data_properties;
  hr = data_package->get_Properties(&data_properties);

  mswrw::HString title;
  title.Attach(MakeHString(current_title));
  data_properties->put_Title(title.Get());

  mswr::ComPtr<winfoundtn::IUriRuntimeClassFactory> uri_factory;
  hr = winrt_utils::CreateActivationFactory(
      RuntimeClass_Windows_Foundation_Uri,
      uri_factory.GetAddressOf());
  CheckHR(hr);

  mswrw::HString url;
  url.Attach(MakeHString(current_url));
  mswr::ComPtr<winfoundtn::IUriRuntimeClass> uri;
  hr = uri_factory->CreateUri(url.Get(), &uri);
  CheckHR(hr);

  hr = data_package->SetUri(uri.Get());
  CheckHR(hr);

  return S_OK;
}

void ChromeAppView::HandleInputPaneVisible(const RECT& osk_rect) {
  DCHECK(!osk_visible_notification_received_);

  DVLOG(1) << __FUNCTION__;
  DVLOG(1) << "OSK width:" << osk_rect.right - osk_rect.left;
  DVLOG(1) << "OSK height:" << osk_rect.bottom - osk_rect.top;

  globals.host_windows.front().second = false;

  POINT cursor_pos = {0};
  GetCursorPos(&cursor_pos);

  osk_offset_adjustment_ = 0;

  if (::PtInRect(&osk_rect, cursor_pos)) {
    DVLOG(1) << "OSK covering focus point.";
    int osk_height = osk_rect.bottom - osk_rect.top;

    osk_offset_adjustment_ = osk_height + kOSKAdjustmentOffset;

    DVLOG(1) << "Scrolling window by offset: " << osk_offset_adjustment_;
    ::ScrollWindowEx(globals.host_windows.front().first,
                     0,
                     -osk_offset_adjustment_,
                     NULL,
                     NULL,
                     NULL,
                     NULL,
                     SW_INVALIDATE | SW_SCROLLCHILDREN);

    globals.host_windows.front().second  = true;
  }
  osk_visible_notification_received_ = true;
}

void ChromeAppView::HandleInputPaneHidden(const RECT& osk_rect) {
  DCHECK(osk_visible_notification_received_);
  DVLOG(1) << __FUNCTION__;
  DVLOG(1) << "OSK width:" << osk_rect.right - osk_rect.left;
  DVLOG(1) << "OSK height:" << osk_rect.bottom - osk_rect.top;
  osk_visible_notification_received_ = false;
  if (globals.host_windows.front().second == true) {

    if (osk_offset_adjustment_) {
      DVLOG(1) << "Restoring scrolled window offset: "
               << osk_offset_adjustment_;

      ::ScrollWindowEx(globals.host_windows.front().first,
                       0,
                       osk_offset_adjustment_,
                       NULL,
                       NULL,
                       NULL,
                       NULL,
                       SW_INVALIDATE | SW_SCROLLCHILDREN);
    }

    globals.host_windows.front().second = false;
  }
}

HRESULT ChromeAppView::OnInputPaneVisible(
    winui::ViewManagement::IInputPane* input_pane,
    winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args) {
  DVLOG(1) << __FUNCTION__;
  return S_OK;
}

HRESULT ChromeAppView::OnInputPaneHiding(
    winui::ViewManagement::IInputPane* input_pane,
    winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args) {
  DVLOG(1) << __FUNCTION__;
  return S_OK;
}

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

ChromeAppViewFactory::ChromeAppViewFactory(
    winapp::Core::ICoreApplication* icore_app,
    LPTHREAD_START_ROUTINE host_main,
    void* host_context) {
  globals.host_main = host_main;
  globals.host_context = host_context;
  mswr::ComPtr<winapp::Core::ICoreApplication> core_app(icore_app);
  mswr::ComPtr<winapp::Core::ICoreApplicationExit> app_exit;
  CheckHR(core_app.As(&app_exit));
  globals.app_exit = app_exit.Detach();
}

IFACEMETHODIMP
ChromeAppViewFactory::CreateView(winapp::Core::IFrameworkView** view) {
  globals.view = mswr::Make<ChromeAppView>().Detach();
  *view = globals.view;
  return (*view) ? S_OK :  E_OUTOFMEMORY;
}
