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

#include <map>
#include <memory>
#include <string>

#include "base/logging.h"
#include "base/number_util.h"
#include "base/port.h"
#include "base/util.h"
#include "base/version.h"
#include "ipc/ipc_mock.h"
#include "protocol/commands.pb.h"
#include "testing/base/public/gunit.h"
#include "absl/strings/str_format.h"

namespace mozc {
namespace client {

namespace {
const char kPrecedingText[] = "preceding_text";
const char kFollowingText[] = "following_text";
const bool kSuppressSuggestion = true;

const std::string UpdateVersion(int diff) {
  std::vector<std::string> tokens;
  Util::SplitStringUsing(Version::GetMozcVersion(), ".", &tokens);
  EXPECT_EQ(tokens.size(), 4);
  char buf[64];
  absl::SNPrintF(buf, sizeof(buf), "%d",
                 NumberUtil::SimpleAtoi(tokens[3]) + diff);
  tokens[3] = buf;
  std::string output;
  Util::JoinStrings(tokens, ".", &output);
  return output;
}
}  // namespace

class TestServerLauncher : public ServerLauncherInterface {
 public:
  explicit TestServerLauncher(IPCClientFactoryMock *factory)
      : factory_(factory),
        start_server_result_(false),
        start_server_called_(false),
        force_terminate_server_result_(false),
        force_terminate_server_called_(false),
        server_protocol_version_(IPC_PROTOCOL_VERSION) {}

  virtual void Ready() {}
  virtual void Wait() {}
  virtual void Error() {}

  virtual bool StartServer(ClientInterface *client) {
    if (!response_.empty()) {
      factory_->SetMockResponse(response_);
    }
    if (!product_version_after_start_server_.empty()) {
      factory_->SetServerProductVersion(product_version_after_start_server_);
    }
    factory_->SetServerProtocolVersion(server_protocol_version_);
    start_server_called_ = true;
    return start_server_result_;
  }

  virtual bool ForceTerminateServer(const std::string &name) {
    force_terminate_server_called_ = true;
    return force_terminate_server_result_;
  }

  virtual bool WaitServer(uint32 pid) { return true; }

  virtual void OnFatal(ServerLauncherInterface::ServerErrorType type) {
    LOG(ERROR) << static_cast<int>(type);
    error_map_[static_cast<int>(type)]++;
  }

  int error_count(ServerLauncherInterface::ServerErrorType type) {
    return error_map_[static_cast<int>(type)];
  }

  bool start_server_called() const { return start_server_called_; }

  void set_start_server_called(bool start_server_called) {
    start_server_called_ = start_server_called;
  }

  bool force_terminate_server_called() const {
    return force_terminate_server_called_;
  }

  void set_force_terminate_server_called(bool force_terminate_server_called) {
    force_terminate_server_called_ = force_terminate_server_called;
  }

  void set_server_program(const std::string &server_path) {}

  virtual const std::string &server_program() const {
    static const std::string path;
    return path;
  }

  void set_restricted(bool restricted) {}

  void set_suppress_error_dialog(bool suppress) {}

  void set_start_server_result(const bool result) {
    start_server_result_ = result;
  }

  void set_force_terminate_server_result(const bool result) {
    force_terminate_server_result_ = result;
  }

  void set_server_protocol_version(uint32 server_protocol_version) {
    server_protocol_version_ = server_protocol_version;
  }

  void set_mock_after_start_server(const commands::Output &mock_output) {
    mock_output.SerializeToString(&response_);
  }

  void set_product_version_after_start_server(const std::string &version) {
    product_version_after_start_server_ = version;
  }

 private:
  IPCClientFactoryMock *factory_;
  bool start_server_result_;
  bool start_server_called_;
  bool force_terminate_server_result_;
  bool force_terminate_server_called_;
  uint32 server_protocol_version_;
  std::string response_;
  std::string product_version_after_start_server_;
  std::map<int, int> error_map_;
};

class ClientTest : public testing::Test {
 protected:
  ClientTest() : version_diff_(0) {}

  virtual void SetUp() {
    client_factory_.reset(new IPCClientFactoryMock);
    client_.reset(new Client);
    client_->SetIPCClientFactory(client_factory_.get());

    server_launcher_ = new TestServerLauncher(client_factory_.get());
    client_->SetServerLauncher(server_launcher_);
  }

