// Copyright 2010-2020, 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/base/uninstall_helper.h"

#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <msctf.h>
#include <objbase.h>
#include <strsafe.h>

#include <iomanip>
#include <map>
#include <memory>
#include <sstream>

#include "base/logging.h"
#include "base/scoped_handle.h"
#include "base/system_util.h"
#include "base/win_util.h"
#include "win32/base/imm_registrar.h"
#include "win32/base/imm_util.h"
#include "win32/base/immdev.h"
#include "win32/base/tsf_profile.h"

namespace mozc {
namespace win32 {
using ATL::CComPtr;
using ATL::CRegKey;
using std::unique_ptr;

namespace {

typedef std::map<int, DWORD> PreloadOrderToKLIDMap;

// Windows NT 6.0, 6.1 and 6.2
const CLSID CLSID_IMJPTIP = {0x03b5835f,
                             0xf03c,
                             0x411b,
                             {0x9c, 0xe2, 0xaa, 0x23, 0xe1, 0x17, 0x1e, 0x36}};
const GUID GUID_IMJPTIP = {0xa76c93d9,
                           0x5523,
                           0x4e90,
                           {0xaa, 0xfa, 0x4d, 0xb1, 0x12, 0xf9, 0xac, 0x76}};

const LANGID kLANGJaJP = MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);

const wchar_t kRegKeyboardLayouts[] =
    L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts";
const wchar_t kPreloadKeyName[] = L"Keyboard Layout\\Preload";

// Registry element size limits are described in the link below.
// http://msdn.microsoft.com/en-us/library/ms724872(VS.85).aspx
const DWORD kMaxValueNameLength = 16383;

// Timeout value used by a work around against b/5765783.
// Note that the following timeout threshold is not well tested.
// TODO(yukawa): Investigate the best timeout threshold. b/6165722
const uint32 kWaitForAsmCacheReadyEventTimeout = 10000;  // 10 sec.

// Converts an unsigned integer to a wide string.
std::wstring utow(unsigned int i) {
  std::wstringstream ss;
  ss << i;
  return ss.str();
}

std::wstring GetIMEFileNameFromKeyboardLayout(const CRegKey &key,
                                              const KeyboardLayoutID &klid) {
  CRegKey subkey;
  LONG result = subkey.Open(key, klid.ToString().c_str(), KEY_READ);
  if (ERROR_SUCCESS != result) {
    return L"";
  }

  wchar_t filename_buffer[kMaxValueNameLength];
  ULONG filename_length_including_null = kMaxValueNameLength;
  result = subkey.QueryStringValue(L"Ime File", filename_buffer,
                                   &filename_length_including_null);

  // Note that |filename_length_including_null| contains NUL terminator.
  if ((ERROR_SUCCESS != result) || (filename_length_including_null <= 1)) {
    return L"";
  }

  const ULONG filename_length = (filename_length_including_null - 1);
  const std::wstring filename(filename_buffer);

  // Note that |filename_length| does not contain NUL character.
  DCHECK_EQ(filename_length, filename.size());
  return filename;
}

bool GenerateKeyboardLayoutList(
    std::vector<KeyboardLayoutInfo> *keyboard_layouts) {
  if (keyboard_layouts == nullptr) {
    return false;
  }
  keyboard_layouts->clear();

  CRegKey key;
  LONG result = key.Open(HKEY_LOCAL_MACHINE, kRegKeyboardLayouts, KEY_READ);
  if (ERROR_SUCCESS != result) {
    return false;
  }

  wchar_t value_name[kMaxValueNameLength];
  for (DWORD enum_reg_index = 0;; ++enum_reg_index) {
    DWORD value_name_length = kMaxValueNameLength;
    result = key.EnumKey(enum_reg_index, value_name, &value_name_length);
    if (ERROR_NO_MORE_ITEMS == result) {
      return true;
    }
    if (ERROR_SUCCESS != result) {
      return true;
    }

    // Note that |value_name_length| does not contain NUL character.
    const KeyboardLayoutID klid(
        std::wstring(value_name, value_name + value_name_length));

    if (!klid.has_id()) {
      continue;
    }

    KeyboardLayoutInfo info;
    info.klid = klid.id();
    info.ime_filename = GetIMEFileNameFromKeyboardLayout(key, klid);
    keyboard_layouts->push_back(info);
  }
  return true;
}

bool GenerateKeyboardLayoutMap(
    std::map<DWORD, std::wstring> *keyboard_layouts) {
  if (keyboard_layouts == nullptr) {
    return false;
  }
  std::vector<KeyboardLayoutInfo> keyboard_layout_list;
  if (!GenerateKeyboardLayoutList(&keyboard_layout_list)) {
    return false;
  }

  for (size_t i = 0; i < keyboard_layout_list.size(); ++i) {
    const KeyboardLayoutInfo &layout = keyboard_layout_list[i];
    (*keyboard_layouts)[layout.klid] = layout.ime_filename;
  }

  return true;
}

std::wstring GetIMEFileName(HKL hkl) {
  const UINT num_chars_without_null = ::ImmGetIMEFileName(hkl, nullptr, 0);
  const size_t num_chars_with_null = num_chars_without_null + 1;
  unique_ptr<wchar_t[]> buffer(new wchar_t[num_chars_with_null]);
  const UINT num_copied =
      ::ImmGetIMEFileName(hkl, buffer.get(), num_chars_with_null);

  // |num_copied| does not include terminating null character.
  return std::wstring(buffer.get(), buffer.get() + num_copied);
}

bool GetInstalledProfilesByLanguageForTSF(
    LANGID langid, std::vector<LayoutProfileInfo> *installed_profiles) {
  ScopedCOMInitializer com_initializer;
  if (FAILED(com_initializer.error_code())) {
    return false;
  }

  HRESULT hr = S_OK;

  CComPtr<ITfInputProcessorProfiles> profiles;
  hr = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
  if (FAILED(hr)) {
    return false;
  }

  CComPtr<IEnumTfLanguageProfiles> enum_profiles;
  hr = profiles->EnumLanguageProfiles(langid, &enum_profiles);
  if (FAILED(hr)) {
    return false;
  }

  while (true) {
    ULONG num_fetched = 0;
    TF_LANGUAGEPROFILE src = {0};
    hr = enum_profiles->Next(1, &src, &num_fetched);
    if (FAILED(hr)) {
      return false;
    }
    if ((hr == S_FALSE) || (num_fetched != 1)) {
      break;
    }

    if (src.catid != GUID_TFCAT_TIP_KEYBOARD) {
      continue;
    }
    LayoutProfileInfo profile;
    profile.langid = src.langid;
    profile.is_default = (src.fActive != FALSE);
    BOOL enabled = FALSE;
    hr = profiles->IsEnabledLanguageProfile(src.clsid, langid, src.guidProfile,
                                            &enabled);
    if (SUCCEEDED(hr)) {
      profile.is_enabled = (enabled != FALSE);
    }
    profile.clsid = src.clsid;
    profile.profile_guid = src.guidProfile;
    profile.is_tip = true;
    installed_profiles->push_back(profile);
  }

  return true;
}

bool GetInstalledProfilesByLanguageForIMM32(
    LANGID langid, std::vector<LayoutProfileInfo> *installed_profiles) {
  std::vector<KeyboardLayoutInfo> keyboard_layouts;
  if (!GenerateKeyboardLayoutList(&keyboard_layouts)) {
    DLOG(ERROR) << "GenerateKeyboardLayoutList failed.";
    return false;
  }

  for (size_t i = 0; i < keyboard_layouts.size(); ++i) {
    const KeyboardLayoutInfo &info = keyboard_layouts[i];
    const LANGID info_langid = static_cast<LANGID>(info.klid & 0xffff);
    if (info_langid == langid) {
      LayoutProfileInfo profile;
      profile.langid = langid;
      profile.is_tip = false;
      profile.klid = info.klid;
      // TODO(yukawa): determine |profile.is_default|
      // TODO(yukawa): determine |profile.is_enabled|
      profile.ime_filename = info.ime_filename;
      installed_profiles->push_back(profile);
    }
  }

  return true;
}

bool GetPreloadLayoutsMain(PreloadOrderToKLIDMap *preload_map) {
  if (preload_map == nullptr) {
    return false;
  }

  // Retrieve keys under kPreloadKeyName.
  CRegKey preload_key;
  LONG result = preload_key.Open(HKEY_CURRENT_USER, kPreloadKeyName);
  if (ERROR_SUCCESS != result) {
    return false;
  }

  wchar_t value_name[kMaxValueNameLength];
  const DWORD kMaxValueLength = 256;
  BYTE value[kMaxValueLength];
  for (DWORD i = 0;; ++i) {
    DWORD value_name_length = kMaxValueNameLength;
    DWORD value_length = kMaxValueLength;
    result = RegEnumValue(preload_key, i, value_name, &value_name_length,
                          nullptr,  // reserved (must be nullptr)
                          nullptr,  // type (optional)
                          value, &value_length);
    if (ERROR_NO_MORE_ITEMS == result) {
      break;
    }
    if (ERROR_SUCCESS != result) {
      return false;
    }

    const int ivalue_name = _wtoi(value_name);
    const std::wstring wvalue(reinterpret_cast<wchar_t *>(value),
                              (value_length / sizeof(wchar_t)) - 1);
    KeyboardLayoutID klid(wvalue);
    if (!klid.has_id()) {
      continue;
    }
    (*preload_map)[ivalue_name] = klid.id();
  }
  return true;
}

std::wstring GUIDToString(const GUID &guid) {
  wchar_t buffer[256];
  const int character_length_with_null =
      ::StringFromGUID2(guid, buffer, arraysize(buffer));
  if (character_length_with_null <= 0) {
    return L"";
  }

  const size_t character_length_without_null = character_length_with_null - 1;
  return std::wstring(buffer, buffer + character_length_without_null);
}

std::wstring LANGIDToString(LANGID langid) {
  wchar_t buffer[5];
  HRESULT hr = ::StringCchPrintf(buffer, arraysize(buffer), L"%04x", langid);
  if (FAILED(hr)) {
    return L"";
  }
  return buffer;
}

bool BroadcastNewIME(const KeyboardLayoutInfo &layout) {
  KeyboardLayoutID klid(layout.klid);

  // We cannot use const HKL because |&mozc_hkl| will be cast into PVOID.
  HKL hkl = ::LoadKeyboardLayout(klid.ToString().c_str(), KLF_ACTIVATE);

  // SPI_SETDEFAULTINPUTLANG ensures that new process in this session will
  // use |hkl| by default but this setting is volatile even if you specified
  // SPIF_UPDATEINIFILE flag.
  // SPI_SETDEFAULTINPUTLANG does not work perfectly for a HKL substituted
  // by a TIP on Windows XP.  It works for notepad but wordpad still uses
  // the previous layout.  Consider to use
  // ITfInputProcessorProfiles::SetDefaultLanguageProfile for TIP backed
  // layout.
  if (::SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &hkl,
                             SPIF_SENDCHANGE) == FALSE) {
    LOG(ERROR) << "SystemParameterInfo failed: " << GetLastError();
    return false;
  }

