// 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 "dictionary/user_dictionary_session.h"

#include <algorithm>
#include <cstdint>
#include <memory>

#include "base/logging.h"
#include "base/port.h"
#include "base/protobuf/protobuf.h"
#include "base/protobuf/repeated_field.h"
#include "dictionary/user_dictionary_importer.h"
#include "dictionary/user_dictionary_storage.h"
#include "dictionary/user_dictionary_util.h"
#include "protocol/user_dictionary_storage.pb.h"
#include "absl/container/fixed_array.h"

namespace mozc {
namespace user_dictionary {

using ::mozc::protobuf::RepeatedPtrField;

class UserDictionarySession::UndoCommand {
 public:
  virtual ~UndoCommand() {}
  virtual bool RunUndo(mozc::UserDictionaryStorage *storage) = 0;

 protected:
  UndoCommand() {}

 private:
  DISALLOW_COPY_AND_ASSIGN(UndoCommand);
};

namespace {
// Implement "undo commands" for each operations.

class UndoCreateDictionaryCommand : public UserDictionarySession::UndoCommand {
 public:
  UndoCreateDictionaryCommand() {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    if (storage->GetProto().dictionaries_size() == 0) {
      return false;
    }

    storage->GetProto().mutable_dictionaries()->RemoveLast();
    return true;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(UndoCreateDictionaryCommand);
};

class UndoDeleteDictionaryCommand : public UserDictionarySession::UndoCommand {
 public:
  // This instance takes the ownership of the given dictionary.
  UndoDeleteDictionaryCommand(int index, UserDictionary *dictionary)
      : index_(index), dictionary_(dictionary) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    if (dictionary_ == nullptr) {
      return false;
    }

    RepeatedPtrField<UserDictionary> *dictionaries =
        storage->GetProto().mutable_dictionaries();
    dictionaries->AddAllocated(dictionary_.release());

    // Adjust the position of the reverted dictionary.
    std::rotate(dictionaries->pointer_begin() + index_,
                dictionaries->pointer_end() - 1, dictionaries->pointer_end());
    return true;
  }

 private:
  int index_;
  std::unique_ptr<UserDictionary> dictionary_;

  DISALLOW_COPY_AND_ASSIGN(UndoDeleteDictionaryCommand);
};

class UndoDeleteDictionaryWithEnsuringNonEmptyStorageCommand
    : public UserDictionarySession::UndoCommand {
 public:
  // This instance takes the ownership of the given dictionary.
  explicit UndoDeleteDictionaryWithEnsuringNonEmptyStorageCommand(
      UserDictionary *dictionary)
      : dictionary_(dictionary) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    if (storage->GetProto().dictionaries_size() != 1) {
      return false;
    }
    dictionary_->Swap(storage->GetProto().mutable_dictionaries(0));
    return true;
  }

 private:
  std::unique_ptr<UserDictionary> dictionary_;

  DISALLOW_COPY_AND_ASSIGN(
      UndoDeleteDictionaryWithEnsuringNonEmptyStorageCommand);
};

class UndoRenameDictionaryCommand : public UserDictionarySession::UndoCommand {
 public:
  UndoRenameDictionaryCommand(uint64_t dictionary_id,
                              const std::string &original_name)
      : dictionary_id_(dictionary_id), original_name_(original_name) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    UserDictionary *dictionary =
        UserDictionaryUtil::GetMutableUserDictionaryById(
            &storage->GetProto(), dictionary_id_);
    if (dictionary == nullptr) {
      return false;
    }

    dictionary->set_name(original_name_);
    return true;
  }

 private:
  uint64_t dictionary_id_;
  std::string original_name_;

  DISALLOW_COPY_AND_ASSIGN(UndoRenameDictionaryCommand);
};

class UndoAddEntryCommand : public UserDictionarySession::UndoCommand {
 public:
  explicit UndoAddEntryCommand(uint64_t dictionary_id)
      : dictionary_id_(dictionary_id) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    UserDictionary *dictionary =
        UserDictionaryUtil::GetMutableUserDictionaryById(
            &storage->GetProto(), dictionary_id_);
    if (dictionary == nullptr || dictionary->entries_size() == 0) {
      return false;
    }