  virtual void TearDown() {
    client_.reset();
    client_factory_.reset();
  }

  void SetMockOutput(const commands::Output &mock_output) {
    std::string response;
    mock_output.SerializeToString(&response);
    client_factory_->SetMockResponse(response);
  }

  void GetGeneratedInput(commands::Input *input) {
    input->ParseFromString(client_factory_->GetGeneratedRequest());
    if (input->type() != commands::Input::CREATE_SESSION) {
      ASSERT_TRUE(input->has_id());
    }
  }

  void SetupProductVersion(int version_diff) { version_diff_ = version_diff; }

  bool SetupConnection(const int id) {
    client_factory_->SetConnection(true);
    client_factory_->SetResult(true);
    if (version_diff_ == 0) {
      client_factory_->SetServerProductVersion(Version::GetMozcVersion());
    } else {
      client_factory_->SetServerProductVersion(UpdateVersion(version_diff_));
    }
    server_launcher_->set_start_server_result(true);

    // TODO(komatsu): Due to the limitation of the testing mock,
    // EnsureConnection should be explicitly called before calling
    // SendKey.  Fix the testing mock.
    commands::Output mock_output;
    mock_output.set_id(id);
    SetMockOutput(mock_output);
    return client_->EnsureConnection();
  }

  std::unique_ptr<IPCClientFactoryMock> client_factory_;
  std::unique_ptr<Client> client_;
  TestServerLauncher *server_launcher_;
  int version_diff_;

 private:
  DISALLOW_COPY_AND_ASSIGN(ClientTest);
};

TEST_F(ClientTest, ConnectionError) {
  client_factory_->SetConnection(false);
  server_launcher_->set_start_server_result(false);
  EXPECT_FALSE(client_->EnsureConnection());

  commands::KeyEvent key;
  commands::Output output;
  EXPECT_FALSE(client_->SendKey(key, &output));

  key.Clear();
  output.Clear();
  EXPECT_FALSE(client_->TestSendKey(key, &output));

  commands::SessionCommand command;
  output.Clear();
  EXPECT_FALSE(client_->SendCommand(command, &output));
}

TEST_F(ClientTest, SendKey) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::SEND_KEY, input.type());
}

TEST_F(ClientTest, SendKeyWithContext) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Context context;
  context.set_preceding_text(kPrecedingText);
  context.set_following_text(kFollowingText);
  context.set_suppress_suggestion(kSuppressSuggestion);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKeyWithContext(key_event, context, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::SEND_KEY, input.type());
  EXPECT_EQ(kPrecedingText, input.context().preceding_text());
  EXPECT_EQ(kFollowingText, input.context().following_text());
  EXPECT_EQ(kSuppressSuggestion, input.context().suppress_suggestion());
}

TEST_F(ClientTest, TestSendKey) {
  const int mock_id = 512;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->TestSendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::TEST_SEND_KEY, input.type());
}

TEST_F(ClientTest, TestSendKeyWithContext) {
  const int mock_id = 512;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Context context;
  context.set_preceding_text(kPrecedingText);
  context.set_following_text(kFollowingText);
  context.set_suppress_suggestion(kSuppressSuggestion);

  commands::Output mock_output;
  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->TestSendKeyWithContext(key_event, context, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::TEST_SEND_KEY, input.type());
  EXPECT_EQ(kPrecedingText, input.context().preceding_text());
  EXPECT_EQ(kFollowingText, input.context().following_text());
  EXPECT_EQ(kSuppressSuggestion, input.context().suppress_suggestion());
}

TEST_F(ClientTest, SendCommand) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::SessionCommand session_command;
  session_command.set_type(commands::SessionCommand::SUBMIT);

  commands::Output mock_output;
  mock_output.Clear();
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendCommand(session_command, &output));

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::SEND_COMMAND, input.type());
}

