// Copyright 2015 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 "content/browser/devtools/protocol/emulation_handler.h"

#include "base/strings/string_number_conversions.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/geolocation/geolocation_service_context.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/common/url_constants.h"

namespace content {
namespace devtools {
namespace emulation {

using Response = DevToolsProtocolClient::Response;

namespace {

ui::GestureProviderConfigType TouchEmulationConfigurationToType(
    const std::string& protocol_value) {
  ui::GestureProviderConfigType result =
      ui::GestureProviderConfigType::CURRENT_PLATFORM;
  if (protocol_value ==
      set_touch_emulation_enabled::kConfigurationMobile) {
    result = ui::GestureProviderConfigType::GENERIC_MOBILE;
  }
  if (protocol_value ==
      set_touch_emulation_enabled::kConfigurationDesktop) {
    result = ui::GestureProviderConfigType::GENERIC_DESKTOP;
  }
  return result;
}

}  // namespace

EmulationHandler::EmulationHandler(page::PageHandler* page_handler)
    : touch_emulation_enabled_(false),
      device_emulation_enabled_(false),
      page_handler_(page_handler),
      host_(nullptr)
{
  page_handler->SetScreencastListener(this);
}

EmulationHandler::~EmulationHandler() {
}

void EmulationHandler::ScreencastEnabledChanged() {
   UpdateTouchEventEmulationState();
}

void EmulationHandler::SetRenderFrameHost(RenderFrameHostImpl* host) {
  if (host_ == host)
    return;

  host_ = host;
  UpdateTouchEventEmulationState();
  UpdateDeviceEmulationState();
}

void EmulationHandler::Detached() {
  touch_emulation_enabled_ = false;
  device_emulation_enabled_ = false;
  UpdateTouchEventEmulationState();
  UpdateDeviceEmulationState();
}

Response EmulationHandler::SetGeolocationOverride(
    double* latitude, double* longitude, double* accuracy) {
  if (!GetWebContents())
    return Response::InternalError("Could not connect to view");

  GeolocationServiceContext* geolocation_context =
      GetWebContents()->GetGeolocationServiceContext();
  scoped_ptr<Geoposition> geoposition(new Geoposition());
  if (latitude && longitude && accuracy) {
    geoposition->latitude = *latitude;
    geoposition->longitude = *longitude;
    geoposition->accuracy = *accuracy;
    geoposition->timestamp = base::Time::Now();
    if (!geoposition->Validate()) {
      return Response::InternalError("Invalid geolocation");
    }
  } else {
    geoposition->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
  }
  geolocation_context->SetOverride(geoposition.Pass());
  return Response::OK();
}

Response EmulationHandler::ClearGeolocationOverride() {
  if (!GetWebContents())
    return Response::InternalError("Could not connect to view");

  GeolocationServiceContext* geolocation_context =
      GetWebContents()->GetGeolocationServiceContext();
  geolocation_context->ClearOverride();
  return Response::OK();
}

Response EmulationHandler::SetTouchEmulationEnabled(
    bool enabled, const std::string* configuration) {
  touch_emulation_enabled_ = enabled;
  touch_emulation_configuration_ =
      configuration ? *configuration : std::string();
  UpdateTouchEventEmulationState();
  return Response::FallThrough();
}

Response EmulationHandler::CanEmulate(bool* result) {
#if defined(OS_ANDROID)
  *result = false;
#else
  *result = true;
  if (WebContentsImpl* web_contents = GetWebContents())
    *result &= !web_contents->GetVisibleURL().SchemeIs(kChromeDevToolsScheme);
  if (host_ && host_->GetRenderWidgetHost())
    *result &= !host_->GetRenderWidgetHost()->auto_resize_enabled();
#endif  // defined(OS_ANDROID)
  return Response::OK();
}

Response EmulationHandler::SetDeviceMetricsOverride(
    int width,
    int height,
    double device_scale_factor,
    bool mobile,
    bool fit_window,
    const double* optional_scale,
    const double* optional_offset_x,
    const double* optional_offset_y,
    const int* screen_width,
    const int* screen_height,
    const int* position_x,
    const int* position_y) {
  const static int max_size = 10000000;
  const static double max_scale = 10;

  if (!host_)
    return Response::InternalError("Could not connect to view");

  if (screen_width && screen_height &&
      (*screen_width < 0 || *screen_height < 0 ||
           *screen_width > max_size || *screen_height > max_size)) {
    return Response::InvalidParams(
        "Screen width and height values must be positive, not greater than " +
        base::IntToString(max_size));
  }

  if (screen_width && screen_height && position_x && position_y &&
      (*position_x < 0 || *position_y < 0 ||
           *position_x > *screen_width || *position_y > *screen_height)) {
    return Response::InvalidParams("View position should be on the screen");
  }

  if (width < 0 || height < 0 || width > max_size || height > max_size) {
    return Response::InvalidParams(
        "Width and height values must be positive, not greater than " +
        base::IntToString(max_size));
  }

  if (device_scale_factor < 0)
    return Response::InvalidParams("deviceScaleFactor must be non-negative");

  if (optional_scale && (*optional_scale <= 0 || *optional_scale > max_scale)) {
    return Response::InvalidParams(
        "scale must be positive, not greater than " +
        base::IntToString(max_scale));
  }

  blink::WebDeviceEmulationParams params;
  params.screenPosition = mobile ? blink::WebDeviceEmulationParams::Mobile :
      blink::WebDeviceEmulationParams::Desktop;
  if (screen_width && screen_height)
    params.screenSize = blink::WebSize(*screen_width, *screen_height);
  if (position_x && position_y)
    params.viewPosition = blink::WebPoint(*position_x, *position_y);
  params.deviceScaleFactor = device_scale_factor;
  params.viewSize = blink::WebSize(width, height);
  params.fitToView = fit_window;
  params.scale = optional_scale ? *optional_scale : 1;
  params.offset = blink::WebFloatPoint(
      optional_offset_x ? *optional_offset_x : 0.f,
      optional_offset_y ? *optional_offset_y : 0.f);

  if (device_emulation_enabled_ && params == device_emulation_params_)
    return Response::OK();

  device_emulation_enabled_ = true;
  device_emulation_params_ = params;
  UpdateDeviceEmulationState();
  return Response::OK();
}

Response EmulationHandler::ClearDeviceMetricsOverride() {
  if (!device_emulation_enabled_)
    return Response::OK();

  device_emulation_enabled_ = false;
  UpdateDeviceEmulationState();
  return Response::OK();
}

WebContentsImpl* EmulationHandler::GetWebContents() {
  return host_ ?
      static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_)) :
      nullptr;
}

void EmulationHandler::UpdateTouchEventEmulationState() {
  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (!widget_host)
    return;
  bool enabled = touch_emulation_enabled_ ||
      page_handler_->screencast_enabled();
  ui::GestureProviderConfigType config_type =
      TouchEmulationConfigurationToType(touch_emulation_configuration_);
  widget_host->SetTouchEventEmulationEnabled(enabled, config_type);
  if (GetWebContents())
    GetWebContents()->SetForceDisableOverscrollContent(enabled);
}

void EmulationHandler::UpdateDeviceEmulationState() {
  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (!widget_host)
    return;
  if (device_emulation_enabled_) {
    widget_host->Send(new ViewMsg_EnableDeviceEmulation(
        widget_host->GetRoutingID(), device_emulation_params_));
  } else {
    widget_host->Send(new ViewMsg_DisableDeviceEmulation(
        widget_host->GetRoutingID()));
  }
}

}  // namespace emulation
}  // namespace devtools
}  // namespace content