    dictionary->mutable_entries()->RemoveLast();
    return true;
  }

 private:
  uint64_t dictionary_id_;

  DISALLOW_COPY_AND_ASSIGN(UndoAddEntryCommand);
};

class UndoEditEntryCommand : public UserDictionarySession::UndoCommand {
 public:
  UndoEditEntryCommand(uint64_t dictionary_id, int index,
                       const UserDictionary::Entry &original_entry)
      : dictionary_id_(dictionary_id),
        index_(index),
        original_entry_(original_entry) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    UserDictionary *dictionary =
        UserDictionaryUtil::GetMutableUserDictionaryById(
            &storage->GetProto(), dictionary_id_);
    if (dictionary == nullptr || index_ < 0 ||
        dictionary->entries_size() <= index_) {
      return false;
    }

    dictionary->mutable_entries(index_)->CopyFrom(original_entry_);
    return true;
  }

 private:
  uint64_t dictionary_id_;
  int index_;
  UserDictionary::Entry original_entry_;

  DISALLOW_COPY_AND_ASSIGN(UndoEditEntryCommand);
};

struct DeleteEntryComparator {
  bool operator()(const std::pair<int, UserDictionary::Entry *> &entry1,
                  const std::pair<int, UserDictionary::Entry *> &entry2) {
    return entry1.first < entry2.first;
  }
};

class UndoDeleteEntryCommand : public UserDictionarySession::UndoCommand {
 public:
  // This instance takes the ownership of the given entries.
  UndoDeleteEntryCommand(
      uint64_t dictionary_id,
      std::vector<std::pair<int, UserDictionary::Entry *>> deleted_entries)
      : dictionary_id_(dictionary_id),
        deleted_entries_(std::move(deleted_entries)) {
    std::sort(deleted_entries_.begin(), deleted_entries_.end(),
              DeleteEntryComparator());
  }
  ~UndoDeleteEntryCommand() override {
    for (size_t i = 0; i < deleted_entries_.size(); ++i) {
      delete deleted_entries_[i].second;
    }
  }

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    UserDictionary *dictionary =
        UserDictionaryUtil::GetMutableUserDictionaryById(
            &storage->GetProto(), dictionary_id_);
    if (dictionary == nullptr) {
      return false;
    }

    // Check validity of the holding indices.
    const int num_merged_entries =
        dictionary->entries_size() + deleted_entries_.size();
    for (size_t i = 0; i < deleted_entries_.size(); ++i) {
      const int index = deleted_entries_[i].first;
      if (index < 0 || num_merged_entries <= index) {
        return false;
      }
    }

    RepeatedPtrField<UserDictionary::Entry> *entries =
        dictionary->mutable_entries();

    // Move instances to backup vector.
    absl::FixedArray<UserDictionary::Entry *> backup(entries->size());
    entries->ExtractSubrange(0, entries->size(), backup.data());

    // Merge two vectors into entries.
    int backup_index = 0;
    int deleted_entry_index = 0;
    for (int index = 0; deleted_entry_index < deleted_entries_.size();
         ++index) {
      if (index == deleted_entries_[deleted_entry_index].first) {
        entries->AddAllocated(deleted_entries_[deleted_entry_index].second);
        ++deleted_entry_index;
      } else {
        entries->AddAllocated(backup[backup_index]);
        ++backup_index;
      }
    }

    // Add remaining entries in backup to the entries.
    for (; backup_index < backup.size(); ++backup_index) {
      entries->AddAllocated(backup[backup_index]);
    }

    // Release the entries.
    deleted_entries_.clear();
    return true;
  }

 private:
  uint64_t dictionary_id_;
  std::vector<std::pair<int, UserDictionary::Entry *> > deleted_entries_;

  DISALLOW_COPY_AND_ASSIGN(UndoDeleteEntryCommand);
};