TEST_F(ClientTest, SendCommandWithContext) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::SessionCommand session_command;
  session_command.set_type(commands::SessionCommand::SUBMIT);

  commands::Context context;
  context.set_preceding_text(kPrecedingText);
  context.set_following_text(kFollowingText);
  context.set_suppress_suggestion(kSuppressSuggestion);

  commands::Output mock_output;
  mock_output.Clear();
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(
      client_->SendCommandWithContext(session_command, context, &output));

  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_EQ(mock_id, input.id());
  EXPECT_EQ(commands::Input::SEND_COMMAND, input.type());
  EXPECT_EQ(kPrecedingText, input.context().preceding_text());
  EXPECT_EQ(kFollowingText, input.context().following_text());
  EXPECT_EQ(kSuppressSuggestion, input.context().suppress_suggestion());
}

TEST_F(ClientTest, SetConfig) {
  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  config::Config config;
  EXPECT_TRUE(client_->SetConfig(config));
}

TEST_F(ClientTest, GetConfig) {
  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.mutable_config()->set_verbose_level(2);
  mock_output.mutable_config()->set_incognito_mode(true);
  SetMockOutput(mock_output);

  config::Config config;
  EXPECT_TRUE(client_->GetConfig(&config));

  EXPECT_EQ(2, config.verbose_level());
  EXPECT_EQ(true, config.incognito_mode());
}

TEST_F(ClientTest, EnableCascadingWindow) {
  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  client_->NoOperation();
  commands::Input input;
  GetGeneratedInput(&input);
  EXPECT_FALSE(input.has_config());

  client_->EnableCascadingWindow(false);
  client_->NoOperation();
  GetGeneratedInput(&input);
  ASSERT_TRUE(input.has_config());
  ASSERT_TRUE(input.config().has_use_cascading_window());
  EXPECT_FALSE(input.config().use_cascading_window());

  client_->EnableCascadingWindow(true);
  client_->NoOperation();
  GetGeneratedInput(&input);
  ASSERT_TRUE(input.has_config());
  ASSERT_TRUE(input.config().has_use_cascading_window());
  EXPECT_TRUE(input.config().use_cascading_window());

  client_->NoOperation();
  GetGeneratedInput(&input);
  ASSERT_TRUE(input.has_config());
  EXPECT_TRUE(input.config().has_use_cascading_window());
}

TEST_F(ClientTest, VersionMismatch) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  // Suddenly, connects to a different server
  client_factory_->SetServerProtocolVersion(IPC_PROTOCOL_VERSION + 1);
  commands::Output output;
  EXPECT_FALSE(client_->SendKey(key_event, &output));
  EXPECT_FALSE(client_->EnsureConnection());
  EXPECT_EQ(1, server_launcher_->error_count(
                   ServerLauncherInterface::SERVER_VERSION_MISMATCH));
}

TEST_F(ClientTest, ProtocolUpdate) {
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  server_launcher_->set_force_terminate_server_called(false);
  server_launcher_->set_force_terminate_server_result(true);
  server_launcher_->set_start_server_called(false);

  // Now connecting to an old server
  client_factory_->SetServerProtocolVersion(IPC_PROTOCOL_VERSION - 1);
  // after start server, protocol version becomes the same
  server_launcher_->set_server_protocol_version(IPC_PROTOCOL_VERSION);

  EXPECT_TRUE(client_->EnsureSession());
  EXPECT_TRUE(server_launcher_->start_server_called());
  EXPECT_TRUE(server_launcher_->force_terminate_server_called());
}

TEST_F(ClientTest, ProtocolUpdateFailSameBinary) {
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  server_launcher_->set_force_terminate_server_called(false);
  server_launcher_->set_force_terminate_server_result(true);
  server_launcher_->set_start_server_called(false);

  EXPECT_FALSE(server_launcher_->start_server_called());

  // version is updated after restart the server
  client_factory_->SetServerProtocolVersion(IPC_PROTOCOL_VERSION - 1);
  // even after server reboot, protocol version is old
  server_launcher_->set_server_protocol_version(IPC_PROTOCOL_VERSION - 1);
  server_launcher_->set_mock_after_start_server(mock_output);
  EXPECT_FALSE(client_->EnsureSession());
  EXPECT_TRUE(server_launcher_->start_server_called());
  EXPECT_TRUE(server_launcher_->force_terminate_server_called());
  EXPECT_FALSE(client_->EnsureConnection());
  EXPECT_EQ(1, server_launcher_->error_count(
                   ServerLauncherInterface::SERVER_BROKEN_MESSAGE));
}

