// 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.

#ifndef MOZC_RENDERER_WIN32_WIN32_RENDERER_UTIL_H_
#define MOZC_RENDERER_WIN32_WIN32_RENDERER_UTIL_H_

#include <Windows.h>

#include <memory>
#include <string>
#include <vector>

#include "base/port.h"
#include "testing/base/public/gunit_prod.h"
// for FRIEND_TEST()

// TODO(yukawa): Use platform independent primitive types.
#ifdef OS_WIN
namespace mozc {

namespace commands {
class RendererCommand;
// We cannot use RendererCommand::ApplicationInfo nor
// RendererCommand::CandidateForm because C++ does not
// allow forward declaration of a nested-type.
class RendererCommand_ApplicationInfo;
class RendererCommand_CandidateForm;
}  // namespace commands

namespace renderer {
namespace win32 {

struct CharacterRange {
  CharacterRange();
  int begin;
  int length;
};

struct LineLayout {
  std::wstring text;
  int line_length;
  int line_width;
  int line_start_offset;
  std::vector<CharacterRange> character_positions;
  LineLayout();
};

struct SegmentMarkerLayout {
  POINT from;
  POINT to;
  bool highlighted;
  SegmentMarkerLayout();
};

struct CompositionWindowLayout {
  RECT window_position_in_screen_coordinate;
  RECT text_area;
  RECT caret_rect;
  POINT base_position;
  LOGFONT log_font;
  std::wstring text;
  std::vector<SegmentMarkerLayout> marker_layouts;
  CompositionWindowLayout();
};

// A POD-like class which represents positional information about where the
// candidate window should be displayed.
// Do not inherit from this class. This class is not designed to be
// inheritable.
class CandidateWindowLayout {
 public:
  // Initializes fields with default values keeping |initialized_| false.
  CandidateWindowLayout();

  // Destructor Stub.  Actually do nothing.
  ~CandidateWindowLayout();

  // Returns true if this object is initialized with valid parameter.
  bool initialized() const;

  // Initializes fields with given target position and sets true
  // to |initialized_|.
  void InitializeWithPosition(const POINT &position);

  // Initializes fields with given target position and exclude region and
  // sets true to |initialized_|.
  void InitializeWithPositionAndExcludeRegion(const POINT &position,
                                              const RECT &exclude_region);

  // Clears fields and sets false to |initialized_|.
  void Clear();

  // Returns target position in screen coordinate.
  const POINT &position() const;

  // Returns exclude region in screen coordinate.  You can call this method
  // when |has_exclude_region| returns true.
  const RECT &exclude_region() const;

  // Returns true if |exclude_region| is available.
  bool has_exclude_region() const;

 private:
  POINT position_;
  RECT exclude_region_;
  bool has_exclude_region_;
  bool initialized_;
};

struct IndicatorWindowLayout {
 public:
  IndicatorWindowLayout();
  void Clear();

  RECT window_rect;
  bool is_vertical;
};

// This interface is designed to hook API calls for unit test.
class SystemPreferenceInterface {
 public:
  virtual ~SystemPreferenceInterface() {}

  // This method can be used to retrieve some kind of default font
  // for GUI rendering.
  // Returns true if succeeds.
  virtual bool GetDefaultGuiFont(LOGFONTW *log_font) = 0;
};

// This class implements SystemPreferenceInterface for unit tests.
class SystemPreferenceFactory {
 public:
  // Returns an instance of WindowPositionEmulator. Caller must delete
  // the instance.
  static SystemPreferenceInterface *CreateMock(const LOGFONTW &gui_font);

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(SystemPreferenceFactory);
};

// This interface is designed to hook API calls for unit test.
class WorkingAreaInterface {
 public:
  virtual ~WorkingAreaInterface() {}

  virtual bool GetWorkingAreaFromPoint(const POINT &point,
                                       RECT *working_area) = 0;
};

class WorkingAreaFactory {
 public:
  // Returns an instance of WorkingAreaInterface. Caller must delete
  // the instance.
  static WorkingAreaInterface *Create();

  // Returns an instance of WorkingAreaInterface. Caller must delete
  // the instance.
  static WorkingAreaInterface *CreateMock(const RECT &working_area);

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(WorkingAreaFactory);
};

// This interface is designed to hook API calls for unit test.
class WindowPositionInterface {
 public:
  virtual ~WindowPositionInterface() {}

  // This method wraps API call of LogicalToPhysicalPoint.
  // Returns true if this method can convert the given coordinate into
  // physical space.  We do not declare this method as const method so
  // that a mock class can implement this method in non-const way.
  virtual bool LogicalToPhysicalPoint(HWND window_handle,
                                      const POINT &logical_coordinate,
                                      POINT *physical_coordinate) = 0;

  // This method wraps API call of GetWindowRect.
  virtual bool GetWindowRect(HWND window_handle, RECT *rect) = 0;

  // This method wraps API call of GetClientRect.
  virtual bool GetClientRect(HWND window_handle, RECT *rect) = 0;

  // This method wraps API call of ClientToScreen.
  virtual bool ClientToScreen(HWND window_handle, POINT *point) = 0;

