// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "renderer/win32/candidate_window.h"

#include <windows.h>

#include <sstream>
#include <string>

#include "base/coordinates.h"
#include "base/logging.h"
#include "base/util.h"
#include "client/client_interface.h"
#include "protocol/renderer_command.pb.h"
#include "renderer/renderer_style_handler.h"
#include "renderer/table_layout.h"
#include "renderer/win32/text_renderer.h"
#include "renderer/win_resource.h"

namespace mozc {
namespace renderer {
namespace win32 {

using WTL::CBitmap;
using WTL::CDC;
using WTL::CDCHandle;
using WTL::CMemoryDC;
using WTL::CPaintDC;
using WTL::CPenHandle;
using WTL::CPoint;
using WTL::CRect;
using WTL::CSize;

namespace {

// 96 DPI is the default DPI in Windows.
const int kDefaultDPI = 96;

// layout size constants in pixel unit in the default DPI.
const int kIndicatorWidthInDefaultDPI = 4;

// DPI-invariant layout size constants in pixel unit.
const int kWindowBorder = 1;
const int kFooterSeparatorHeight = 1;
const int kRowRectPadding = 1;

// usage type for each column.
enum COLUMN_TYPE {
  COLUMN_SHORTCUT = 0,  // show shortcut key
  COLUMN_GAP1,          // padding region
  COLUMN_CANDIDATE,     // show candidate string
  COLUMN_GAP2,          // padding region
  COLUMN_DESCRIPTION,   // show description message
  NUMBER_OF_COLUMNS,    // number of columns. (this item should be last)
};

const char kMinimumCandidateAndDescriptionWidthAsString[] = "そのほかの文字種";

// Color scheme
const COLORREF kFrameColor = RGB(0x96, 0x96, 0x96);
const COLORREF kShortcutBackgroundColor = RGB(0xf3, 0xf4, 0xff);
const COLORREF kSelectedRowBackgroundColor = RGB(0xd1, 0xea, 0xff);
const COLORREF kDefaultBackgroundColor = RGB(0xff, 0xff, 0xff);
const COLORREF kSelectedRowFrameColor = RGB(0x7f, 0xac, 0xdd);
const COLORREF kIndicatorBackgroundColor = RGB(0xe0, 0xe0, 0xe0);
const COLORREF kIndicatorColor = RGB(0x75, 0x90, 0xb8);
const COLORREF kFooterTopColor = RGB(0xff, 0xff, 0xff);
const COLORREF kFooterBottomColor = RGB(0xee, 0xee, 0xee);

// ------------------------------------------------------------------------
// Utility functions
// ------------------------------------------------------------------------
WTL::CRect ToCRect(const Rect &rect) {
  return WTL::CRect(rect.Left(), rect.Top(), rect.Right(), rect.Bottom());
}

// Returns the smallest index of the given candidate list which satisfies
// candidates.candidate(i) == |candidate_index|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetCandidateArrayIndexByCandidateIndex(
    const commands::Candidates &candidates, int candidate_index) {
  for (size_t i = 0; i < candidates.candidate_size(); ++i) {
    const commands::Candidates::Candidate &candidate = candidates.candidate(i);

    if (candidate.index() == candidate_index) {
      return i;
    }
  }

  return candidates.candidate_size();
}

// Returns a text which includes the selected index number and
// the number of the candidates. For example, "13/123" means
// the selected index is "13" (in 1-origin) and the number of
// candidates is "123"
// Returns an empty string if index string should not be displayed.
std::string GetIndexGuideString(const commands::Candidates &candidates) {
  if (!candidates.has_footer() || !candidates.footer().index_visible()) {
    return "";
  }

  const int focused_index = candidates.focused_index();
  const int total_items = candidates.size();

  std::stringstream footer_string;
  footer_string << focused_index + 1 << "/" << total_items
                << " ";  // for padding.

  return footer_string.str();
}

// Returns the smallest index of the given candidate list which satisfies
// |candidates.focused_index| == |candidates.candidate(i).index()|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetFocusedArrayIndex(const commands::Candidates &candidates) {
  const int kInvalidIndex = candidates.candidate_size();