TEST_F(ClientTest, ProtocolUpdateFailOnTerminate) {
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  server_launcher_->set_force_terminate_server_called(false);
  server_launcher_->set_force_terminate_server_result(false);
  server_launcher_->set_start_server_called(false);

  EXPECT_FALSE(server_launcher_->start_server_called());

  // version is updated after restart the server
  client_factory_->SetServerProtocolVersion(IPC_PROTOCOL_VERSION - 1);
  // even after server reboot, protocol version is old
  server_launcher_->set_server_protocol_version(IPC_PROTOCOL_VERSION);
  server_launcher_->set_mock_after_start_server(mock_output);
  EXPECT_FALSE(client_->EnsureSession());
  EXPECT_FALSE(server_launcher_->start_server_called());
  EXPECT_TRUE(server_launcher_->force_terminate_server_called());
  EXPECT_FALSE(client_->EnsureConnection());
  EXPECT_EQ(1, server_launcher_->error_count(
                   ServerLauncherInterface::SERVER_BROKEN_MESSAGE));
}

TEST_F(ClientTest, ServerUpdate) {
  SetupProductVersion(-1);  // old version
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  LOG(ERROR) << Version::GetMozcVersion();

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  server_launcher_->set_start_server_called(false);
  EXPECT_FALSE(server_launcher_->start_server_called());

  // version is updated after restart the server
  server_launcher_->set_product_version_after_start_server(
      Version::GetMozcVersion());
  EXPECT_TRUE(client_->EnsureSession());
  EXPECT_TRUE(server_launcher_->start_server_called());
}

TEST_F(ClientTest, ServerUpdateToNewer) {
  SetupProductVersion(1);  // new version
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  LOG(ERROR) << Version::GetMozcVersion();

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());
  server_launcher_->set_start_server_called(false);
  EXPECT_TRUE(client_->EnsureSession());
  EXPECT_FALSE(server_launcher_->start_server_called());
}

TEST_F(ClientTest, ServerUpdateFail) {
  SetupProductVersion(-1);  // old
  server_launcher_->set_start_server_result(true);

  const int mock_id = 0;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->EnsureConnection());

  server_launcher_->set_start_server_called(false);
  EXPECT_FALSE(server_launcher_->start_server_called());

  // version is not updated after restart the server
  server_launcher_->set_mock_after_start_server(mock_output);
  EXPECT_FALSE(client_->EnsureSession());
  EXPECT_TRUE(server_launcher_->start_server_called());
  EXPECT_FALSE(client_->EnsureConnection());
  EXPECT_EQ(1, server_launcher_->error_count(
                   ServerLauncherInterface::SERVER_BROKEN_MESSAGE));
}

TEST_F(ClientTest, TranslateProtoBufToMozcToolArgTest) {
  commands::Output output;
  std::string mode = "";

  // If no value is set, we expect to return false
  EXPECT_FALSE(client::Client::TranslateProtoBufToMozcToolArg(output, &mode));
  EXPECT_EQ("", mode);

  // If NO_TOOL is set, we  expect to return false
  output.set_launch_tool_mode(commands::Output::NO_TOOL);
  EXPECT_FALSE(client::Client::TranslateProtoBufToMozcToolArg(output, &mode));
  EXPECT_EQ("", mode);

  output.set_launch_tool_mode(commands::Output::CONFIG_DIALOG);
  EXPECT_TRUE(client::Client::TranslateProtoBufToMozcToolArg(output, &mode));
  EXPECT_EQ("config_dialog", mode);

  output.set_launch_tool_mode(commands::Output::DICTIONARY_TOOL);
  EXPECT_TRUE(client::Client::TranslateProtoBufToMozcToolArg(output, &mode));
  EXPECT_EQ("dictionary_tool", mode);

  output.set_launch_tool_mode(commands::Output::WORD_REGISTER_DIALOG);
  EXPECT_TRUE(client::Client::TranslateProtoBufToMozcToolArg(output, &mode));
  EXPECT_EQ("word_register_dialog", mode);
}

