// Copyright 2013 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/shell/browser/shell.h"

#include <windows.h>
#include <commctrl.h>
#include <fcntl.h>
#include <io.h>

#include "base/strings/utf_string_conversions.h"
#include "base/win/wrapped_window_proc.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/shell/app/resource.h"
#include "ui/gfx/win/hwnd_util.h"

namespace {

const wchar_t kWindowTitle[] = L"Content Shell";
const wchar_t kWindowClass[] = L"CONTENT_SHELL";

const int kButtonWidth = 72;
const int kURLBarHeight = 24;

const int kMaxURLLength = 1024;

}  // namespace

namespace content {

HINSTANCE Shell::instance_handle_;

void Shell::PlatformInitialize(const gfx::Size& default_window_size) {
  _setmode(_fileno(stdout), _O_BINARY);
  _setmode(_fileno(stderr), _O_BINARY);
  INITCOMMONCONTROLSEX InitCtrlEx;
  InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
  InitCtrlEx.dwICC  = ICC_STANDARD_CLASSES;
  InitCommonControlsEx(&InitCtrlEx);
  RegisterWindowClass();
}

void Shell::PlatformExit() {
  std::vector<Shell*> windows = windows_;
  for (std::vector<Shell*>::iterator it = windows.begin();
       it != windows.end(); ++it)
    DestroyWindow((*it)->window_);
}

void Shell::PlatformCleanUp() {
  // When the window is destroyed, tell the Edit field to forget about us,
  // otherwise we will crash.
  gfx::SetWindowProc(url_edit_view_, default_edit_wnd_proc_);
  gfx::SetWindowUserData(url_edit_view_, NULL);
}

void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) {
  int id;
  switch (control) {
    case BACK_BUTTON:
      id = IDC_NAV_BACK;
      break;
    case FORWARD_BUTTON:
      id = IDC_NAV_FORWARD;
      break;
    case STOP_BUTTON:
      id = IDC_NAV_STOP;
      break;
    default:
      NOTREACHED() << "Unknown UI control";
      return;
  }
  EnableWindow(GetDlgItem(window_, id), is_enabled);
}

void Shell::PlatformSetAddressBarURL(const GURL& url) {
  std::wstring url_string = UTF8ToWide(url.spec());
  SendMessage(url_edit_view_, WM_SETTEXT, 0,
              reinterpret_cast<LPARAM>(url_string.c_str()));
}

void Shell::PlatformSetIsLoading(bool loading) {
}

void Shell::PlatformCreateWindow(int width, int height) {
  window_ = CreateWindow(kWindowClass, kWindowTitle,
                         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                         CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                         NULL, NULL, instance_handle_, NULL);
  gfx::SetWindowUserData(window_, this);

  HWND hwnd;
  int x = 0;

  hwnd = CreateWindow(L"BUTTON", L"Back",
                      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,
                      x, 0, kButtonWidth, kURLBarHeight,
                      window_, (HMENU) IDC_NAV_BACK, instance_handle_, 0);
  x += kButtonWidth;

  hwnd = CreateWindow(L"BUTTON", L"Forward",
                      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,
                      x, 0, kButtonWidth, kURLBarHeight,
                      window_, (HMENU) IDC_NAV_FORWARD, instance_handle_, 0);
  x += kButtonWidth;

  hwnd = CreateWindow(L"BUTTON", L"Reload",
                      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,
                      x, 0, kButtonWidth, kURLBarHeight,
                      window_, (HMENU) IDC_NAV_RELOAD, instance_handle_, 0);
  x += kButtonWidth;

  hwnd = CreateWindow(L"BUTTON", L"Stop",
                      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,
                      x, 0, kButtonWidth, kURLBarHeight,
                      window_, (HMENU) IDC_NAV_STOP, instance_handle_, 0);
  x += kButtonWidth;

  // This control is positioned by PlatformResizeSubViews.
  url_edit_view_ = CreateWindow(L"EDIT", 0,
                                WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT |
                                ES_AUTOVSCROLL | ES_AUTOHSCROLL,
                                x, 0, 0, 0, window_, 0, instance_handle_, 0);

  default_edit_wnd_proc_ = gfx::SetWindowProc(url_edit_view_,
                                             Shell::EditWndProc);
  gfx::SetWindowUserData(url_edit_view_, this);

  ShowWindow(window_, SW_SHOW);

  SizeTo(gfx::Size(width, height));
}

void Shell::PlatformSetContents() {
  SetParent(web_contents_->GetView()->GetNativeView(), window_);
}

