// 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 "chrome/browser/automation/automation_provider.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/debug/trace_event.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/automation/automation_browser_tracker.h"
#include "chrome/browser/automation/automation_tab_tracker.h"
#include "chrome/browser/automation/automation_window_tracker.h"
#include "chrome/browser/external_tab/external_tab_container.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/automation_messages.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_zoom.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/focus/accelerator_handler.h"
#include "ui/views/widget/root_view.h"

using content::NavigationController;
using content::RenderViewHost;
using content::WebContents;

void AutomationProvider::CreateExternalTab(
    const ExternalTabSettings& settings,
    HWND* tab_container_window,
    HWND* tab_window,
    int* tab_handle,
    int* session_id) {
  TRACE_EVENT_BEGIN_ETW("AutomationProvider::CreateExternalTab", 0, "");

  *tab_handle = 0;
  *tab_container_window = NULL;
  *tab_window = NULL;
  *session_id = -1;
  scoped_refptr<ExternalTabContainer> external_tab_container =
      ExternalTabContainer::Create(this, automation_resource_message_filter_);

  Profile* profile = settings.is_incognito ?
      profile_->GetOffTheRecordProfile() : profile_;

  // When the ExternalTabContainer window is created we grab a reference on it
  // which is released when the window is destroyed.
  external_tab_container->Init(profile, settings.parent, settings.dimensions,
      settings.style, settings.load_requests_via_automation,
      settings.handle_top_level_requests, NULL, settings.initial_url,
      settings.referrer, settings.infobars_enabled,
      settings.route_all_top_level_navigations);

  if (AddExternalTab(external_tab_container)) {
    WebContents* web_contents = external_tab_container->GetWebContents();
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(web_contents);
    *tab_handle = external_tab_container->GetTabHandle();
    *tab_container_window = external_tab_container->GetExternalTabHWND();
    *tab_window = external_tab_container->GetContentHWND();
    *session_id = session_tab_helper->session_id().id();
  } else {
    external_tab_container->Uninitialize();
  }

  TRACE_EVENT_END_ETW("AutomationProvider::CreateExternalTab", 0, "");
}

bool AutomationProvider::AddExternalTab(ExternalTabContainer* external_tab) {
  DCHECK(external_tab != NULL);

  WebContents* web_contents = external_tab->GetWebContents();
  if (web_contents) {
    int tab_handle = tab_tracker_->Add(&web_contents->GetController());
    external_tab->SetTabHandle(tab_handle);
    return true;
  }

  return false;
}

void AutomationProvider::ProcessUnhandledAccelerator(
    const IPC::Message& message, int handle, const MSG& msg) {
  ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
  if (external_tab) {
    external_tab->ProcessUnhandledAccelerator(msg);
  }
  // This message expects no response.
}

void AutomationProvider::SetInitialFocus(const IPC::Message& message,
                                         int handle, bool reverse,
                                         bool restore_focus_to_view) {
  ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
  if (external_tab) {
    external_tab->FocusThroughTabTraversal(reverse, restore_focus_to_view);
  }
  // This message expects no response.
}

void AutomationProvider::PrintAsync(int tab_handle) {
  WebContents* web_contents = GetWebContentsForHandle(tab_handle, NULL);
  if (!web_contents)
    return;

  printing::PrintViewManager* print_view_manager =
      printing::PrintViewManager::FromWebContents(web_contents);
  print_view_manager->PrintNow();
}

ExternalTabContainer* AutomationProvider::GetExternalTabForHandle(int handle) {
  if (tab_tracker_->ContainsHandle(handle)) {
    NavigationController* tab = tab_tracker_->GetResource(handle);
    return ExternalTabContainer::GetContainerForTab(tab->GetWebContents());
  }

  return NULL;
}

void AutomationProvider::OnTabReposition(int tab_handle,
                                         const Reposition_Params& params) {
  if (!tab_tracker_->ContainsHandle(tab_handle))
    return;

  if (!IsWindow(params.window))
    return;

  unsigned long process_id = 0;
  unsigned long thread_id = 0;

  thread_id = GetWindowThreadProcessId(params.window, &process_id);

  if (thread_id != GetCurrentThreadId()) {
    DCHECK_EQ(thread_id, GetCurrentThreadId());
    return;
  }

  SetWindowPos(params.window, params.window_insert_after, params.left,
               params.top, params.width, params.height, params.flags);

  if (params.set_parent) {
    if (IsWindow(params.parent_window)) {
      if (!SetParent(params.window, params.parent_window))
        DLOG(WARNING) << "SetParent failed. Error 0x%x" << GetLastError();
    }
  }
}