class UndoImportFromStringCommand : public UserDictionarySession::UndoCommand {
 public:
  UndoImportFromStringCommand(uint64_t dictionary_id, int original_num_entries)
      : dictionary_id_(dictionary_id),
        original_num_entries_(original_num_entries) {}

  bool RunUndo(mozc::UserDictionaryStorage *storage) override {
    UserDictionary *dictionary =
        UserDictionaryUtil::GetMutableUserDictionaryById(
            &storage->GetProto(), dictionary_id_);
    if (dictionary == nullptr) {
      return false;
    }

    RepeatedPtrField<UserDictionary::Entry> *entries =
        dictionary->mutable_entries();
    while (original_num_entries_ < entries->size()) {
      entries->RemoveLast();
    }
    return true;
  }

 private:
  uint64_t dictionary_id_;
  int original_num_entries_;

  DISALLOW_COPY_AND_ASSIGN(UndoImportFromStringCommand);
};

// The limit of the number of commands remembered by the session for undo.
const int kMaxUndoHistory = 30;

// The default name of a dictionary, which is created to ensure "non-empty"
// storage.
const char kDefaultDictionaryName[] = "user dictionary";

}  // namespace

UserDictionarySession::UserDictionarySession(const std::string &filepath)
    : storage_(new mozc::UserDictionaryStorage(filepath)),
      default_dictionary_name_(kDefaultDictionaryName) {}
UserDictionarySession::~UserDictionarySession() { ClearUndoHistory(); }

// TODO(hidehiko) move this to header.
const UserDictionaryStorage &UserDictionarySession::storage() const {
  return storage_->GetProto();
}
mozc::UserDictionaryStorage *UserDictionarySession::mutable_storage() {
  return storage_.get();
}

UserDictionaryCommandStatus::Status
UserDictionarySession::SetDefaultDictionaryName(
    const std::string &dictionary_name) {
  // Validate the name for the default dictionary. The name is used to create
  // a dictionary "for an empty storage", so check the validity with the
  // default instance of UserDictionaryStorage.
  UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::ValidateDictionaryName(
          UserDictionaryStorage::default_instance(), dictionary_name);
  if (status == UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    default_dictionary_name_ = dictionary_name;
  }
  return status;
}

UserDictionaryCommandStatus::Status UserDictionarySession::Load() {
  return LoadInternal(false);
}

UserDictionaryCommandStatus::Status
UserDictionarySession::LoadWithEnsuringNonEmptyStorage() {
  return LoadInternal(true);
}

UserDictionaryCommandStatus::Status UserDictionarySession::LoadInternal(
    bool ensure_non_empty_storage) {
  UserDictionaryCommandStatus::Status status;
  if (storage_->Load()) {
    status = UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
  } else {
    switch (storage_->GetLastError()) {
      case mozc::UserDictionaryStorage::FILE_NOT_EXISTS:
        status = UserDictionaryCommandStatus::FILE_NOT_FOUND;
        break;
      case mozc::UserDictionaryStorage::BROKEN_FILE:
        status = UserDictionaryCommandStatus::INVALID_FILE_FORMAT;
        break;
      default:
        LOG(ERROR) << "Unknown error code: " << storage_->GetLastError();
        status = UserDictionaryCommandStatus::UNKNOWN_ERROR;
        break;
    }
  }

  if ((ensure_non_empty_storage && EnsureNonEmptyStorage()) ||
      status == UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    // If the storage is updated, clear the undo history.
    ClearUndoHistory();
  }
  return status;
}

namespace {
// Locker of mozc::UserDictionaryStorage in RAII idiom.
class ScopedUserDictionaryLocker {
 public:
  explicit ScopedUserDictionaryLocker(mozc::UserDictionaryStorage *storage)
      : storage_(storage) {
    is_locked_ = storage_->Lock();
  }
  ~ScopedUserDictionaryLocker() {
    if (is_locked_) {
      storage_->UnLock();
    }
  }

  bool is_locked() const { return is_locked_; }

 private:
  mozc::UserDictionaryStorage *storage_;
  bool is_locked_;

  DISALLOW_COPY_AND_ASSIGN(ScopedUserDictionaryLocker);
};
}  // namespace