  if (!candidates.has_focused_index()) {
    return kInvalidIndex;
  }

  const int focused_index = candidates.focused_index();

  return GetCandidateArrayIndexByCandidateIndex(candidates, focused_index);
}

// Retrieves the display string from the specified candidate for the specified
// column and returns it.
std::wstring GetDisplayStringByColumn(
    const commands::Candidates::Candidate &candidate, COLUMN_TYPE column_type) {
  std::wstring display_string;

  switch (column_type) {
    case COLUMN_SHORTCUT:
      if (candidate.has_annotation()) {
        const commands::Annotation &annotation = candidate.annotation();
        if (annotation.has_shortcut()) {
          mozc::Util::UTF8ToWide(annotation.shortcut(), &display_string);
        }
      }
      break;
    case COLUMN_CANDIDATE:
      if (candidate.has_value()) {
        mozc::Util::UTF8ToWide(candidate.value(), &display_string);
      }
      if (candidate.has_annotation()) {
        const commands::Annotation &annotation = candidate.annotation();
        if (annotation.has_prefix()) {
          std::wstring annotation_prefix;
          mozc::Util::UTF8ToWide(annotation.prefix(), &annotation_prefix);
          display_string = annotation_prefix + display_string;
        }
        if (annotation.has_suffix()) {
          std::wstring annotation_suffix;
          mozc::Util::UTF8ToWide(annotation.suffix(), &annotation_suffix);
          display_string += annotation_suffix;
        }
      }
      break;
    case COLUMN_DESCRIPTION:
      if (candidate.has_annotation()) {
        const commands::Annotation &annotation = candidate.annotation();
        if (annotation.has_description()) {
          mozc::Util::UTF8ToWide(annotation.description(), &display_string);
        }
      }
      break;
    default:
      LOG(ERROR) << "Unknown column type: " << column_type;
      break;
  }

  return display_string;
}

// Loads a DIB from a Win32 resource in the specified module and returns its
// handle.  This function will fail if you try to load a top-down bitmap in
// Windows XP.
// Returns nullptr if failed to load the image.
// Caller must delete the object if this function returns non-null value.
HBITMAP LoadBitmapFromResource(HMODULE module, int resource_id) {
  // We can use LR_CREATEDIBSECTION to load a 32-bpp bitmap.
  // You cannot load a a top-down DIB with LoadImage in Windows XP.
  // http://b/2076264
  return reinterpret_cast<HBITMAP>(
      ::LoadImage(module, MAKEINTRESOURCE(resource_id), IMAGE_BITMAP, 0, 0,
                  LR_CREATEDIBSECTION));
}

}  // namespace

// ------------------------------------------------------------------------
// CandidateWindow
// ------------------------------------------------------------------------

CandidateWindow::CandidateWindow()
    : candidates_(new commands::Candidates),
      indicator_width_(0),
      footer_logo_display_size_(0, 0),
      metrics_changed_(false),
      mouse_moving_(true),
      text_renderer_(TextRenderer::Create()),
      table_layout_(new TableLayout),
      send_command_interface_(nullptr) {
  double scale_factor_x = 1.0;
  double scale_factor_y = 1.0;
  RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x, &scale_factor_y);
  double image_scale_factor = 1.0;
  if (scale_factor_x < 1.125 || scale_factor_y < 1.125) {
    footer_logo_.Attach(LoadBitmapFromResource(::GetModuleHandle(nullptr),
                                               IDB_FOOTER_LOGO_COLOR_100));
    image_scale_factor = 1.0;
  } else if (scale_factor_x < 1.375 || scale_factor_y < 1.375) {
    footer_logo_.Attach(LoadBitmapFromResource(::GetModuleHandle(nullptr),
                                               IDB_FOOTER_LOGO_COLOR_125));
    image_scale_factor = 1.25;
  } else if (scale_factor_x < 1.75 || scale_factor_y < 1.75) {
    footer_logo_.Attach(LoadBitmapFromResource(::GetModuleHandle(nullptr),
                                               IDB_FOOTER_LOGO_COLOR_150));
    image_scale_factor = 1.5;
  } else {
    footer_logo_.Attach(LoadBitmapFromResource(::GetModuleHandle(nullptr),
                                               IDB_FOOTER_LOGO_COLOR_200));
    image_scale_factor = 2.0;
  }