  // A work around against b/5765783.
  if (!ImeUtil::WaitForAsmCacheReady(kWaitForAsmCacheReadyEventTimeout)) {
    DLOG(ERROR) << "ImeUtil::WaitForAsmCacheReady failed.";
  }

  // Broadcasting WM_INPUTLANGCHANGEREQUEST so that existing process in the
  // current session will change their input method to |hkl|. This mechanism
  // also works against a HKL which is substituted by a TIP on Windows XP.
  // Note: we have virtually the same code in imm_util.cc too.
  // TODO(yukawa): Make a common function around WM_INPUTLANGCHANGEREQUEST.
  DWORD recipients = BSM_APPLICATIONS;
  const LONG result = ::BroadcastSystemMessage(
      BSF_POSTMESSAGE, &recipients, WM_INPUTLANGCHANGEREQUEST,
      INPUTLANGCHANGE_SYSCHARSET, reinterpret_cast<LPARAM>(hkl));
  if (result == 0) {
    const int error = ::GetLastError();
    LOG(ERROR) << "BroadcastSystemMessage failed. error = " << error;
    return false;
  }

  return true;
}

bool BroadcastNewTIPOnVista(const LayoutProfileInfo &profile) {
  ScopedCOMInitializer com_initializer;
  if (FAILED(com_initializer.error_code())) {
    return false;
  }

  CComPtr<ITfInputProcessorProfileMgr> profile_manager;

  HRESULT hr = S_OK;
  hr = profile_manager.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
  if (FAILED(hr)) {
    return false;
  }

  const DWORD activate_flags = TF_IPPMF_FORSESSION | TF_IPPMF_ENABLEPROFILE |
                               TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE;

  hr = profile_manager->ActivateProfile(
      TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, profile.clsid,
      profile.profile_guid, nullptr, activate_flags);
  if (FAILED(hr)) {
    return false;
  }

  return true;
}

bool EnableAndBroadcastNewLayout(const LayoutProfileInfo &profile,
                                 bool broadcast_change) {
  if (profile.is_tip) {
    ScopedCOMInitializer com_initializer;
    if (FAILED(com_initializer.error_code())) {
      return false;
    }

    CComPtr<ITfInputProcessorProfileMgr> profile_manager;

    HRESULT hr = S_OK;
    hr = profile_manager.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
    if (FAILED(hr)) {
      return false;
    }

    DWORD activate_flags =
        TF_IPPMF_ENABLEPROFILE | TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE;

    if (broadcast_change) {
      activate_flags |= TF_IPPMF_FORSESSION;
    }
    hr = profile_manager->ActivateProfile(
        TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, profile.clsid,
        profile.profile_guid, nullptr, activate_flags);
    if (FAILED(hr)) {
      DLOG(ERROR) << "ActivateProfile failed";
      return false;
    }

    return true;
  }

  // |profile| is IME.
  if (!broadcast_change) {
    return true;
  }

  KeyboardLayoutInfo layout;
  layout.klid = profile.klid;
  layout.ime_filename = profile.ime_filename;
  if (!BroadcastNewIME(layout)) {
    DLOG(ERROR) << "BroadcastNewIME failed";
    return false;
  }
  return true;
}

bool GetActiveKeyboardLayouts(std::vector<HKL> *keyboard_layouts) {
  if (keyboard_layouts == nullptr) {
    return false;
  }
  keyboard_layouts->clear();

  const int num_keyboard_layout = ::GetKeyboardLayoutList(0, nullptr);
  unique_ptr<HKL[]> buffer(new HKL[num_keyboard_layout]);
  const int num_copied =
      ::GetKeyboardLayoutList(num_keyboard_layout, buffer.get());
  keyboard_layouts->assign(buffer.get(), buffer.get() + num_copied);

  return true;
}

void EnableAndSetDefaultIfLayoutIsTIP(const KeyboardLayoutInfo &layout) {
  ScopedCOMInitializer com_initializer;
  if (FAILED(com_initializer.error_code())) {
    return;
  }

  HRESULT hr = S_OK;

  CComPtr<ITfInputProcessorProfiles> profiles;
  hr = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
  if (FAILED(hr)) {
    return;
  }
  CComPtr<ITfInputProcessorProfileSubstituteLayout> substitute_layout;
  hr = profiles.QueryInterface(&substitute_layout);
  if (FAILED(hr)) {
    return;
  }

  CComPtr<IEnumTfLanguageProfiles> enum_profiles;
  const LANGID langid = static_cast<LANGID>(layout.klid);
  hr = profiles->EnumLanguageProfiles(langid, &enum_profiles);
  if (FAILED(hr)) {
    return;
  }

  while (true) {
    ULONG num_fetched = 0;
    TF_LANGUAGEPROFILE profile = {0};
    hr = enum_profiles->Next(1, &profile, &num_fetched);
    if (FAILED(hr)) {
      return;
    }
    if ((hr == S_FALSE) || (num_fetched != 1)) {
      return;
    }

    if (profile.catid != GUID_TFCAT_TIP_KEYBOARD) {
      continue;
    }

    HKL hkl = nullptr;
    hr = substitute_layout->GetSubstituteKeyboardLayout(
        profile.clsid, profile.langid, profile.guidProfile, &hkl);
    if (FAILED(hr)) {
      DLOG(ERROR) << "GetSubstituteKeyboardLayout failed";
      continue;
    }
    if (!WinUtil::SystemEqualString(GetIMEFileName(hkl), layout.ime_filename,
                                    true)) {
      continue;
    }

    BOOL enabled = TRUE;
    hr = profiles->EnableLanguageProfile(profile.clsid, profile.langid,
                                         profile.guidProfile, TRUE);
    if (FAILED(hr)) {
      DLOG(ERROR) << "EnableLanguageProfile failed";
      continue;
    }

    hr = profiles->SetDefaultLanguageProfile(profile.langid, profile.clsid,
                                             profile.guidProfile);
    if (FAILED(hr)) {
      DLOG(ERROR) << "SetDefaultLanguageProfile failed";
      continue;
    }
    return;
  }
  return;
}

// This function lists all the active keyboard layouts up and unloads each
// layout based on the specified condition.  If |exclude| is true, this
// function unloads any active IME if it is included in |ime_filenames|.
// If |exclude| is false, this function unloads any active IME unless it is
// included in |ime_filenames|.
void UnloadActivatedKeyboardMain(const std::vector<std::wstring> &ime_filenames,
                                 bool exclude) {
  std::vector<HKL> loaded_layouts;
  if (!GetActiveKeyboardLayouts(&loaded_layouts)) {
    return;
  }
  for (size_t i = 0; i < loaded_layouts.size(); ++i) {
    const HKL hkl = loaded_layouts[i];
    const std::wstring ime_filename = GetIMEFileName(hkl);
    if (ime_filename.empty()) {
      continue;
    }
    bool can_unload = !exclude;
    for (size_t j = 0; j < ime_filenames.size(); ++j) {
      if (WinUtil::SystemEqualString(ime_filename, ime_filenames[j], true)) {
        can_unload = exclude;
        break;
      }
    }
    if (can_unload) {
      ::UnloadKeyboardLayout(hkl);
    }
  }
}

void UnloadProfilesForVista(
    const std::vector<LayoutProfileInfo> &profiles_to_be_removed) {
  std::vector<std::wstring> ime_filenames;
  for (size_t i = 0; i < profiles_to_be_removed.size(); ++i) {
    ime_filenames.push_back(profiles_to_be_removed[i].ime_filename);
  }
  UnloadActivatedKeyboardMain(ime_filenames, true);
}

bool IsEqualProfile(const LayoutProfileInfo &lhs,
                    const LayoutProfileInfo &rhs) {
  // Check if the profile type (TIP or IME) is the same.
  if (lhs.is_tip != rhs.is_tip) {
    return false;
  }
  // Check if the target language is the same.
  if (lhs.langid != rhs.langid) {
    return false;
  }

  if (lhs.is_tip) {
    // If both of them are TIP, check if they have the same CLSID and Profile
    // GUID.  Otherwise, they are different from each other.
    DCHECK(rhs.is_tip);
    if (::IsEqualCLSID(lhs.clsid, rhs.clsid) == 0) {
      return false;
    }
    if (::IsEqualGUID(lhs.profile_guid, rhs.profile_guid) == 0) {
      return false;
    }
    return true;
  }

  // If both of them are IME, check if they have the same KLID and IME file
  // name (if any).  Otherwise, they are different from each other.
  DCHECK(!lhs.is_tip);
  DCHECK(!rhs.is_tip);
  if (lhs.klid != rhs.klid) {
    return false;
  }
  if (!WinUtil::SystemEqualString(lhs.ime_filename.c_str(),
                                  rhs.ime_filename.c_str(), true)) {
    return false;
  }
  return true;
}

bool IsEqualPreload(
    const PreloadOrderToKLIDMap &current_preload_map,
    const std::vector<KeyboardLayoutInfo> &new_preload_layouts) {
  if (current_preload_map.size() != new_preload_layouts.size()) {
    return false;
  }

  int index = 0;
  for (PreloadOrderToKLIDMap::const_iterator it = current_preload_map.begin();
       it != current_preload_map.end(); ++it) {
    const DWORD klid = it->second;
    DCHECK_GE(index, 0);
    DCHECK_LT(index, new_preload_layouts.size());
    if (klid != new_preload_layouts[index].klid) {
      return false;
    }
    ++index;
  }

  return true;
}

// Currently only keyboard layouts which have IME filename are supported.
bool RemoveHotKeyForIME(
    const std::vector<KeyboardLayoutInfo> &layouts_to_be_removed) {
  bool succeeded = true;
  for (DWORD id = IME_HOTKEY_DSWITCH_FIRST; id <= IME_HOTKEY_DSWITCH_LAST;
       ++id) {
    UINT modifiers = 0;
    UINT virtual_key = 0;
    HKL hkl = nullptr;
    BOOL result = ::ImmGetHotKey(id, &modifiers, &virtual_key, &hkl);
    if (result == FALSE) {
      continue;
    }
    if (hkl == nullptr) {
      continue;
    }
    const std::wstring ime_name = GetIMEFileName(hkl);
    for (size_t i = 0; i < layouts_to_be_removed.size(); ++i) {
      const KeyboardLayoutInfo &layout = layouts_to_be_removed[i];
      if (layout.ime_filename.empty()) {
        continue;
      }
      if (!WinUtil::SystemEqualString(layout.ime_filename, ime_name, true)) {
        continue;
      }
      // ImmSetHotKey fails when both 2nd and 3rd arguments are valid while 4th
      // argument is nullptr.  To remove the HotKey, pass 0 to them.
      result = ::ImmSetHotKey(id, 0, 0, nullptr);
      if (result == FALSE) {
        succeeded = false;
      }
      break;
    }
  }
  return succeeded;
}

// Currently this function is Mozc-specific.
// TODO(yukawa): Generalize this function for any IME.
void RemoveHotKeyForVista(
    const std::vector<LayoutProfileInfo> &installed_profiles) {
  std::vector<KeyboardLayoutInfo> hotkey_remove_targets;
  for (size_t i = 0; i < installed_profiles.size(); ++i) {
    const LayoutProfileInfo &profile = installed_profiles[i];
    if (!profile.is_tip &&
        WinUtil::SystemEqualString(profile.ime_filename,
                                   ImmRegistrar::GetFileNameForIME(), true)) {
      // This is the full IMM32 version of Google Japanese Input.
      KeyboardLayoutInfo info;
      info.klid = profile.klid;
      info.ime_filename = profile.ime_filename;
      hotkey_remove_targets.push_back(info);
      continue;
    }
  }

  if (!RemoveHotKeyForIME(hotkey_remove_targets)) {
    DLOG(ERROR) << "RemoveHotKeyForIME failed.";
  }
}

}  // namespace