  // This method wraps API call of IsWindow.
  virtual bool IsWindow(HWND window_handle) = 0;

  // This method wraps API call of GetAncestor/GA_ROOT.
  virtual HWND GetRootWindow(HWND window_handle) = 0;

  // This method wraps API call of GetClassName.
  virtual bool GetWindowClassName(HWND window_handle,
                                  std::wstring *class_name) = 0;
};

// This class implements WindowPositionInterface and emulates APIs
// with positional information registered by RegisterWindow method.
class WindowPositionEmulator : public WindowPositionInterface {
 public:
  // Returns an instance of WindowPositionEmulator.
  static WindowPositionEmulator *Create();

  // Returns a dummy window handle for this emulator.  You can call methods of
  // WindowPositionInterface with this dummy handle.  You need not to release
  // the returned handle.
  virtual HWND RegisterWindow(const std::wstring &class_name,
                              const RECT &window_rect,
                              const POINT &client_area_offset,
                              const SIZE &client_area_size,
                              double scale_factor) = 0;

  virtual void SetRoot(HWND child_window, HWND root_window) = 0;
};

enum CompatibilityMode {
  // This flag represents the position can be calculated with default strategy.
  COMPATIBILITY_MODE_NONE = 0,
  // Some applications always keep CandidateForm up-to-date.  When this flag
  // is specified, CandidateForm is taken into account so that the suggest
  // window can be shown at more appropriate position.  Qt-based applications
  // match this category.
  CAN_USE_CANDIDATE_FORM_FOR_SUGGEST = 1,
  // Some applications interpret the coordinate system of CandidateForm as
  // the offset from the upper-left corner of the window as opposed to most
  // of other applications which simply use local coordinate.  When this flag
  // is specified, positional fields in CandidateForm are transformed as if
  // they are the offset from the upper-left corner of the window.  GTK-based
  // applications and Java AWT-based applications match this category.
  USE_LOCAL_COORD_FOR_CANDIDATE_FORM = 2,
  // Some applications such as V2C occasionally create zero-initialized
  // CANDIDATEFORM and maintain it regardless of the actual position of the
  // composition. This compatibility flag represents that it is better to
  // ignore this phantom CANDIDATEFORM.  Currently all the Java AWT-based
  // applications match this category.
  IGNORE_DEFAULT_COMPOSITION_FORM = 4,
  // Some applications such as NTEmacs22 or Meadow3.0 automatically and
  // frequently generates WM_IME_CONTROL/IMC_SETCOMPOSITIONWINDOW messages.
  // In this case, the renderer might not be able to show the InfoList
  // because new rendering messages will come before the InfoList is
  // displayed with the default delay (~500 msec). This compatibility flag
  // represents that we should show the InfoList immediately on such
  // applications. See b/5824433 for details.
  // TODO(horo, yukawa): Perhapes we can improve the rendering algorithm
  //     when the renderer is receiving the same messages successively.
  //     If we can safely ignore such successive messages, this flag
  //     can be removed.
  SHOW_INFOLIST_IMMEDIATELY = 8,
};

class LayoutManager {
 public:
  LayoutManager();
  ~LayoutManager();

  // A special constructor for unit tests.  You can set a mock object which
  // emulates native APIs for unit test.  This class is responsible for
  // deleting the mock objects passed.
  LayoutManager(SystemPreferenceInterface *mock_system_preference,
                WindowPositionInterface *mock_window_position);

  // Calculates layout of composition windows including preferred position of
  // the candidate window and text attributes such as underline or text
  // highlighting in the composition windows.  Returns true when succeeds.
  // This method is thread-safe.
  //  command: The renderer commant to be rendered.
  //  composition_window_layouts: calculated layout for composition windows.
  //  candidate_form: calculated position for the candidate window to be
  //    displayed.
  bool LayoutCompositionWindow(
      const commands::RendererCommand &command,
      std::vector<CompositionWindowLayout> *composition_window_layouts,
      CandidateWindowLayout *candidate_layout) const;

  // Returns compatibility bits for given target application.
  int GetCompatibilityMode(
      const commands::RendererCommand_ApplicationInfo &app_info);

  // Determines the position where the suggest window should be placed when the
  // composition window is drawn by the application.  This function does not
  // take DPI virtualization into account.  In other words, any positional
  // field in |candidate_form| is calculated in virtualized screen coordinates
  // for the target application window.
  bool LayoutCandidateWindowForSuggestion(
      const commands::RendererCommand_ApplicationInfo &app_info,
      CandidateWindowLayout *candidate_layout);

  // Determines the position where the candidate/predict window should be
  // placed when the composition window is drawn by the application.  This
  // function does not take DPI virtualization into account.  In other words,
  // any positional field in |candidate_form| is calculated in virtualized
  // screen coordinates for the target application window.
  bool LayoutCandidateWindowForConversion(
      const commands::RendererCommand_ApplicationInfo &app_info,
      CandidateWindowLayout *candidate_layout);