  // If DPI is not default value, re-calculate the size based on the DPI.
  if (!footer_logo_.IsNull()) {
    CSize size;
    footer_logo_.GetSize(size);
    size.cx *= (scale_factor_x / image_scale_factor);
    size.cy *= (scale_factor_y / image_scale_factor);
    footer_logo_display_size_ = Size(size.cx, size.cy);
  }

  indicator_width_ = kIndicatorWidthInDefaultDPI * scale_factor_x;
}

CandidateWindow::~CandidateWindow() {}

LRESULT CandidateWindow::OnCreate(LPCREATESTRUCT create_struct) {
  EnableOrDisableWindowForWorkaround();
  return 0;
}

void CandidateWindow::EnableOrDisableWindowForWorkaround() {
  // Disable the window if SPI_GETACTIVEWINDOWTRACKING is enabled.
  // See b/2317702 for details.
  // TODO(yukawa): Support mouse operations before we add a GUI feature which
  //   requires UI interaction by mouse and/or touch. (b/2954874)
  BOOL is_tracking_enabled = FALSE;
  if (::SystemParametersInfo(SPI_GETACTIVEWINDOWTRACKING, 0,
                             &is_tracking_enabled, 0)) {
    EnableWindow(!is_tracking_enabled);
  }
}

void CandidateWindow::OnDestroy() {
  // PostQuitMessage may stop the message loop even though other
  // windows are not closed. WindowManager should close these windows
  // before process termination.
  ::PostQuitMessage(0);
}

BOOL CandidateWindow::OnEraseBkgnd(CDCHandle dc) {
  // We do not have to erase background
  // because all pixels in client area will be drawn in the DoPaint method.
  return TRUE;
}

void CandidateWindow::OnGetMinMaxInfo(MINMAXINFO *min_max_info) {
  // Do not restrict the window size in case the candidate window must be
  // very small size.
  min_max_info->ptMinTrackSize.x = 1;
  min_max_info->ptMinTrackSize.y = 1;
  SetMsgHandled(TRUE);
}

void CandidateWindow::HandleMouseEvent(UINT nFlags, const WTL::CPoint &point,
                                       bool close_candidatewindow) {
  if (send_command_interface_ == nullptr) {
    LOG(ERROR) << "send_command_interface_ is nullptr";
    return;
  }

  const int focused_array_index = GetFocusedArrayIndex(*candidates_);

  for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
    const commands::Candidates::Candidate &candidate =
        candidates_->candidate(i);

    const CRect rect = ToCRect(table_layout_->GetRowRect(i));
    if (rect.PtInRect(point)) {
      commands::SessionCommand command;
      if (close_candidatewindow) {
        command.set_type(commands::SessionCommand::SELECT_CANDIDATE);
      } else {
        command.set_type(commands::SessionCommand::HIGHLIGHT_CANDIDATE);
      }
      command.set_id(candidate.id());
      commands::Output output;
      send_command_interface_->SendCommand(command, &output);
      return;
    }
  }
}

void CandidateWindow::OnLButtonDown(UINT nFlags, CPoint point) {
  HandleMouseEvent(nFlags, point, false);
}

void CandidateWindow::OnLButtonUp(UINT nFlags, CPoint point) {
  HandleMouseEvent(nFlags, point, true);
}

void CandidateWindow::OnMouseMove(UINT nFlags, WTL::CPoint point) {
  // Window manager sometimes generates WM_MOUSEMOVE message when the contents
  // under the mouse cursor has been changed (e.g. the window is moved) so that
  // the mouse handler can change its cursor image based on the contents to
  // which the cursor is newly pointing.  In order to filter these pseudo
  // WM_MOUSEMOVE out, |mouse_moving_| is checked here.
  // See http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx for
  // details about such an artificial WM_MOUSEMOVE.  See also b/3104996.
  if (!mouse_moving_) {
    return;
  }
  if ((nFlags & MK_LBUTTON) != MK_LBUTTON) {
    return;
  }

  HandleMouseEvent(nFlags, point, false);
}