KeyboardLayoutInfo::KeyboardLayoutInfo() : klid(0) {}

LayoutProfileInfo::LayoutProfileInfo()
    : langid(0),
      clsid(CLSID_NULL),
      profile_guid(GUID_NULL),
      klid(0),
      is_default(false),
      is_tip(false),
      is_enabled(false) {}

// Currently this function is Mozc-specific.
// TODO(yukawa): Generalize this function for any IME and/or TIP.
bool UninstallHelper::GetNewEnabledProfileForVista(
    const std::vector<LayoutProfileInfo> &current_profiles,
    const std::vector<LayoutProfileInfo> &installed_profiles,
    LayoutProfileInfo *current_default, LayoutProfileInfo *new_default,
    std::vector<LayoutProfileInfo> *removed_profiles) {
  if (current_default == nullptr) {
    return false;
  }
  // Initialize in case no entry is marked as default.
  *current_default = LayoutProfileInfo();

  if (new_default == nullptr) {
    return false;
  }
  if (removed_profiles == nullptr) {
    return false;
  }
  removed_profiles->clear();

  bool default_found = false;
  bool default_set = false;
  for (size_t i = 0; i < current_profiles.size(); ++i) {
    const LayoutProfileInfo &profile = current_profiles[i];
    if (profile.is_default) {
      *current_default = profile;
    }

    if (!profile.is_tip &&
        WinUtil::SystemEqualString(profile.ime_filename,
                                   ImmRegistrar::GetFileNameForIME(), true)) {
      // This is the full IMM32 version of Google Japanese Input.
      removed_profiles->push_back(profile);
      continue;
    }
    if (profile.is_tip &&
        ::IsEqualCLSID(TsfProfile::GetTextServiceGuid(), profile.clsid) &&
        ::IsEqualGUID(TsfProfile::GetProfileGuid(), profile.profile_guid)) {
      // This is the full TSF version of Google Japanese Input.
      removed_profiles->push_back(profile);
      continue;
    }

    if (!default_found && profile.is_enabled && profile.is_default) {
      default_found = true;
      default_set = true;
      *new_default = profile;
    }

    if (!default_found && !default_set && profile.is_enabled) {
      default_set = true;
      *new_default = profile;
    }
  }

  if (!default_set) {
    // TODO(yukawa): Consider this case.
    // Use MS-IME as a fallback.
    new_default->langid = MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);
    new_default->clsid = CLSID_IMJPTIP;
    new_default->profile_guid = GUID_IMJPTIP;
    new_default->klid = 0;
    new_default->ime_filename.clear();
    new_default->is_default = true;
    new_default->is_tip = true;
    new_default->is_enabled = false;
  }

  return true;
}