void AutomationProvider::OnForwardContextMenuCommandToChrome(int tab_handle,
                                                             int command) {
  ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle);
  if (external_tab)
    external_tab->ExecuteContextMenuCommand(command);
}

void AutomationProvider::ConnectExternalTab(uint64 cookie,
                                            bool allow,
                                            HWND parent_window,
                                            HWND* tab_container_window,
                                            HWND* tab_window,
                                            int* tab_handle,
                                            int* session_id) {
  TRACE_EVENT_BEGIN_ETW("AutomationProvider::ConnectExternalTab", 0, "");

  *tab_handle = 0;
  *tab_container_window = NULL;
  *tab_window = NULL;
  *session_id = -1;

  scoped_refptr<ExternalTabContainer> external_tab_container =
      ExternalTabContainer::RemovePendingTab(static_cast<uintptr_t>(cookie));
  if (!external_tab_container.get()) {
    NOTREACHED();
    return;
  }

  if (allow && AddExternalTab(external_tab_container)) {
    external_tab_container->Reinitialize(this,
                                         automation_resource_message_filter_,
                                         parent_window);
    WebContents* web_contents = external_tab_container->GetWebContents();
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(web_contents);
    *tab_handle = external_tab_container->GetTabHandle();
    *tab_container_window = external_tab_container->GetExternalTabHWND();
    *tab_window = external_tab_container->GetContentHWND();
    *session_id = session_tab_helper->session_id().id();
  } else {
    external_tab_container->Uninitialize();
  }

  TRACE_EVENT_END_ETW("AutomationProvider::ConnectExternalTab", 0, "");
}

void AutomationProvider::OnBrowserMoved(int tab_handle) {
  ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle);
  if (!external_tab) {
    DLOG(WARNING) <<
      "AutomationProvider::OnBrowserMoved called with invalid tab handle.";
  }
}

void AutomationProvider::OnMessageFromExternalHost(int handle,
                                                   const std::string& message,
                                                   const std::string& origin,
                                                   const std::string& target) {
  RenderViewHost* view_host = GetViewForTab(handle);
  if (!view_host)
    return;

  view_host->Send(new ChromeViewMsg_HandleMessageFromExternalHost(
      view_host->GetRoutingID(), message, origin, target));
}

void AutomationProvider::NavigateInExternalTab(
    int handle, const GURL& url, const GURL& referrer,
    AutomationMsg_NavigationResponseValues* status) {
  *status = AUTOMATION_MSG_NAVIGATION_ERROR;

  if (tab_tracker_->ContainsHandle(handle)) {
    NavigationController* tab = tab_tracker_->GetResource(handle);
    tab->LoadURL(
        url,
        content::Referrer(referrer, blink::WebReferrerPolicyDefault),
        content::PAGE_TRANSITION_TYPED, std::string());
    *status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
  }
}

void AutomationProvider::NavigateExternalTabAtIndex(
    int handle, int navigation_index,
    AutomationMsg_NavigationResponseValues* status) {
  *status = AUTOMATION_MSG_NAVIGATION_ERROR;

  if (tab_tracker_->ContainsHandle(handle)) {
    NavigationController* tab = tab_tracker_->GetResource(handle);
    tab->GoToIndex(navigation_index);
    *status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
  }
}

void AutomationProvider::OnRunUnloadHandlers(
    int handle, IPC::Message* reply_message) {
  ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
  if (external_tab) {
    external_tab->RunUnloadHandlers(reply_message);
  }
}

void AutomationProvider::OnSetZoomLevel(int handle, int zoom_level) {
  if (tab_tracker_->ContainsHandle(handle)) {
    NavigationController* tab = tab_tracker_->GetResource(handle);
    if (tab->GetWebContents() && tab->GetWebContents()->GetRenderViewHost()) {
      RenderViewHost* host = tab->GetWebContents()->GetRenderViewHost();
      content::PageZoom zoom = static_cast<content::PageZoom>(zoom_level);
      host->Zoom(zoom);
    }
  }
}