void CandidateWindow::OnPaint(CDCHandle dc) {
  CRect client_rect;
  this->GetClientRect(&client_rect);

  if (dc != nullptr) {
    CMemoryDC memdc(dc, client_rect);
    DoPaint(memdc.m_hDC);
  } else {
    CPaintDC paint_dc(this->m_hWnd);
    {  // Create a copy of |paint_dc| and render the candidate strings in it.
      // The image rendered to this |memdc| is to be copied into the original
      // |paint_dc| in its destructor. So, we don't have to explicitly call
      // any functions that copy this |memdc| to the |paint_dc| but putting
      // the following code into a local block.
      CMemoryDC memdc(paint_dc, client_rect);
      DoPaint(memdc.m_hDC);
    }
  }
}

void CandidateWindow::OnPrintClient(CDCHandle dc, UINT uFlags) { OnPaint(dc); }

void CandidateWindow::DoPaint(CDCHandle dc) {
  switch (candidates_->category()) {
    case commands::CONVERSION:
    case commands::PREDICTION:
    case commands::TRANSLITERATION:
    case commands::SUGGESTION:
    case commands::USAGE:
      break;
    default:
      LOG(INFO) << "Unknown candidates category: " << candidates_->category();
      return;
  }

  if (!table_layout_->IsLayoutFrozen()) {
    LOG(WARNING) << "Table layout is not frozen.";
    return;
  }

  dc.SetBkMode(TRANSPARENT);

  DrawBackground(dc);
  DrawShortcutBackground(dc);
  DrawSelectedRect(dc);
  DrawCells(dc);
  DrawInformationIcon(dc);
  DrawVScrollBar(dc);
  DrawFooter(dc);
  DrawFrame(dc);
}

void CandidateWindow::OnSettingChange(UINT uFlags, LPCTSTR /*lpszSection*/) {
  // Since TextRenderer uses dialog font to render,
  // we monitor font-related parameters to know when the font style is changed.
  switch (uFlags) {
    case 0x1049:  // = SPI_SETCLEARTYPE
    case SPI_SETFONTSMOOTHING:
    case SPI_SETFONTSMOOTHINGCONTRAST:
    case SPI_SETFONTSMOOTHINGORIENTATION:
    case SPI_SETFONTSMOOTHINGTYPE:
    case SPI_SETNONCLIENTMETRICS:
      metrics_changed_ = true;
      break;
    case SPI_SETACTIVEWINDOWTRACKING:
      EnableOrDisableWindowForWorkaround();
    default:
      // We ignore other changes.
      break;
  }
}

