// Copyright (c) 2006-2008 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 "config.h"

#pragma warning(push, 0)
#include "FloatRect.h"
#include "FileChooser.h"
#include "FrameLoadRequest.h"
#include "FrameView.h"
#include "HitTestResult.h"
#include "IntRect.h"
#include "Page.h"
#include "ScriptController.h"
#include "WindowFeatures.h"
#pragma warning(pop)
#undef LOG

#include "webkit/glue/chrome_client_impl.h"

#include "base/gfx/rect.h"
#include "googleurl/src/gurl.h"
#include "webkit/glue/glue_util.h"
#include "webkit/glue/webframe_impl.h"
#include "webkit/glue/webinputevent.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/glue/webview_delegate.h"
#include "webkit/glue/webview_impl.h"

struct IWebURLResponse;

// Callback class that's given to the WebViewDelegate during a file choose
// operation.
class WebFileChooserCallbackImpl : public WebFileChooserCallback {
 public:
  WebFileChooserCallbackImpl(PassRefPtr<WebCore::FileChooser> file_chooser)
      : file_chooser_(file_chooser) {
  }

  void OnFileChoose(const std::wstring& file_name) {
    file_chooser_->chooseFile(webkit_glue::StdWStringToString(file_name));
  }

 private:
  RefPtr<WebCore::FileChooser> file_chooser_;
  DISALLOW_EVIL_CONSTRUCTORS(WebFileChooserCallbackImpl);
};

ChromeClientImpl::ChromeClientImpl(WebViewImpl* webview)
    : webview_(webview),
      toolbars_visible_(true),
      statusbar_visible_(true),
      scrollbars_visible_(true),
      menubar_visible_(true),
      resizable_(true) {
}

ChromeClientImpl::~ChromeClientImpl() {
}

void ChromeClientImpl::chromeDestroyed() {
  delete this;
}

void ChromeClientImpl::setWindowRect(const WebCore::FloatRect& r) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    WebCore::IntRect ir(r);
    d->SetWindowRect(webview_,
                     gfx::Rect(ir.x(), ir.y(), ir.width(), ir.height()));
  }
}

WebCore::FloatRect ChromeClientImpl::windowRect() {
  if (webview_->delegate()) {
    gfx::Rect rect;
    webview_->delegate()->GetRootWindowRect(webview_, &rect);
    return WebCore::FloatRect(
        static_cast<float>(rect.x()),
        static_cast<float>(rect.y()),
        static_cast<float>(rect.width()),
        static_cast<float>(rect.height()));
  } else {
    // These numbers will be fairly wrong. The window's x/y coordinates will
    // be the top left corner of the screen and the size will be the content
    // size instead of the window size.
    gfx::Point origin;
    const gfx::Size size = webview_->size();
    return WebCore::FloatRect(
        static_cast<float>(origin.x()),
        static_cast<float>(origin.y()),
        static_cast<float>(size.width()),
        static_cast<float>(size.height()));
  }
}

WebCore::FloatRect ChromeClientImpl::pageRect() {
  // We hide the details of the window's border thickness from the web page by
  // simple re-using the window position here.  So, from the point-of-view of
  // the web page, the window has no border.
  return windowRect();
}

float ChromeClientImpl::scaleFactor() {
  // This is supposed to return the scale factor of the web page. It looks like
  // the implementor of the graphics layer is responsible for doing most of the
  // operations associated with scaling. However, this value is used ins some
  // cases by WebCore. For example, this is used as a scaling factor in canvas
  // so that things drawn in it are scaled just like the web page is.
  //
  // We don't currently implement scaling, so just return 1.0 (no scaling).
  return 1.0;
}

void ChromeClientImpl::focus() {
  WebViewDelegate* d = webview_->delegate();
  if (d)
    d->Focus(webview_);
}

void ChromeClientImpl::unfocus() {
  WebViewDelegate* d = webview_->delegate();
  if (d)
    d->Blur(webview_);
}

bool ChromeClientImpl::canTakeFocus(WebCore::FocusDirection) {
  // For now the browser can always take focus if we're not running layout
  // tests.
  return !webkit_glue::IsLayoutTestMode();
}

void ChromeClientImpl::takeFocus(WebCore::FocusDirection direction) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    d->TakeFocus(webview_,
                 direction == WebCore::FocusDirectionBackward);
  }
}