bool UninstallHelper::GetInstalledProfilesByLanguage(
    LANGID langid, std::vector<LayoutProfileInfo> *installed_profiles) {
  if (installed_profiles == nullptr) {
    return false;
  }
  installed_profiles->clear();

  if (!GetInstalledProfilesByLanguageForTSF(langid, installed_profiles)) {
    // Actually this can fail if user have explicitly unregistered TSF modules
    // like b/2636769.  We do not return false because it would be better to
    // continue with the result of GetInstalledProfilesByLanguageForIMM32.
    LOG(ERROR) << "GetInstalledProfilesByLanguageForTSF failed.";
  }

  if (!GetInstalledProfilesByLanguageForIMM32(langid, installed_profiles)) {
    LOG(ERROR) << "GetInstalledProfilesByLanguageForIMM32 failed.";
    return false;
  }

  return true;
}

bool UninstallHelper::GetCurrentProfilesForVista(
    std::vector<LayoutProfileInfo> *current_profiles) {
  if (current_profiles == nullptr) {
    return false;
  }
  current_profiles->clear();

  std::map<DWORD, std::wstring> keyboard_layouts;
  if (!GenerateKeyboardLayoutMap(&keyboard_layouts)) {
    return false;
  }

  {
    const UINT num_element =
        ::EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, nullptr, 0);
    unique_ptr<LAYOUTORTIPPROFILE[]> buffer(
        new LAYOUTORTIPPROFILE[num_element]);
    const UINT num_copied = ::EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr,
                                                     buffer.get(), num_element);

    for (size_t i = 0; i < num_copied; ++i) {
      const LAYOUTORTIPPROFILE &src = buffer[i];
      if (src.catid != GUID_TFCAT_TIP_KEYBOARD) {
        continue;
      }

      LayoutProfileInfo profile;
      profile.langid = src.langid;
      profile.is_default = ((src.dwFlags & LOT_DEFAULT) == LOT_DEFAULT);
      profile.is_enabled = !((src.dwFlags & LOT_DISABLED) == LOT_DISABLED);
      profile.clsid = src.clsid;
      profile.profile_guid = src.guidProfile;

      if ((src.dwProfileType & LOTP_INPUTPROCESSOR) == LOTP_INPUTPROCESSOR) {
        profile.is_tip = true;
        current_profiles->push_back(profile);
        continue;
      }

      if ((src.dwProfileType & LOTP_KEYBOARDLAYOUT) == LOTP_KEYBOARDLAYOUT) {
        const std::wstring id(src.szId);
        // A valid |profile.szId| should consists of language ID (LANGID) and
        // keyboard layout ID (KILD) as follows.
        //  <LangID 1>:<KLID 1>
        //       "0411:E0200411"
        // Check if |id.size()| is expected.
        if (id.size() != 13) {
          continue;
        }
        // Extract KLID.  It should be 8-letter hexadecimal code begins at 6th
        // character in the |id|, that is, |id.substr(5, 8)|.
        const KeyboardLayoutID klid(id.substr(5, 8));
        if (!klid.has_id()) {
          continue;
        }
        profile.klid = klid.id();
        if (keyboard_layouts.find(profile.klid) != keyboard_layouts.end()) {
          profile.ime_filename = keyboard_layouts[profile.klid];
        }
        profile.is_tip = false;
        current_profiles->push_back(profile);
        continue;
      }
    }
  }  // release |buffer|

  return true;
}