void CandidateWindow::UpdateLayout(const commands::Candidates &candidates) {
  candidates_->CopyFrom(candidates);

  // If we detect any change of font parameters, update text renderer
  if (metrics_changed_) {
    text_renderer_->OnThemeChanged();
    metrics_changed_ = false;
  }

  switch (candidates_->category()) {
    case commands::CONVERSION:
    case commands::PREDICTION:
    case commands::TRANSLITERATION:
    case commands::SUGGESTION:
    case commands::USAGE:
      break;
    default:
      LOG(INFO) << "Unknown candidates category: " << candidates_->category();
      return;
  }

  table_layout_->Initialize(candidates_->candidate_size(), NUMBER_OF_COLUMNS);

  table_layout_->SetWindowBorder(kWindowBorder);

  // Add a vertical scroll bar if candidate list consists of more than
  // one page.
  if (candidates_->candidate_size() < candidates_->size()) {
    table_layout_->SetVScrollBar(indicator_width_);
  }

  if (candidates_->has_footer()) {
    Size footer_size(0, 0);

    // Calculate the size to display a label string.
    if (candidates_->footer().has_label()) {
      std::wstring footer_label;
      mozc::Util::UTF8ToWide(candidates_->footer().label(), &footer_label);
      const Size label_string_size = text_renderer_->MeasureString(
          TextRenderer::FONTSET_FOOTER_LABEL, L" " + footer_label + L" ");
      footer_size.width += label_string_size.width;
      footer_size.height =
          std::max(footer_size.height, label_string_size.height);
    } else if (candidates_->footer().has_sub_label()) {
      // Currently the sub label will not be shown unless (main) label is
      // absent.
      // TODO(yukawa): Refactor the layout system for the footer.
      std::wstring footer_sub_label;
      mozc::Util::UTF8ToWide(candidates_->footer().sub_label(),
                             &footer_sub_label);
      const Size label_string_size =
          text_renderer_->MeasureString(TextRenderer::FONTSET_FOOTER_SUBLABEL,
                                        L" " + footer_sub_label + L" ");
      footer_size.width += label_string_size.width;
      footer_size.height =
          std::max(footer_size.height, label_string_size.height);
    }

    // Calculate the size to display a index string.
    if (candidates_->footer().index_visible()) {
      std::wstring index_guide_string;
      mozc::Util::UTF8ToWide(GetIndexGuideString(*candidates_),
                             &index_guide_string);
      const Size index_guide_size = text_renderer_->MeasureString(
          TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string);
      footer_size.width += index_guide_size.width;
      footer_size.height =
          std::max(footer_size.height, index_guide_size.height);
    }

    // Calculate the size to display a Footer logo.
    if (!footer_logo_.IsNull()) {
      if (candidates_->footer().logo_visible()) {
        footer_size.width += footer_logo_display_size_.width;
        footer_size.height =
            std::max(footer_size.height, footer_logo_display_size_.height);
      } else if (footer_size.height > 0) {
        // Ensure the footer height is greater than the Footer logo height
        // even if the Footer logo is absent.  This hack prevents the footer
        // from changing its height too frequently.
        footer_size.height =
            std::max(footer_size.height, footer_logo_display_size_.height);
      }
    }

    // Ensure minimum columns width if candidate list consists of more than
    // one page.
    if (candidates_->candidate_size() < candidates_->size()) {
      // We use FONTSET_CANDIDATE for calculating the minimum width.
      std::wstring minimum_width_as_wstring;
      mozc::Util::UTF8ToWide(kMinimumCandidateAndDescriptionWidthAsString,
                             &minimum_width_as_wstring);
      const Size minimum_size = text_renderer_->MeasureString(
          TextRenderer::FONTSET_CANDIDATE, minimum_width_as_wstring.c_str());
      table_layout_->EnsureColumnsWidth(COLUMN_CANDIDATE, COLUMN_DESCRIPTION,
                                        minimum_size.width);
    }

    // Add separator height
    footer_size.height += kFooterSeparatorHeight;

    table_layout_->EnsureFooterSize(footer_size);
  }

  table_layout_->SetRowRectPadding(kRowRectPadding);

  // put a padding in COLUMN_GAP1.
  // the width is determined to be equal to the width of " ".
  const Size gap1_size =
      text_renderer_->MeasureString(TextRenderer::FONTSET_CANDIDATE, L" ");
  table_layout_->EnsureCellSize(COLUMN_GAP1, gap1_size);

  bool description_found = false;

  // calculate table size.
  for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
    const commands::Candidates::Candidate &candidate =
        candidates_->candidate(i);
    const std::wstring shortcut =
        GetDisplayStringByColumn(candidate, COLUMN_SHORTCUT);
    const std::wstring description =
        GetDisplayStringByColumn(candidate, COLUMN_DESCRIPTION);
    const std::wstring candidate_string =
        GetDisplayStringByColumn(candidate, COLUMN_CANDIDATE);

    if (!shortcut.empty()) {
      std::wstring text;
      text.push_back(L' ');  // put a space for padding
      text.append(shortcut);
      text.push_back(L' ');  // put a space for padding
      const Size rendering_size =
          text_renderer_->MeasureString(TextRenderer::FONTSET_SHORTCUT, text);
      table_layout_->EnsureCellSize(COLUMN_SHORTCUT, rendering_size);
    }

    if (!candidate_string.empty()) {
      std::wstring text;
      text.append(candidate_string);

      const Size rendering_size =
          text_renderer_->MeasureString(TextRenderer::FONTSET_CANDIDATE, text);
      table_layout_->EnsureCellSize(COLUMN_CANDIDATE, rendering_size);
    }

    if (!description.empty()) {
      std::wstring text;
      text.append(description);
      text.push_back(L' ');  // put a space for padding
      const Size rendering_size = text_renderer_->MeasureString(
          TextRenderer::FONTSET_DESCRIPTION, text);
      table_layout_->EnsureCellSize(COLUMN_DESCRIPTION, rendering_size);

      description_found = true;
    }
  }

  // Put a padding in COLUMN_GAP2.
  // We use wide padding if there is any description column.
  const wchar_t *gap2_string = (description_found ? L"   " : L" ");
  const Size gap2_size = text_renderer_->MeasureString(
      TextRenderer::FONTSET_CANDIDATE, gap2_string);
  table_layout_->EnsureCellSize(COLUMN_GAP2, gap2_size);

  table_layout_->FreezeLayout();
}