WebCore::Page* ChromeClientImpl::createWindow(
    WebCore::Frame* frame, const WebCore::FrameLoadRequest& r,
    const WebCore::WindowFeatures& features) {
  WebViewDelegate* d = webview_->delegate();
  if (!d)
    return NULL;

  bool userGesture = frame->script()->processingUserGesture();
  WebViewImpl* new_view = static_cast<WebViewImpl*>(
      d->CreateWebView(webview_, userGesture));
  if (!new_view)
    return NULL;

  // The request is empty when we are just being asked to open a blank window.
  // This corresponds to window.open(""), for example.
  if (!r.resourceRequest().isEmpty()) {
    WebRequestImpl request(r);
    new_view->main_frame()->LoadRequest(&request);
  }

  WebViewImpl* new_view_impl = static_cast<WebViewImpl*>(new_view);
  return new_view_impl->page();
}

static inline bool CurrentEventShouldCauseBackgroundTab(
    const WebInputEvent* input_event) {
  if (!input_event)
    return false;

  if (input_event->type != WebInputEvent::MOUSE_UP)
    return false;

  const WebMouseEvent* mouse_event = static_cast<const WebMouseEvent*>(input_event);
  return (mouse_event->button == WebMouseEvent::BUTTON_MIDDLE);
}

void ChromeClientImpl::show() {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    // If our default configuration was modified by a script or wasn't
    // created by a user gesture, then show as a popup. Else, let this
    // new window be opened as a toplevel window.
    //
    bool as_popup =
        !toolbars_visible_ ||
        !statusbar_visible_ ||
        !scrollbars_visible_ ||
        !menubar_visible_ ||
        !resizable_ ||
        !d->WasOpenedByUserGesture(webview_);

    WindowOpenDisposition disposition = NEW_FOREGROUND_TAB;
    if (as_popup)
      disposition = NEW_POPUP;
    if (CurrentEventShouldCauseBackgroundTab(WebViewImpl::current_input_event()))
      disposition = NEW_BACKGROUND_TAB;

    d->Show(webview_, disposition);
  }
}

bool ChromeClientImpl::canRunModal() {
  return webview_->delegate() != NULL;
}

void ChromeClientImpl::runModal() {
  WebViewDelegate* d = webview_->delegate();
  if (d)
    d->RunModal(webview_);
}

void ChromeClientImpl::setToolbarsVisible(bool value) {
  toolbars_visible_ = value;
}

bool ChromeClientImpl::toolbarsVisible() {
  return toolbars_visible_;
}

void ChromeClientImpl::setStatusbarVisible(bool value) {
  statusbar_visible_ = value;
}

bool ChromeClientImpl::statusbarVisible() {
  return statusbar_visible_;
}

void ChromeClientImpl::setScrollbarsVisible(bool value) {
  scrollbars_visible_ = value;
  WebFrameImpl* web_frame =
      static_cast<WebFrameImpl*>(webview_->GetMainFrame());
  if (web_frame)
    web_frame->SetAllowsScrolling(value);
}

bool ChromeClientImpl::scrollbarsVisible() {
  return scrollbars_visible_;
}

void ChromeClientImpl::setMenubarVisible(bool value) {
  menubar_visible_ = value;
}

bool ChromeClientImpl::menubarVisible() {
  return menubar_visible_;
}

void ChromeClientImpl::setResizable(bool value) {
  resizable_ = value;
}

void ChromeClientImpl::addMessageToConsole(const WebCore::String& message,
                                           unsigned int line_no,
                                           const WebCore::String& source_id) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    std::wstring wstr_message = webkit_glue::StringToStdWString(message);
    std::wstring wstr_source_id = webkit_glue::StringToStdWString(source_id);
    d->AddMessageToConsole(webview_, wstr_message, line_no, wstr_source_id);
  }
}

bool ChromeClientImpl::canRunBeforeUnloadConfirmPanel() {
  return webview_->delegate() != NULL;
}

bool ChromeClientImpl::runBeforeUnloadConfirmPanel(const WebCore::String& message,
                                                   WebCore::Frame* frame) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    std::wstring wstr = webkit_glue::StringToStdWString(message);
    return d->RunBeforeUnloadConfirm(webview_, wstr);
  }
  return false;
}

void ChromeClientImpl::closeWindowSoon() {
  // Make sure this Page can no longer be found by JS.
  webview_->page()->setGroupName(WebCore::String());
  
  // Make sure that all loading is stopped.  Ensures that JS stops executing!
  webview_->StopLoading();

  WebViewDelegate* d = webview_->delegate();
  if (d)
    d->CloseWidgetSoon(webview_);
}

// Although a WebCore::Frame is passed in, we don't actually use it, since we
// already know our own webview_.
void ChromeClientImpl::runJavaScriptAlert(WebCore::Frame* frame,
                                          const WebCore::String& message) {
  // Pass the request on to the WebView delegate, for more control.
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    std::wstring wstr = webkit_glue::StringToStdWString(message);
    d->RunJavaScriptAlert(webview_, wstr);
  }
}

