// 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 "win32/ime/ime_candidate_info.h"

#include <safeint.h>
#include <strsafe.h>
#include <windows.h>  // windows.h must be included before strsafe.h

#include <algorithm>
#include <limits>

#include "google/protobuf/stubs/common.h"
#include "base/logging.h"
#include "base/util.h"
#include "protocol/commands.pb.h"
#include "win32/ime/ime_ui_window.h"

namespace mozc {
namespace win32 {
namespace {

using ::mozc::commands::CandidateList;
using ::mozc::commands::Candidates;

using ::msl::utilities::SafeAdd;
using ::msl::utilities::SafeCast;
using ::msl::utilities::SafeMultiply;
using ::msl::utilities::SafeSubtract;

// Since IMM32 uses DWORD rather than size_t for data size in data structures,
// relevant data size are stored into DWORD constants here.
static_assert(sizeof(DWORD) <= std::numeric_limits<int32>::max(),
              "Check DWORD size.");

const DWORD kSizeOfDWORD = static_cast<DWORD>(sizeof(DWORD));

static_assert(sizeof(wchar_t) <= std::numeric_limits<int32>::max(),
              "Check wchar_t size.");
const DWORD kSizeOfWCHAR = static_cast<DWORD>(sizeof(wchar_t));

static_assert(sizeof(CANDIDATEINFO) <= std::numeric_limits<int32>::max(),
              "Check CANDIDATEINFO size.");
const DWORD kSizeOfCANDIDATEINFO = static_cast<DWORD>(sizeof(CANDIDATEINFO));

static_assert(sizeof(CANDIDATELIST) <= std::numeric_limits<int32>::max(),
              "Check CANDIDATELIST size.");
const DWORD kSizeOfCANDIDATELIST = static_cast<DWORD>(sizeof(CANDIDATELIST));

static_assert(sizeof(CANDIDATELIST) > sizeof(DWORD),
              "Check CANDIDATELIST size.");
const DWORD kSizeOfCANDIDATELISTHeader =
    static_cast<DWORD>(sizeof(CANDIDATELIST) - sizeof(DWORD));

static_assert((static_cast<int64>(sizeof(CANDIDATEINFO)) +
               static_cast<int64>(sizeof(CANDIDATELIST))) <
              std::numeric_limits<int32>::max(),
              "Check CANDIDATEINFO + CANDIDATELIST size.");
const DWORD kSizeOfCANDIDATEINFOAndCANDIDATELIST =
    static_cast<DWORD>(sizeof(CANDIDATEINFO) + sizeof(CANDIDATELIST));

// Some games such as EMIL CHRONICLE ONLINE assumes that
// CANDIDATELIST::dwPageSize never be zero nor greater than 10 despite that
// the WDK document for IMM32 declares this field can be 0.  See b/3033499.
// We conform to those applications by always setting a safe number.
// Note that Office-IME 2010 always returns 9 CANDIDATELIST::dwPageSize
// regardless of the actual number of candidates.  So we use the same strategy.
const int kSafePageSize = 9;

bool GetCandidateCountInternal(const CANDIDATEINFO *info, DWORD buffer_size,
                               DWORD *count) {
  DCHECK_NE(nullptr, info);
  DCHECK_NE(nullptr, count);

  if (info->dwCount < 1) {
    return false;
  }

  // |count_data_offset = info->dwOffset[0] + offsetof(CANDIDATELIST, dwCount)|
  DWORD count_data_offset = info->dwOffset[0];
  if (!SafeAdd(count_data_offset, offsetof(CANDIDATELIST, dwCount),
               count_data_offset)) {
    return false;
  }

  // |count_next_data_offset = count_data_offset + sizeof(DWORD)|
  DWORD count_next_data_offset = count_data_offset;
  if (!SafeAdd(count_next_data_offset, kSizeOfDWORD, count_next_data_offset)) {
    return false;
  }

  // Check if the target memory block has sufficient size to contain
  // CANDIDATELIST::dwCount.
  if (count_next_data_offset > buffer_size) {
    return false;
  }

  // Dereference the data.
  *count = *reinterpret_cast<const DWORD *>(
      reinterpret_cast<const BYTE *>(info) + count_data_offset);

  return true;
}

bool GetCandidateCount(HIMCC candidate_info_handle, DWORD *count) {
  if (candidate_info_handle == nullptr) {
    return false;
  }
  if (count == nullptr) {
    return false;
  }

  // If the target memory block does not have sufficient size, stop
  // reading the content.
  const DWORD buffer_size = ::ImmGetIMCCSize(candidate_info_handle);
  if (buffer_size < kSizeOfCANDIDATEINFO) {
    return false;
  }

  const CANDIDATEINFO *info =
      static_cast<CANDIDATEINFO *>(::ImmLockIMCC(candidate_info_handle));
  if (info == nullptr) {
    return false;
  }

  const bool result = GetCandidateCountInternal(info, buffer_size, count);
  ::ImmUnlockIMCC(candidate_info_handle);
  return result;
}
}  // namespace

CandidateInfo::CandidateInfo()
    : candidate_info_size(0),
      candidate_list_size(0),
      count(0),
      selection(0),
      show_candidate(false) {}

void CandidateInfo::Clear() {
  candidate_info_size = 0;
  candidate_list_size = 0;
  count = 0;
  selection = 0;
  show_candidate = false;
  offsets.clear();
  text_buffer.clear();
}

void CandidateInfoUtil::SetSafeDefault(CandidateInfo *info) {
  DCHECK_NE(nullptr, info);
  info->Clear();
  info->candidate_info_size = kSizeOfCANDIDATEINFOAndCANDIDATELIST;
  info->candidate_list_size = kSizeOfCANDIDATELIST;
  info->offsets.push_back(0);
}

bool CandidateInfoUtil::Convert(const mozc::commands::Output &output,
                                CandidateInfo *info) {
  if (info == nullptr) {
    return false;
  }
  info->Clear();

  // Note that |output.all_candidate_words()| delivers the result of the
  // latest activities in the server while |output.candidates()| reflects an
  // expected content of the candidate-window-like UI.  Here is an example to
  // explain the difference between |output.all_candidate_words()| and
  // |output.candidates()|
  //   1. Type "あ"
  //       -> |all_candidate_words()| == empty
  //       -> |candidates()| == empty
  //   2. Hit space to convert.
  //       -> |all_candidate_words()| == [CONVERSION: "あ", "吾", ...]
  //       -> |candidates()| == empty
  //          (candidate window is still invisible)
  //   3. Hit space again.
  //       -> |all_candidate_words()| == [CONVERSION: "あ", "吾", ...]
  //       -> |candidates()|  == [CONVERSION: "あ", "吾", ...]
  //          (candidate window shows up)
  // As filed in b/2978825, candidate list should be updated when and only
  // when the candidate window is visible.  This is why |output.candidates()|
  // is used here.
  if (!output.has_candidates()) {
    return true;
  }

  const Candidates &candidates = output.candidates();
  if (candidates.has_category() &&
      (candidates.category() == mozc::commands::SUGGESTION)) {
    // If this is a suggest UI popup, we will not update the candidate info.
    return true;
  }

  // Even though the update timing should be determined by the availability of
  // |output.candidates()|, |output.all_candidate_words()| is preferable to
  // fill the candidate list itself because it contains all of the candidates
  // as opposed to |output.candidates()| which contains candidates which is in
  // the current page.
  if (!output.has_all_candidate_words()) {
    return false;
  }

  const CandidateList &candidate_list = output.all_candidate_words();

  if (!candidate_list.has_focused_index()) {
    return false;
  }

  if (!SafeCast(candidate_list.candidates_size(), info->count)) {
    return false;
  }

  DCHECK(candidate_list.has_focused_index());
  info->selection = candidate_list.focused_index();

  // |offset_buffer_size = sizeof(DWORD) * info.count|
  DWORD offset_buffer_size = 0;
  if (!SafeMultiply(kSizeOfDWORD, info->count, offset_buffer_size)) {
    return false;
  }

  // |text_buffer_initial_offset =
  //      sizeof(CANDIDATELIST) - sizeof(DWORD) + offset_buffer_size|
  DWORD text_buffer_initial_offset = kSizeOfCANDIDATELISTHeader;
  if (!SafeAdd(text_buffer_initial_offset, offset_buffer_size,
               text_buffer_initial_offset)) {
    return false;
  }

  DWORD text_buffer_size = 0;
  for (size_t i = 0; i < candidate_list.candidates_size(); ++i) {
    // Calculates the offset from the top of CANDIDATELIST.
    DWORD offset = text_buffer_initial_offset;
    if (!SafeAdd(offset, text_buffer_size, offset)) {
      return false;
    }
    info->offsets.push_back(offset);

    std::wstring value;
    if (mozc::Util::UTF8ToWide(candidate_list.candidates(i).value(), &value) ==
        0) {
      value.clear();
    }

    // Add '\0'
    value += L'\0';

    DWORD text_len = 0;
    // |text_len = sizeof(wchar_t) * value.size()|
    if (!SafeMultiply(kSizeOfWCHAR, value.size(), text_len)) {
      return false;
    }
    // |text_buffer_size += text_len|
    if (!SafeAdd(text_buffer_size, text_len, text_buffer_size)) {
      return false;
    }
    for (size_t char_index = 0; char_index < value.size(); ++char_index) {
      info->text_buffer.push_back(value[char_index]);
    }
  }

  // |candidate_list_size = (sizeof(CANDIDATELIST) - sizeof(DWORD))|
  DWORD candidate_list_size = kSizeOfCANDIDATELISTHeader;
  // |candidate_list_size += offset_buffer_size|
  if (!SafeAdd(candidate_list_size, offset_buffer_size, candidate_list_size)) {
    return false;
  }
  // |candidate_list_size += text_buffer_size|
  if (!SafeAdd(candidate_list_size, text_buffer_size, candidate_list_size)) {
    return false;
  }

  // |candidate_info_size = sizeof(CANDIDATEINFO)|
  DWORD candidate_info_size = kSizeOfCANDIDATEINFO;
  // |candidate_info_size += candidate_list_size|
  if (!SafeAdd(candidate_info_size, candidate_list_size, candidate_info_size)) {
    return false;
  }

  info->candidate_info_size = candidate_info_size;
  info->candidate_list_size = candidate_list_size;
  info->show_candidate = true;
  return true;
}

void CandidateInfoUtil::Write(const CandidateInfo &info,
                              CANDIDATEINFO *target) {
  if (target == nullptr) {
    return;
  }

  target->dwSize = info.candidate_info_size;
  if (info.candidate_list_size == 0) {
    target->dwCount = 0;
    target->dwPrivateOffset = 0;
    target->dwPrivateSize = 0;
    for (size_t i = 0; i < arraysize(target->dwOffset); ++i) {
      target->dwOffset[i] = 0;
    }
    return;
  }

  target->dwCount = 1;  // Only 1 Candidate Window
  target->dwPrivateOffset = 0;
  target->dwPrivateSize = 0;
  target->dwOffset[0] = kSizeOfCANDIDATEINFO;

  for (size_t i = 1; i < arraysize(target->dwOffset); ++i) {
    // CANDIDATELIST is to be placed just after CANDIDATEINFO.
    target->dwOffset[i] = 0;
  }

  CANDIDATELIST *candidate_list = reinterpret_cast<CANDIDATELIST *>(
      reinterpret_cast<BYTE *>(target) + kSizeOfCANDIDATEINFO);

  candidate_list->dwSize = info.candidate_list_size;
  candidate_list->dwStyle = IME_CAND_READ;
  candidate_list->dwCount = info.count;
  candidate_list->dwSelection = info.selection;
  // Emulate dwPageStart to work around b/4077022 because Mozc server
  // does not support paging. See b/1855733
  // Note that IMM32 Office IME 2010 sets 0 to |dwPageStart| unless
  // it receives NI_SETCANDIDATE_PAGESTART.
  // TODO(yukawa): Investigate further.
  candidate_list->dwPageStart =
      (info.selection / kSafePageSize) * kSafePageSize;
  candidate_list->dwPageSize = kSafePageSize;

  if (info.count > 0) {
    DWORD *offsets = &candidate_list->dwOffset[0];
    wchar_t *text_buffer = reinterpret_cast<wchar_t *>(
        reinterpret_cast<BYTE *>(candidate_list) + info.offsets[0]);
    copy(info.offsets.begin(), info.offsets.end(), offsets);
    copy(info.text_buffer.begin(), info.text_buffer.end(), text_buffer);
  }
}

HIMCC CandidateInfoUtil::Initialize(HIMCC current_handle) {
  CandidateInfo info;
  SetSafeDefault(&info);

  return UpdateCandidateInfo(current_handle, info);
}

HIMCC CandidateInfoUtil::Update(HIMCC current_handle,
                                const mozc::commands::Output &output,
                                std::vector<UIMessage> *messages) {
  CandidateInfo info;
  Convert(output, &info);

  // If the data is not initialized or too small for some reason,
  // replace it with harmful data just in case.
  if (info.candidate_info_size < kSizeOfCANDIDATEINFOAndCANDIDATELIST) {
    SetSafeDefault(&info);
  }

  bool was_empty = true;
  if (current_handle != nullptr) {
    DWORD previous_count = 0;
    if (GetCandidateCount(current_handle, &previous_count) &&
        previous_count != 0) {
      was_empty = false;
    }
  }

  HIMCC new_handle = UpdateCandidateInfo(current_handle, info);

  if (messages != nullptr) {
    if (was_empty && info.show_candidate) {
      messages->push_back(UIMessage(WM_IME_NOTIFY, IMN_OPENCANDIDATE, 1));
    }

    if (!was_empty && info.show_candidate) {
      messages->push_back(UIMessage(WM_IME_NOTIFY, IMN_CHANGECANDIDATE, 1));
    }

    if (!was_empty && !info.show_candidate) {
      messages->push_back(UIMessage(WM_IME_NOTIFY, IMN_CLOSECANDIDATE, 1));
    }
  }

  return new_handle;
}

HIMCC CandidateInfoUtil::UpdateCandidateInfo(HIMCC current_handle,
                                             const CandidateInfo &list) {
  DCHECK_GE(list.candidate_info_size, kSizeOfCANDIDATEINFOAndCANDIDATELIST);

  HIMCC new_handle = nullptr;
  if (current_handle == nullptr) {
    new_handle = ::ImmCreateIMCC(list.candidate_info_size);
  } else {
    new_handle = ::ImmReSizeIMCC(current_handle, list.candidate_info_size);
  }

  if (new_handle != nullptr) {
    CANDIDATEINFO *buffer =
        static_cast<CANDIDATEINFO *>(::ImmLockIMCC(new_handle));
    if (buffer != nullptr) {
      Write(list, buffer);
      ::ImmUnlockIMCC(new_handle);
    }
  }
  return new_handle;
}
}  // namespace win32
}  // namespace mozc