void CandidateWindow::SetSendCommandInterface(
    client::SendCommandInterface *send_command_interface) {
  send_command_interface_ = send_command_interface;
}

Size CandidateWindow::GetLayoutSize() const {
  DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";

  return table_layout_->GetTotalSize();
}

Rect CandidateWindow::GetSelectionRectInScreenCord() const {
  const int focused_array_index = GetFocusedArrayIndex(*candidates_);

  if (0 <= focused_array_index &&
      focused_array_index < candidates_->candidate_size()) {
    const commands::Candidates::Candidate &candidate =
        candidates_->candidate(focused_array_index);

    CRect rect = ToCRect(table_layout_->GetRowRect(focused_array_index));
    ClientToScreen(&rect);
    return Rect(rect.left, rect.top, rect.Width(), rect.Height());
  }

  return Rect();
}

Rect CandidateWindow::GetCandidateColumnInClientCord() const {
  DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";

  return table_layout_->GetCellRect(0, COLUMN_CANDIDATE);
}

Rect CandidateWindow::GetFirstRowInClientCord() const {
  DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
  DCHECK_GT(table_layout_->number_of_rows(), 0)
      << "number of rows should be positive";
  return table_layout_->GetRowRect(0);
}

void CandidateWindow::DrawCells(CDCHandle dc) {
  COLUMN_TYPE kColumnTypes[] = {COLUMN_SHORTCUT, COLUMN_CANDIDATE,
                                COLUMN_DESCRIPTION};
  TextRenderer::FONT_TYPE kFontTypes[] = {TextRenderer::FONTSET_SHORTCUT,
                                          TextRenderer::FONTSET_CANDIDATE,
                                          TextRenderer::FONTSET_DESCRIPTION};

  DCHECK_EQ(arraysize(kColumnTypes), arraysize(kFontTypes));
  for (size_t type_index = 0; type_index < arraysize(kColumnTypes);
       ++type_index) {
    const COLUMN_TYPE column_type = kColumnTypes[type_index];
    const TextRenderer::FONT_TYPE font_type = kFontTypes[type_index];

    std::vector<TextRenderingInfo> display_list;
    for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
      const commands::Candidates::Candidate &candidate =
          candidates_->candidate(i);
      const std::wstring display_string =
          GetDisplayStringByColumn(candidate, column_type);
      const Rect text_rect = table_layout_->GetCellRect(i, column_type);
      display_list.push_back(TextRenderingInfo(display_string, text_rect));
    }
    text_renderer_->RenderTextList(dc, display_list, font_type);
  }
}

void CandidateWindow::DrawVScrollBar(CDCHandle dc) {
  const Rect &vscroll_rect = table_layout_->GetVScrollBarRect();

  if (!vscroll_rect.IsRectEmpty() && candidates_->candidate_size() > 0) {
    const int begin_index = candidates_->candidate(0).index();
    const int candidates_in_page = candidates_->candidate_size();
    const int candidates_total = candidates_->size();
    const int end_index =
        candidates_->candidate(candidates_in_page - 1).index();

    const CRect background_crect = ToCRect(vscroll_rect);
    dc.FillSolidRect(&background_crect, kIndicatorBackgroundColor);

    const mozc::Rect &indicator_rect = table_layout_->GetVScrollIndicatorRect(
        begin_index, end_index, candidates_total);

    const CRect indicator_crect = ToCRect(indicator_rect);
    dc.FillSolidRect(&indicator_crect, kIndicatorColor);
  }
}