bool UninstallHelper::RemoveProfilesForVista(
    const std::vector<LayoutProfileInfo> &profiles_to_be_removed) {
  if (profiles_to_be_removed.size() == 0) {
    // Nothing to do.
    return true;
  }

  const std::wstring &profile_string =
      ComposeProfileStringForVista(profiles_to_be_removed);

  const BOOL result = ::InstallLayoutOrTipUserReg(
      nullptr, nullptr, nullptr, profile_string.c_str(), ILOT_UNINSTALL);

  return result != FALSE;
}

std::wstring UninstallHelper::ComposeProfileStringForVista(
    const std::vector<LayoutProfileInfo> &profiles) {
  std::wstringstream ss;
  for (size_t i = 0; i < profiles.size(); ++i) {
    const LayoutProfileInfo &info = profiles[i];
    if (i != 0) {
      ss << L";";
    }

    const std::wstring &langid_string = LANGIDToString(info.langid);
    if (langid_string.empty()) {
      continue;
    }

    if (info.is_tip) {
      const std::wstring &clsid_string = GUIDToString(info.clsid);
      const std::wstring &guid_string = GUIDToString(info.profile_guid);
      if (clsid_string.empty() || guid_string.empty()) {
        continue;
      }
      ss << langid_string << L":" << clsid_string << guid_string;
    } else {
      const KeyboardLayoutID klid(info.klid);
      ss << langid_string << L":" << klid.ToString();
    }
  }
  return ss.str();
}