UserDictionaryCommandStatus::Status UserDictionarySession::Save() {
  ScopedUserDictionaryLocker locker(storage_.get());
  if (!locker.is_locked()) {
    LOG(ERROR) << "Failed to take a lock.";
    return UserDictionaryCommandStatus::UNKNOWN_ERROR;
  }

  if (!storage_->Save()) {
    switch (storage_->GetLastError()) {
      case mozc::UserDictionaryStorage::TOO_BIG_FILE_BYTES:
        return UserDictionaryCommandStatus::FILE_SIZE_LIMIT_EXCEEDED;
        // TODO(hidehiko): Handle SYNC_FAILURE.
      default:
        LOG(ERROR) << "Unknown error code: " << storage_->GetLastError();
        return UserDictionaryCommandStatus::UNKNOWN_ERROR;
    }
    // Should never reach here.
  }

  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::Undo() {
  if (undo_history_.empty()) {
    return UserDictionaryCommandStatus::NO_UNDO_HISTORY;
  }

  std::unique_ptr<UndoCommand> undo_command(undo_history_.back());
  undo_history_.pop_back();
  return undo_command->RunUndo(storage_.get())
             ? UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS
             : UserDictionaryCommandStatus::UNKNOWN_ERROR;
}

UserDictionaryCommandStatus::Status UserDictionarySession::CreateDictionary(
    const std::string &dictionary_name, uint64_t *new_dictionary_id) {
  UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::CreateDictionary(
          &storage_->GetProto(), dictionary_name, new_dictionary_id);
  if (status == UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    AddUndoCommand(new UndoCreateDictionaryCommand);
  }
  return status;
}

UserDictionaryCommandStatus::Status UserDictionarySession::DeleteDictionary(
    uint64_t dictionary_id) {
  return DeleteDictionaryInternal(dictionary_id, false);
}

UserDictionaryCommandStatus::Status
UserDictionarySession::DeleteDictionaryWithEnsuringNonEmptyStorage(
    uint64_t dictionary_id) {
  return DeleteDictionaryInternal(dictionary_id, true);
}

UserDictionaryCommandStatus::Status
UserDictionarySession::DeleteDictionaryInternal(uint64_t dictionary_id,
                                                bool ensure_non_empty_storage) {
  int original_index;
  UserDictionary *deleted_dictionary;
  if (!UserDictionaryUtil::DeleteDictionary(
      &storage_->GetProto(), dictionary_id, &original_index,
      &deleted_dictionary)) {
    // Failed to delete the dictionary.
    return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
  }

  if ((ensure_non_empty_storage && EnsureNonEmptyStorage())) {
    // The storage was empty.
    AddUndoCommand(new UndoDeleteDictionaryWithEnsuringNonEmptyStorageCommand(
        deleted_dictionary));
  } else {
    AddUndoCommand(
        new UndoDeleteDictionaryCommand(original_index, deleted_dictionary));
  }

  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::RenameDictionary(
    uint64_t dictionary_id, const std::string &dictionary_name) {
  std::string original_name;
  const UserDictionary *dictionary =
      UserDictionaryUtil::GetUserDictionaryById(
          storage_->GetProto(), dictionary_id);
  if (dictionary != nullptr) {
    // Note that if dictionary is null, it means the dictionary_id is invalid
    // so following RenameDictionary will fail, and error handling is done
    // in the following codes.
    original_name = dictionary->name();
  }

  if (!storage_->RenameDictionary(dictionary_id, dictionary_name)) {
    switch (storage_->GetLastError()) {
      case mozc::UserDictionaryStorage::EMPTY_DICTIONARY_NAME:
        return UserDictionaryCommandStatus::DICTIONARY_NAME_EMPTY;
      case mozc::UserDictionaryStorage::TOO_LONG_DICTIONARY_NAME:
        return UserDictionaryCommandStatus::DICTIONARY_NAME_TOO_LONG;
      case mozc::UserDictionaryStorage::INVALID_CHARACTERS_IN_DICTIONARY_NAME:
        return UserDictionaryCommandStatus ::
            DICTIONARY_NAME_CONTAINS_INVALID_CHARACTER;
      case mozc::UserDictionaryStorage::DUPLICATED_DICTIONARY_NAME:
        return UserDictionaryCommandStatus::DICTIONARY_NAME_DUPLICATED;
      case mozc::UserDictionaryStorage::INVALID_DICTIONARY_ID:
        return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
      default:
        LOG(ERROR) << "Unknown error code: " << storage_->GetLastError();
        return UserDictionaryCommandStatus::UNKNOWN_ERROR;
    }
    // Should never reach here.
  }

  AddUndoCommand(new UndoRenameDictionaryCommand(dictionary_id, original_name));
  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::AddEntry(
    uint64_t dictionary_id, const UserDictionary::Entry &entry) {
  UserDictionary *dictionary = UserDictionaryUtil::GetMutableUserDictionaryById(
      &storage_->GetProto(), dictionary_id);
  if (dictionary == nullptr) {
    return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
  }

  if (UserDictionaryUtil::IsDictionaryFull(*dictionary)) {
    return UserDictionaryCommandStatus::ENTRY_SIZE_LIMIT_EXCEEDED;
  }

  const UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::ValidateEntry(entry);
  if (status != UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    // Invalid entry.
    return status;
  }

  UserDictionary::Entry *new_entry = dictionary->add_entries();
  new_entry->CopyFrom(entry);
  UserDictionaryUtil::SanitizeEntry(new_entry);

  AddUndoCommand(new UndoAddEntryCommand(dictionary_id));
  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::EditEntry(
    uint64_t dictionary_id, int index, const UserDictionary::Entry &entry) {
  UserDictionary *dictionary = UserDictionaryUtil::GetMutableUserDictionaryById(
      &storage_->GetProto(), dictionary_id);
  if (dictionary == nullptr) {
    return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
  }

  if (index < 0 || dictionary->entries_size() <= index) {
    return UserDictionaryCommandStatus::ENTRY_INDEX_OUT_OF_RANGE;
  }

  const UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::ValidateEntry(entry);
  if (status != UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    // Invalid entry.
    return status;
  }

  UserDictionary::Entry *target_entry = dictionary->mutable_entries(index);
  AddUndoCommand(new UndoEditEntryCommand(dictionary_id, index, *target_entry));

  target_entry->CopyFrom(entry);
  UserDictionaryUtil::SanitizeEntry(target_entry);
  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::DeleteEntry(
    uint64_t dictionary_id, std::vector<int> index_list) {
  UserDictionary *dictionary = UserDictionaryUtil::GetMutableUserDictionaryById(
      &storage_->GetProto(), dictionary_id);
  if (dictionary == nullptr) {
    return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
  }

  for (size_t i = 0; i < index_list.size(); ++i) {
    const int index = index_list[i];
    if (index < 0 || dictionary->entries_size() <= index) {
      return UserDictionaryCommandStatus::ENTRY_INDEX_OUT_OF_RANGE;
    }
  }

  std::vector<std::pair<int, UserDictionary::Entry *> > deleted_entries;
  deleted_entries.reserve(index_list.size());

  // Sort these in descending order so the indices don't change as we remove
  // elements.
  std::sort(index_list.begin(), index_list.end(), std::greater<int>());

  RepeatedPtrField<UserDictionary::Entry> *entries =
      dictionary->mutable_entries();
  for (size_t i = 0; i < index_list.size(); ++i) {
    const int index = index_list[i];

    std::rotate(entries->pointer_begin() + index,
                entries->pointer_begin() + index + 1, entries->pointer_end());
    deleted_entries.push_back(std::make_pair(index, entries->ReleaseLast()));
  }

  AddUndoCommand(new UndoDeleteEntryCommand(dictionary_id, deleted_entries));
  return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;
}

UserDictionaryCommandStatus::Status UserDictionarySession::ImportFromString(
    uint64_t dictionary_id, const std::string &data) {
  UserDictionary *dictionary = UserDictionaryUtil::GetMutableUserDictionaryById(
      &storage_->GetProto(), dictionary_id);
  if (dictionary == nullptr) {
    return UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID;
  }

  int original_num_entries = dictionary->entries_size();
  UserDictionaryCommandStatus::Status status =
      ImportFromStringInternal(dictionary, data);

  // Remember the command regardless of whether the importing is successfully
  // done or not, because ImportFromStringInternal updates the dictionary
  // always.
  AddUndoCommand(
      new UndoImportFromStringCommand(dictionary_id, original_num_entries));

  return status;
}

UserDictionaryCommandStatus::Status
UserDictionarySession::ImportFromStringInternal(UserDictionary *dictionary,
                                                const std::string &data) {
  UserDictionaryImporter::ErrorType import_result;
  {
    UserDictionaryImporter::StringTextLineIterator iter(data);
    import_result = UserDictionaryImporter::ImportFromTextLineIterator(
        UserDictionaryImporter::IME_AUTO_DETECT, &iter, dictionary);
  }

  LOG_IF(WARNING, import_result != UserDictionaryImporter::IMPORT_NO_ERROR)
      << "Import failed: " << import_result;

  // Return status code.
  switch (import_result) {
    case UserDictionaryImporter::IMPORT_NO_ERROR:
      // Succeeded.
      return UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS;

    // Failed on some reasons.
    case UserDictionaryImporter::IMPORT_TOO_MANY_WORDS:
      return UserDictionaryCommandStatus::IMPORT_TOO_MANY_WORDS;
    case UserDictionaryImporter::IMPORT_INVALID_ENTRIES:
      return UserDictionaryCommandStatus::IMPORT_INVALID_ENTRIES;
    default:
      LOG(ERROR) << "Unknown error: " << import_result;
      return UserDictionaryCommandStatus::UNKNOWN_ERROR;
  }
}

UserDictionaryCommandStatus::Status
UserDictionarySession::ImportToNewDictionaryFromString(
    const std::string &dictionary_name, const std::string &data,
    uint64_t *new_dictionary_id) {
  UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::CreateDictionary(
          &storage_->GetProto(), dictionary_name, new_dictionary_id);
  if (status != UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) {
    return status;
  }

  // We can use undo command for CreateDictionary here, too.
  AddUndoCommand(new UndoCreateDictionaryCommand);

  UserDictionary *dictionary = UserDictionaryUtil::GetMutableUserDictionaryById(
      &storage_->GetProto(), *new_dictionary_id);
  if (dictionary == nullptr) {
    // The dictionary should be always found.
    return UserDictionaryCommandStatus::UNKNOWN_ERROR;
  }

  return ImportFromStringInternal(dictionary, data);
}

bool UserDictionarySession::EnsureNonEmptyStorage() {
  if (storage_->GetProto().dictionaries_size() > 0) {
    // The storage already has at least one dictionary. Do nothing.
    return false;
  }

  // Creates a dictionary with the default name. Should never fail.
  uint64_t new_dictionary_id;
  UserDictionaryCommandStatus::Status status =
      UserDictionaryUtil::CreateDictionary(
          &storage_->GetProto(), default_dictionary_name_, &new_dictionary_id);
  CHECK_EQ(status,
           UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS);
  return true;
}

void UserDictionarySession::ClearUndoHistory() {
  for (std::deque<UndoCommand *>::iterator iter = undo_history_.begin();
       iter != undo_history_.end(); ++iter) {
    delete *iter;
  }
  undo_history_.clear();
}

void UserDictionarySession::AddUndoCommand(UndoCommand *undo_command) {
  // To avoid OOM due to huge undo history, we limit the undo-able
  // command size by kMaxUndoHistory.
  while (undo_history_.size() >= kMaxUndoHistory) {
    delete undo_history_.front();
    undo_history_.pop_front();
  }

  undo_history_.push_back(undo_command);
}

void UserDictionarySession::ClearDictionariesAndUndoHistory() {
  ScopedUserDictionaryLocker l(storage_.get());
  storage_->GetProto().clear_dictionaries();
  ClearUndoHistory();
}

}  // namespace user_dictionary
}  // namespace mozc