class SessionPlaybackTestServerLauncher : public ServerLauncherInterface {
 public:
  explicit SessionPlaybackTestServerLauncher(IPCClientFactoryMock *factory)
      : factory_(factory),
        start_server_result_(false),
        start_server_called_(false),
        force_terminate_server_result_(false),
        force_terminate_server_called_(false),
        server_protocol_version_(IPC_PROTOCOL_VERSION) {}

  virtual void Ready() {}
  virtual void Wait() {}
  virtual void Error() {}

  virtual bool StartServer(ClientInterface *client) {
    if (!response_.empty()) {
      factory_->SetMockResponse(response_);
    }
    if (!product_version_after_start_server_.empty()) {
      factory_->SetServerProductVersion(product_version_after_start_server_);
    }
    factory_->SetServerProtocolVersion(server_protocol_version_);
    start_server_called_ = true;
    return start_server_result_;
  }

  virtual bool ForceTerminateServer(const std::string &name) {
    force_terminate_server_called_ = true;
    return force_terminate_server_result_;
  }

  virtual bool WaitServer(uint32 pid) { return true; }

  virtual void OnFatal(ServerLauncherInterface::ServerErrorType type) {}

  void set_server_program(const std::string &server_path) {}

  void set_restricted(bool restricted) {}

  void set_suppress_error_dialog(bool suppress) {}

  void set_start_server_result(const bool result) {
    start_server_result_ = result;
  }

  virtual const std::string &server_program() const {
    static const std::string path;
    return path;
  }

 private:
  IPCClientFactoryMock *factory_;
  bool start_server_result_;
  bool start_server_called_;
  bool force_terminate_server_result_;
  bool force_terminate_server_called_;
  uint32 server_protocol_version_;
  std::string response_;
  std::string product_version_after_start_server_;
  std::map<int, int> error_map_;
};

class SessionPlaybackTest : public testing::Test {
 protected:
  SessionPlaybackTest() {}
  virtual ~SessionPlaybackTest() {}

  virtual void SetUp() {
    ipc_client_factory_.reset(new IPCClientFactoryMock);
    ipc_client_.reset(
        reinterpret_cast<IPCClientMock *>(ipc_client_factory_->NewClient("")));
    client_.reset(new Client);
    client_->SetIPCClientFactory(ipc_client_factory_.get());
    server_launcher_ =
        new SessionPlaybackTestServerLauncher(ipc_client_factory_.get());
    client_->SetServerLauncher(server_launcher_);
  }

  virtual void TearDown() {
    client_.reset();
    ipc_client_factory_.reset();
  }

  bool SetupConnection(const int id) {
    ipc_client_factory_->SetConnection(true);
    ipc_client_factory_->SetResult(true);
    ipc_client_factory_->SetServerProductVersion(Version::GetMozcVersion());
    server_launcher_->set_start_server_result(true);

    // TODO(komatsu): Due to the limitation of the testing mock,
    // EnsureConnection should be explicitly called before calling
    // SendKey.  Fix the testing mock.
    commands::Output mock_output;
    mock_output.set_id(id);
    SetMockOutput(mock_output);
    return client_->EnsureConnection();
  }

  void SetMockOutput(const commands::Output &mock_output) {
    std::string response;
    mock_output.SerializeToString(&response);
    ipc_client_factory_->SetMockResponse(response);
  }

  std::unique_ptr<IPCClientFactoryMock> ipc_client_factory_;
  std::unique_ptr<IPCClientMock> ipc_client_;
  std::unique_ptr<Client> client_;
  SessionPlaybackTestServerLauncher *server_launcher_;

 private:
  DISALLOW_COPY_AND_ASSIGN(SessionPlaybackTest);
};

// b/2797557
TEST_F(SessionPlaybackTest, PushAndResetHistoryWithNoModeTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(1, history.size());

  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.mutable_result()->set_type(commands::Result::STRING);
  mock_output.mutable_result()->set_value("output");
  EXPECT_FALSE(mock_output.has_mode());
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  // history should be reset.
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(0, history.size());
}