bool UninstallHelper::SetDefaultForVista(
    const LayoutProfileInfo &current_default,
    const LayoutProfileInfo &new_default, bool broadcast_change) {
  if (current_default.is_default && current_default.is_enabled &&
      IsEqualProfile(current_default, new_default)) {
    // |new_default| is alreasy default and enabled.
    return true;
  }

  if (!EnableAndBroadcastNewLayout(new_default, broadcast_change)) {
    // We do not return false here because the main task of this function is
    // setting the specified profile to default.
    DLOG(ERROR) << "EnableAndBroadcastNewLayout failed.";
  }

  std::vector<LayoutProfileInfo> profile_list;
  profile_list.push_back(new_default);
  const std::wstring profile_string =
      ComposeProfileStringForVista(profile_list);
  if (profile_string.empty()) {
    return false;
  }

  DWORD flag = 0;
  if (!broadcast_change) {
    // If |broadcast_change| prevent the SetDefaultLayoutOrTip API from
    // disturbing the current session in case the thread is impersonated.
    flag = SDLOT_NOAPPLYTOCURRENTSESSION;
  }

  if (!::SetDefaultLayoutOrTip(profile_string.c_str(), flag)) {
    DLOG(ERROR) << "SetDefaultLayoutOrTip failed";
    return false;
  }

  return true;
}