  // Converts a virtualized screen coordinate for the DPI-unaware application
  // specified by |window_handle| to the universal screen coordinate for
  // DPI-aware applications.  When the LogicalToPhysicalPoint API is not
  // available in the target platform, this function simply copies |point|
  // into |result|.  If LogicalToPhysicalPoint API fails due to some
  // limitations, this function tries to emulate the result assuming the
  // scaling factor is one dimension and scaling center is (0, 0).
  // See remarks of the following document for details about the limitations.
  // http://msdn.microsoft.com/en-us/library/ms633533.aspx
  // This method is thread-safe.
  void GetPointInPhysicalCoords(HWND window_handle, const POINT &point,
                                POINT *result) const;

  // RECT version of GetPointInPhysicalCoords.  This method is thread-safe.
  void GetRectInPhysicalCoords(HWND window_handle, const RECT &rect,
                               RECT *result) const;

  // Converts a local coordinate into a logical screen coordinate assuming
  // |src_point| is the relative offset from the top-left of the
  // window specified by the |src_window_handle|.
  // Returns false if fails.
  bool LocalPointToScreen(HWND src_window_handle, const POINT &src_point,
                          POINT *dest_point) const;

  // Converts a local coordinate into a logical screen coordinate assuming
  // |src_point| is the relative offset from the top-left of the
  // window specified by the |src_window_handle|.
  // Returns false if fails.
  bool LocalRectToScreen(HWND src_window_handle, const RECT &src_rect,
                         RECT *dest_rect) const;

  // Converts a local coordinate into a logical screen coordinate assuming
  // |src_point| is the client coorinate in the window specified by
  // |src_window_handle|.
  // Returns false if fails.
  bool ClientRectToScreen(HWND src_window_handle, const RECT &src_rect,
                          RECT *dest_rect) const;

  // Converts a local coordinate into a logical screen coordinate assuming
  // |src_point| is the client coorinate in the window specified by
  // |src_window_handle|.
  // Returns false if fails.
  bool ClientPointToScreen(HWND src_window_handle, const POINT &src_point,
                           POINT *dest_point) const;

  // Returns true if the client rect of the target window specified by
  // |src_window_handle| is retrieved in a logical screen coordinate.
  // Returns false if fails.
  bool GetClientRect(HWND window_handle, RECT *client_rect) const;

  // Returns the scaling factor for DPI Virtualization.
  // Returns 1.0 if any error occurs.
  double GetScalingFactor(HWND window_handle) const;

  // Returns true if the default GUI font is retrieved.
  // Returns false if fails.
  bool GetDefaultGuiFont(LOGFONTW *logfong) const;

  // Represents preferred writing direction, especially for composition string.
  enum WritingDirection {
    // The writing direction is not specified.
    WRITING_DIRECTION_UNSPECIFIED = 0,
    // Horizontal writing is specified.
    HORIZONTAL_WRITING = 1,
    // Vertical writing is specified.
    VERTICAL_WRITING = 2,
  };

  // Returns writing direction.
  static WritingDirection GetWritingDirection(
      const commands::RendererCommand_ApplicationInfo &app_info);

  // Returns true when the target rect is successfully obtained.
  bool LayoutIndicatorWindow(
      const commands::RendererCommand_ApplicationInfo &app_info,
      IndicatorWindowLayout *indicator_layout);

 private:
  FRIEND_TEST(Win32RendererUtilTest, HorizontalProportional);
  FRIEND_TEST(Win32RendererUtilTest, VerticalProportional);
  FRIEND_TEST(Win32RendererUtilTest, HorizontalMonospaced);
  FRIEND_TEST(Win32RendererUtilTest, VerticalMonospaced);
  FRIEND_TEST(Win32RendererUtilTest, HorizontalProportionalCompositeGlyph);
  FRIEND_TEST(Win32RendererUtilTest, VerticalProportionalCompositeGlyph);
  FRIEND_TEST(Win32RendererUtilTest, HorizontalMonospacedCompositeGlyph);
  FRIEND_TEST(Win32RendererUtilTest, VerticalMonospacedCompositeGlyph);

  // Calculates text layout with taking text wrapping into account.  Returns
  // true when succeeds.
  //  font: The font information to be used for calculation.
  //  str: The string to be calculated.  Non-printable characters are not fully
  //    supported.  For example, this function may not work well if |str|
  //    contains CR, LF, HT, and VT.
  //  maximum_line_length: The maximum length which limits the grows of text
  //    for each line.
  //  initial_offset: Represents cursor position where the first character in
  //    the |str| starts from if there is enough space to show the first
  //    character.
  //  LineLayout: Layout information for each split line.
  static bool CalcLayoutWithTextWrapping(const LOGFONTW &font,
                                         const std::wstring &text,
                                         int maximum_line_length,
                                         int initial_offset,
                                         std::vector<LineLayout> *line_layouts);

  std::unique_ptr<SystemPreferenceInterface> system_preference_;
  std::unique_ptr<WindowPositionInterface> window_position_;

  DISALLOW_COPY_AND_ASSIGN(LayoutManager);
};

}  // namespace win32
}  // namespace renderer
}  // namespace mozc

#endif  // OS_WIN
#endif  // MOZC_RENDERER_WIN32_WIN32_RENDERER_UTIL_H_