void Shell::SizeTo(const gfx::Size& size) {
  RECT rc, rw;
  GetClientRect(window_, &rc);
  GetWindowRect(window_, &rw);

  int client_width = rc.right - rc.left;
  int window_width = rw.right - rw.left;
  window_width = (window_width - client_width) + size.width();

  int client_height = rc.bottom - rc.top;
  int window_height = rw.bottom - rw.top;
  window_height = (window_height - client_height) + size.height();

  // Add space for the url bar.
  window_height += kURLBarHeight;

  SetWindowPos(window_, NULL, 0, 0, window_width, window_height,
               SWP_NOMOVE | SWP_NOZORDER);
}

void Shell::PlatformResizeSubViews() {
  RECT rc;
  GetClientRect(window_, &rc);

  int x = kButtonWidth * 4;
  MoveWindow(url_edit_view_, x, 0, rc.right - x, kURLBarHeight, TRUE);

  MoveWindow(GetContentView(), 0, kURLBarHeight, rc.right,
             rc.bottom - kURLBarHeight, TRUE);
}

void Shell::Close() {
  DestroyWindow(window_);
}

ATOM Shell::RegisterWindowClass() {
  WNDCLASSEX window_class;
  base::win::InitializeWindowClass(
      kWindowClass,
      &Shell::WndProc,
      CS_HREDRAW | CS_VREDRAW,
      0,
      0,
      LoadCursor(NULL, IDC_ARROW),
      NULL,
      MAKEINTRESOURCE(IDC_CONTENTSHELL),
      NULL,
      NULL,
      &window_class);
  instance_handle_ = window_class.hInstance;
  return RegisterClassEx(&window_class);
}

LRESULT CALLBACK Shell::WndProc(HWND hwnd, UINT message, WPARAM wParam,
                                LPARAM lParam) {
  Shell* shell = static_cast<Shell*>(gfx::GetWindowUserData(hwnd));

  switch (message) {
    case WM_COMMAND: {
      int id = LOWORD(wParam);
      switch (id) {
        case IDM_NEW_WINDOW:
          CreateNewWindow(
              shell->web_contents()->GetBrowserContext(),
              GURL(), NULL, MSG_ROUTING_NONE, gfx::Size());
          break;
        case IDM_CLOSE_WINDOW:
          DestroyWindow(hwnd);
          break;
        case IDM_EXIT:
          PlatformExit();
          break;
        case IDM_SHOW_DEVELOPER_TOOLS:
          shell->ShowDevTools();
          break;
        case IDC_NAV_BACK:
          shell->GoBackOrForward(-1);
          break;
        case IDC_NAV_FORWARD:
          shell->GoBackOrForward(1);
          break;
        case IDC_NAV_RELOAD:
          shell->Reload();
          break;
        case IDC_NAV_STOP:
          shell->Stop();
          break;
      }
      break;
    }
    case WM_DESTROY: {
      delete shell;
      return 0;
    }

    case WM_SIZE: {
      if (shell->GetContentView())
        shell->PlatformResizeSubViews();
      return 0;
    }

    case WM_WINDOWPOSCHANGED: {
      // Notify the content view that the window position of its parent window
      // has been changed by sending window message
      gfx::NativeView native_view = shell->GetContentView();
      if (native_view) {
        SendMessage(native_view, message, wParam, lParam);
      }
      break;
   }
  }

  return DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK Shell::EditWndProc(HWND hwnd, UINT message,
                                    WPARAM wParam, LPARAM lParam) {
  Shell* shell = static_cast<Shell*>(gfx::GetWindowUserData(hwnd));

  switch (message) {
    case WM_CHAR:
      if (wParam == VK_RETURN) {
        wchar_t str[kMaxURLLength + 1];  // Leave room for adding a NULL;
        *(str) = kMaxURLLength;
        LRESULT str_len = SendMessage(hwnd, EM_GETLINE, 0, (LPARAM)str);
        if (str_len > 0) {
          str[str_len] = 0;  // EM_GETLINE doesn't NULL terminate.
          GURL url(str);
          if (!url.has_scheme())
            url = GURL(std::wstring(L"http://") + std::wstring(str));
          shell->LoadURL(url);
        }

        return 0;
      }
  }

  return CallWindowProc(shell->default_edit_wnd_proc_, hwnd, message, wParam,
                        lParam);
}

void Shell::PlatformSetTitle(const base::string16& text) {
  ::SetWindowText(window_, text.c_str());
}

}  // namespace content