void CandidateWindow::DrawShortcutBackground(CDCHandle dc) {
  if (table_layout_->number_of_columns() > 0) {
    Rect shortcut_colmun_rect = table_layout_->GetColumnRect(0);
    if (!shortcut_colmun_rect.IsRectEmpty()) {
      // Due to the mismatch of the implementation of the TableLayout class
      // and the design requiement, we have to *fix* the width and origin
      // of the rectangle.
      // If you remove this *fix*, an empty region appears between the
      // left window border and the colored region of the shortcut column.
      const Rect row_rect = table_layout_->GetRowRect(0);
      const int width = shortcut_colmun_rect.Right() - row_rect.Left();
      shortcut_colmun_rect.origin.x = row_rect.Left();
      shortcut_colmun_rect.size.width = width;
      const CRect shortcut_colmun_crect = ToCRect(shortcut_colmun_rect);
      dc.FillSolidRect(&shortcut_colmun_crect, kShortcutBackgroundColor);
    }
  }
}

void CandidateWindow::DrawFooter(CDCHandle dc) {
  const Rect &footer_rect = table_layout_->GetFooterRect();
  if (!candidates_->has_footer() || footer_rect.IsRectEmpty()) {
    return;
  }

  const COLORREF kFooterSeparatorColors[kFooterSeparatorHeight] = {kFrameColor};

  // DC pen is available in Windows 2000 and later.
  CPenHandle prev_pen(dc.SelectPen(static_cast<HPEN>(GetStockObject(DC_PEN))));
  for (size_t i = 0, y = footer_rect.Top(); i < kFooterSeparatorHeight;
       y++, i++) {
    if (i < ARRAYSIZE(kFooterSeparatorColors)) {
      dc.SetDCPenColor(kFooterSeparatorColors[i]);
      dc.MoveTo(footer_rect.Left(), y, nullptr);
      dc.LineTo(footer_rect.Right(), y);
    }
  }
  dc.SelectPen(prev_pen);

  const Rect footer_content_rect(
      footer_rect.Left(), footer_rect.Top() + kFooterSeparatorHeight,
      footer_rect.Width(), footer_rect.Height() - kFooterSeparatorHeight);

  // Draw gradient rect in the footer area
  {
    TRIVERTEX vertices[] = {
        {footer_content_rect.Left(), footer_content_rect.Top(),
         GetRValue(kFooterTopColor) << 8, GetGValue(kFooterTopColor) << 8,
         GetBValue(kFooterTopColor) << 8, 0xff00},
        {footer_content_rect.Right(), footer_content_rect.Bottom(),
         GetRValue(kFooterBottomColor) << 8, GetGValue(kFooterBottomColor) << 8,
         GetBValue(kFooterBottomColor) << 8, 0xff00}};
    GRADIENT_RECT indices[] = {{0, 1}};
    dc.GradientFill(&vertices[0], ARRAYSIZE(vertices), &indices[0],
                    ARRAYSIZE(indices), GRADIENT_FILL_RECT_V);
  }

  int left_used = 0;

  if (candidates_->footer().logo_visible() && !footer_logo_.IsNull()) {
    const int top_offset =
        (footer_content_rect.Height() - footer_logo_display_size_.height) / 2;
    CDC src_dc;
    src_dc.CreateCompatibleDC(dc);
    const HBITMAP old_bitmap = src_dc.SelectBitmap(footer_logo_);

    CSize src_size;
    footer_logo_.GetSize(src_size);

    // NOTE: AC_SRC_ALPHA requires PBGRA (pre-multiplied alpha) DIB.
    const BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
    dc.AlphaBlend(
        footer_content_rect.Left(), footer_content_rect.Top() + top_offset,
        footer_logo_display_size_.width, footer_logo_display_size_.height,
        src_dc, 0, 0, src_size.cx, src_size.cy, bf);

    src_dc.SelectBitmap(old_bitmap);
    left_used = footer_content_rect.Left() + footer_logo_display_size_.width;
  }

  int right_used = 0;
  if (candidates_->footer().index_visible()) {
    std::wstring index_guide_string;
    mozc::Util::UTF8ToWide(GetIndexGuideString(*candidates_),
                           &index_guide_string);
    const Size index_guide_size = text_renderer_->MeasureString(
        TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string);
    const Rect index_rect(footer_content_rect.Right() - index_guide_size.width,
                          footer_content_rect.Top(), index_guide_size.width,
                          footer_content_rect.Height());
    text_renderer_->RenderText(dc, index_guide_string, index_rect,
                               TextRenderer::FONTSET_FOOTER_INDEX);
    right_used = index_guide_size.width;
  }

  if (candidates_->footer().has_label()) {
    const Rect label_rect(left_used, footer_content_rect.Top(),
                          footer_content_rect.Width() - left_used - right_used,
                          footer_content_rect.Height());
    std::wstring footer_label;
    mozc::Util::UTF8ToWide(candidates_->footer().label(), &footer_label);
    text_renderer_->RenderText(dc, L" " + footer_label + L" ", label_rect,
                               TextRenderer::FONTSET_FOOTER_LABEL);
  } else if (candidates_->footer().has_sub_label()) {
    std::wstring footer_sub_label;
    mozc::Util::UTF8ToWide(candidates_->footer().sub_label(),
                           &footer_sub_label);
    const Rect label_rect(left_used, footer_content_rect.Top(),
                          footer_content_rect.Width() - left_used - right_used,
                          footer_content_rect.Height());
    const std::wstring text = L" " + footer_sub_label + L" ";
    text_renderer_->RenderText(dc, text, label_rect,
                               TextRenderer::FONTSET_FOOTER_SUBLABEL);
  }
}