// b/2797557
TEST_F(SessionPlaybackTest, PushAndResetHistoryWithModeTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);
  key_event.set_mode(commands::HIRAGANA);
  key_event.set_activated(true);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.set_mode(commands::HIRAGANA);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::HIRAGANA, output.mode());

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::HIRAGANA, output.mode());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(2, history.size());

  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.mutable_result()->set_type(commands::Result::STRING);
  mock_output.mutable_result()->set_value("output");
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  client_->GetHistoryInputs(&history);
#ifdef __APPLE__
  // history is reset, but initializer should be added because the last mode
  // is not DIRECT.
  // TODO(team): fix b/10250883 to remove this special treatment.
  EXPECT_EQ(1, history.size());
  // Implicit IMEOn key must be added. See b/2797557 and b/>10250883.
  EXPECT_EQ(commands::Input::SEND_KEY, history[0].type());
  EXPECT_EQ(commands::KeyEvent::ON, history[0].key().special_key());
  EXPECT_EQ(commands::HIRAGANA, history[0].key().mode());
#else
  // history is reset, but initializer is not required.
  EXPECT_EQ(0, history.size());
#endif
}

// b/2797557
TEST_F(SessionPlaybackTest, PushAndResetHistoryWithDirectTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.set_mode(commands::DIRECT);
  SetMockOutput(mock_output);

  commands::Output output;
  // Send key twice
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::DIRECT, output.mode());

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::DIRECT, output.mode());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(2, history.size());

  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.mutable_result()->set_type(commands::Result::STRING);
  mock_output.mutable_result()->set_value("output");
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  // history is reset, and initializer should not be added.
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(0, history.size());
}

TEST_F(SessionPlaybackTest, PlaybackHistoryTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  // On Windows, mode initializer should be added if the output contains mode.
  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(2, history.size());

  // Invalid id
  const int new_id = 456;
  mock_output.set_id(new_id);
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->SendKey(key_event, &output));

#ifndef DEBUG
  // PlaybackHistory and push history
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(3, history.size());
#else
  // PlaybackHistory, dump history(including reset), and add last input
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(1, history.size());
#endif
}

// b/2797557
TEST_F(SessionPlaybackTest, SetModeInitializerTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.set_mode(commands::HIRAGANA);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  mock_output.set_mode(commands::DIRECT);
  SetMockOutput(mock_output);

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::DIRECT, output.mode());

  mock_output.set_mode(commands::FULL_KATAKANA);
  SetMockOutput(mock_output);

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  EXPECT_TRUE(output.has_mode());
  EXPECT_EQ(commands::FULL_KATAKANA, output.mode());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(3, history.size());

  mock_output.Clear();
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  mock_output.mutable_result()->set_type(commands::Result::STRING);
  mock_output.mutable_result()->set_value("output");
  SetMockOutput(mock_output);
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());
  client_->GetHistoryInputs(&history);
#ifdef __APPLE__
  // history is reset, but initializer should be added.
  // TODO(team): fix b/10250883 to remove this special treatment.
  EXPECT_EQ(1, history.size());
  EXPECT_EQ(commands::Input::SEND_KEY, history[0].type());
  EXPECT_EQ(commands::KeyEvent::ON, history[0].key().special_key());
  EXPECT_EQ(commands::FULL_KATAKANA, history[0].key().mode());
#else
  // history is reset, but initializer is not required.
  EXPECT_EQ(0, history.size());
#endif
}

TEST_F(SessionPlaybackTest, ConsumedTest) {
  const int mock_id = 123;
  EXPECT_TRUE(SetupConnection(mock_id));

  commands::KeyEvent key_event;
  key_event.set_special_key(commands::KeyEvent::ENTER);

  commands::Output mock_output;
  mock_output.set_id(mock_id);
  mock_output.set_consumed(true);
  SetMockOutput(mock_output);

  commands::Output output;
  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  std::vector<commands::Input> history;
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(2, history.size());

  mock_output.set_consumed(false);
  SetMockOutput(mock_output);

  EXPECT_TRUE(client_->SendKey(key_event, &output));
  EXPECT_EQ(mock_output.consumed(), output.consumed());

  // Do not push unconsumed input
  client_->GetHistoryInputs(&history);
  EXPECT_EQ(2, history.size());
}
}  // namespace client
}  // namespace mozc