bool UninstallHelper::RestoreUserIMEEnvironmentForVista(bool broadcast_change) {
  std::vector<LayoutProfileInfo> installed_profiles;
  if (!GetInstalledProfilesByLanguage(kLANGJaJP, &installed_profiles)) {
    return false;
  }

  RemoveHotKeyForVista(installed_profiles);

  std::vector<LayoutProfileInfo> current_profiles;
  if (!GetCurrentProfilesForVista(&current_profiles)) {
    return false;
  }
  LayoutProfileInfo current_default;
  LayoutProfileInfo new_default;
  std::vector<LayoutProfileInfo> removed_profiles;
  if (!GetNewEnabledProfileForVista(current_profiles, installed_profiles,
                                    &current_default, &new_default,
                                    &removed_profiles)) {
    return false;
  }
  if (!SetDefaultForVista(current_default, new_default, broadcast_change)) {
    DLOG(ERROR) << "SetDefaultForVista failed.";
  }
  if (!RemoveProfilesForVista(removed_profiles)) {
    DLOG(ERROR) << "RemoveProfilesForVista failed.";
  }
  if (broadcast_change) {
    // Unload unnecessary keyboard layouts.
    UnloadProfilesForVista(removed_profiles);
  }
  return true;
}

bool UninstallHelper::RestoreUserIMEEnvironmentMain() {
  // Basically this function is called from the "non-deferred" custom action
  // with "non-elevated" user privileges, which is enough and prefarable to
  // update entries under HKCU.  Assuming the desktop is available,
  // broadcast messages into existing processes so that these processes
  // start to use the new default IME.
  const bool kBroadcastNewIME = true;

  return RestoreUserIMEEnvironmentForVista(kBroadcastNewIME);
}

bool UninstallHelper::EnsureIMEIsRemovedForCurrentUser(
    bool disable_hkcu_cache) {
  if (disable_hkcu_cache) {
    // For some reason, HKCU in a deferred, non-impersonated custom action is
    // occasionally mapped not to the HKU/.Default but to the active user's
    // profile as if the current thread is impersonated to the active user.
    // To fix up this, we should disable per-process registry cache by calling
    // RegDisablePredefinedCache API.
    const LRESULT result = ::RegDisablePredefinedCache();
    if (result != ERROR_SUCCESS) {
      DLOG(ERROR) << "IsServiceThread failed.  result = " << result;
      return false;
    }
  }

  // Since this function is targeting for the service account, IME change
  // notification will not be sent in case it causes unwilling side-effects
  // against other important processes running in the service session.
  const bool kBroadcastNewIME = false;
  return RestoreUserIMEEnvironmentForVista(kBroadcastNewIME);
}

}  // namespace win32
}  // namespace mozc