void CandidateWindow::DrawSelectedRect(CDCHandle dc) {
  DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";

  const int focused_array_index = GetFocusedArrayIndex(*candidates_);

  if (0 <= focused_array_index &&
      focused_array_index < candidates_->candidate_size()) {
    const commands::Candidates::Candidate &candidate =
        candidates_->candidate(focused_array_index);

    const CRect selected_rect =
        ToCRect(table_layout_->GetRowRect(focused_array_index));
    dc.FillSolidRect(&selected_rect, kSelectedRowBackgroundColor);

    dc.SetDCBrushColor(kSelectedRowFrameColor);
    dc.FrameRect(&selected_rect, static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
  }
}

void CandidateWindow::DrawInformationIcon(CDCHandle dc) {
  DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
  double scale_factor_x = 1.0;
  double scale_factor_y = 1.0;
  RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x, &scale_factor_y);
  for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
    if (candidates_->candidate(i).has_information_id()) {
      CRect rect = ToCRect(table_layout_->GetRowRect(i));
      rect.left = rect.right - (6.0 * scale_factor_x);
      rect.right = rect.right - (2.0 * scale_factor_x);
      rect.top += (2.0 * scale_factor_y);
      rect.bottom -= (2.0 * scale_factor_y);
      dc.FillSolidRect(&rect, kIndicatorColor);
      dc.SetDCBrushColor(kIndicatorColor);
      dc.FrameRect(&rect, static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
    }
  }
}

void CandidateWindow::DrawBackground(CDCHandle dc) {
  const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize());
  const CRect client_crect = ToCRect(client_rect);
  dc.FillSolidRect(&client_crect, kDefaultBackgroundColor);
}

void CandidateWindow::DrawFrame(CDCHandle dc) {
  const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize());
  const CRect client_crect = ToCRect(client_rect);

  // DC brush is available in Windows 2000 and later.
  dc.SetDCBrushColor(kFrameColor);
  dc.FrameRect(&client_crect, static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
}

void CandidateWindow::set_mouse_moving(bool moving) { mouse_moving_ = moving; }
}  // namespace win32
}  // namespace renderer
}  // namespace mozc