// See comments for runJavaScriptAlert().
bool ChromeClientImpl::runJavaScriptConfirm(WebCore::Frame* frame,
                                            const WebCore::String& message) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    std::wstring wstr = webkit_glue::StringToStdWString(message);
    return d->RunJavaScriptConfirm(webview_, wstr);
  }
  return false;
}

// See comments for runJavaScriptAlert().
bool ChromeClientImpl::runJavaScriptPrompt(WebCore::Frame* frame,
                                           const WebCore::String& message,
                                           const WebCore::String& defaultValue,
                                           WebCore::String& result) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    std::wstring wstr_message = webkit_glue::StringToStdWString(message);
    std::wstring wstr_default = webkit_glue::StringToStdWString(defaultValue);
    std::wstring wstr_result;
    bool ok = d->RunJavaScriptPrompt(webview_,
                                     wstr_message,
                                     wstr_default,
                                     &wstr_result);
    if (ok)
      result = webkit_glue::StdWStringToString(wstr_result);
    return ok;
  }
  return false;
}

void ChromeClientImpl::setStatusbarText(const WebCore::String&) {
  // TODO(mbelshe): implement me
}

bool ChromeClientImpl::shouldInterruptJavaScript() {
  // TODO(mbelshe): implement me
  return false;
}

bool ChromeClientImpl::tabsToLinks() const {
  // TODO(pamg): Consider controlling this with a user preference, when we have
  // a preference system in place.
  // For now Chrome will allow link to take focus if we're not running layout
  // tests.
  return !webkit_glue::IsLayoutTestMode();
}

WebCore::IntRect ChromeClientImpl::windowResizerRect() const {
  // TODO(mbelshe): implement me
  WebCore::IntRect rv;
  return rv;
}

void ChromeClientImpl::addToDirtyRegion(const WebCore::IntRect& damaged_rect) {
  ASSERT_NOT_REACHED();
}

void ChromeClientImpl::scrollBackingStore(int dx, int dy,
                                          const WebCore::IntRect& scroll_rect,
                                          const WebCore::IntRect& clip_rect) {
  ASSERT_NOT_REACHED();
}

void ChromeClientImpl::updateBackingStore() {
  ASSERT_NOT_REACHED();
}

void ChromeClientImpl::mouseDidMoveOverElement(const WebCore::HitTestResult& result,
                                               unsigned modifierFlags) {

  // Find out if the mouse is over a link, and if so, let our UI know... somehow
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    if (result.isLiveLink() && !result.absoluteLinkURL().string().isEmpty()) {
      d->UpdateTargetURL(webview_, webkit_glue::KURLToGURL(result.absoluteLinkURL()));
    } else {
      d->UpdateTargetURL(webview_, GURL());
    }
  }
}

void ChromeClientImpl::setToolTip(const WebCore::String& tooltip_text) {
  if (webview_->delegate()) {
    std::wstring tooltip_text_as_wstring =
      webkit_glue::StringToStdWString(tooltip_text);
    webview_->delegate()->SetTooltipText(webview_, tooltip_text_as_wstring);
  }
}

void ChromeClientImpl::runFileChooser(const WebCore::String& default_path,
                                      PassRefPtr<WebCore::FileChooser> fileChooser) {
  WebViewDelegate* delegate = webview_->delegate();
  if (!delegate)
    return;

  std::wstring suggestion = webkit_glue::StringToStdWString(default_path);
  WebFileChooserCallbackImpl* chooser = new WebFileChooserCallbackImpl(fileChooser);
  delegate->RunFileChooser(suggestion, chooser);
}

WebCore::IntRect ChromeClientImpl::windowToScreen(const WebCore::IntRect& rect) {
  WebCore::IntRect screen_rect(rect);

  WebViewDelegate* d = webview_->delegate();
  if (d) {
    gfx::Rect window_rect;
    d->GetWindowRect(webview_, &window_rect);
    screen_rect.move(window_rect.x(), window_rect.y());
  }

  return screen_rect;
}

void ChromeClientImpl::print(WebCore::Frame* frame) {
  WebViewDelegate* d = webview_->delegate();
  if (d) {
    d->ScriptedPrint(WebFrameImpl::FromFrame(frame));
  }
}

void ChromeClientImpl::exceededDatabaseQuota(WebCore::Frame* frame,
    const WebCore::String& databaseName) {
  // TODO(tc): If we enable the storage API, we need to implement this function.
}