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

#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.h"
#include "protocol/renderer_command.pb.h"
#include "renderer/renderer_interface.h"
#include "testing/base/public/gunit.h"
#include "absl/strings/str_format.h"

namespace mozc {

namespace renderer {

namespace {
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;
}

int g_counter = 0;
bool g_connected = false;
uint32 g_server_protocol_version = IPC_PROTOCOL_VERSION;
std::string g_server_product_version;

class TestIPCClient : public IPCClientInterface {
 public:
  TestIPCClient() { g_server_product_version = Version::GetMozcVersion(); }
  ~TestIPCClient() override {}

  bool Connected() const override { return g_connected; }

  uint32 GetServerProtocolVersion() const override {
    return g_server_protocol_version;
  }

  const std::string &GetServerProductVersion() const override {
    return g_server_product_version;
  }

  uint32 GetServerProcessId() const override { return 0; }

  // just count up how many times Call is called.
  bool Call(const char *request, size_t request_size, char *response,
            size_t *response_size, int32 timeout) override {
    g_counter++;
    return true;
  }

  static void set_connected(bool connected) { g_connected = connected; }

  static void Reset() { g_counter = 0; }

  static int counter() { return g_counter; }

  static void set_server_protocol_version(uint32 version) {
    g_server_protocol_version = version;
  }

  IPCErrorType GetLastIPCError() const override { return IPC_NO_ERROR; }
};

class TestIPCClientFactory : public IPCClientFactoryInterface {
 public:
  TestIPCClientFactory() {}
  ~TestIPCClientFactory() override {}

  IPCClientInterface *NewClient(const std::string &name,
                                const std::string &path_name) override {
    return new TestIPCClient;
  }

  IPCClientInterface *NewClient(const std::string &name) override {
    return new TestIPCClient;
  }
};

class TestRendererLauncher : public RendererLauncherInterface {
 public:
  TestRendererLauncher()
      : start_renderer_called_(false),
        force_terminate_renderer_called_(false),
        available_(false),
        can_connect_(false) {}
  ~TestRendererLauncher() override {}

  // implement StartServer.
  // return true if server can launched successfully.
  void StartRenderer(
      const std::string &name, const std::string &renderer_path,
      bool disable_renderer_path_check,
      IPCClientFactoryInterface *ipc_client_factory_interface) override {
    start_renderer_called_ = true;
    LOG(INFO) << name << " " << renderer_path;
  }

  bool ForceTerminateRenderer(const std::string &name) override {
    force_terminate_renderer_called_ = true;
    return true;
  }

  void OnFatal(RendererErrorType type) override {
    LOG(ERROR) << static_cast<int>(type);
  }

  // return true if the renderer is running
  bool IsAvailable() const override { return available_; }

  // return true if client can make a IPC connection.
  bool CanConnect() const override { return can_connect_; }

  void SetPendingCommand(const commands::RendererCommand &command) override {
    set_pending_command_called_ = true;
  }

  void set_suppress_error_dialog(bool suppress) override {}

  void Reset() {
    start_renderer_called_ = false;
    force_terminate_renderer_called_ = false;
    available_ = false;
    can_connect_ = false;
    set_pending_command_called_ = false;
  }

  void set_available(bool available) { available_ = available; }

  void set_can_connect(bool can_connect) { can_connect_ = can_connect; }

  bool is_start_renderer_called() const { return start_renderer_called_; }

  bool is_force_terminate_renderer_called() const {
    return force_terminate_renderer_called_;
  }

  bool is_set_pending_command_called() const {
    return set_pending_command_called_;
  }

 private:
  bool start_renderer_called_;
  bool force_terminate_renderer_called_;
  bool available_;
  bool can_connect_;
  bool set_pending_command_called_;
};
}  // namespace

TEST(RendererClient, InvalidTest) {
  RendererClient client;

  client.SetIPCClientFactory(nullptr);
  client.SetRendererLauncherInterface(nullptr);
  commands::RendererCommand command;

  // IPCClientFactory and Launcher must be set.
  EXPECT_FALSE(client.ExecCommand(command));
  EXPECT_FALSE(client.IsAvailable());
  EXPECT_FALSE(client.Activate());
}

TEST(RendererClient, ActivateTest) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  {
    launcher.set_available(true);
    EXPECT_TRUE(client.IsAvailable());
    launcher.set_available(false);
    EXPECT_FALSE(client.IsAvailable());
  }

  {
    // No connection may happen if can_connect is false
    launcher.set_available(false);
    launcher.set_can_connect(false);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.Activate());
    EXPECT_EQ(0, TestIPCClient::counter());
  }

  {
    // No connection may happen if connected return false
    launcher.set_available(false);
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(false);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.Activate());
    EXPECT_EQ(0, TestIPCClient::counter());
  }

  {
    // one IPC call happens
    launcher.set_available(false);
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.Activate());
    EXPECT_EQ(1, TestIPCClient::counter());
  }

  {
    // once launcher is available, no IPC call happens
    // with Activate()
    launcher.set_available(true);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.Activate());
    EXPECT_TRUE(client.Activate());
    EXPECT_TRUE(client.Activate());
    EXPECT_EQ(0, TestIPCClient::counter());
  }
}

TEST(RendererClient, LaunchTest) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.mutable_output()->set_id(0);
  command.set_type(commands::RendererCommand::NOOP);

  {
    // if can connect is false,
    // renderer is not launched
    launcher.Reset();
    launcher.set_can_connect(false);
    TestIPCClient::set_connected(false);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_start_renderer_called());
  }

  {
    // if connection is not available, start renderer process
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(false);
    command.set_visible(true);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(launcher.is_start_renderer_called());
  }

  {
    // if connection is not available,
    // but the command type is HIDE, renderer is not launched
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(false);
    command.set_visible(false);
    command.set_type(commands::RendererCommand::UPDATE);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_start_renderer_called());
  }

  {
    command.set_type(commands::RendererCommand::NOOP);
    // if every state is OK, renderer is not launched
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    command.set_visible(true);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_start_renderer_called());
  }
}

TEST(RendererClient, ConnectionTest) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));

    // IPC should be called three times
    EXPECT_EQ(3, TestIPCClient::counter());
  }

  {
    // launcher denies connection
    launcher.Reset();
    launcher.set_can_connect(false);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_EQ(0, TestIPCClient::counter());
  }

  {
    // IPC connection is lost.
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(false);
    TestIPCClient::Reset();
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_EQ(0, TestIPCClient::counter());
  }
}

TEST(RendererClient, ShutdownTest) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();

    // Shutdown with commands::RendererCommand::SHUTDOWN command
    EXPECT_TRUE(client.Shutdown(false));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(1, TestIPCClient::counter());
  }

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();

    // Shutdown with ForceTerminateRenderer
    EXPECT_TRUE(client.Shutdown(true));
    EXPECT_TRUE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(0, TestIPCClient::counter());
  }

  {
    launcher.Reset();
    launcher.set_can_connect(false);
    TestIPCClient::set_connected(false);
    TestIPCClient::Reset();

    EXPECT_TRUE(client.Shutdown(false));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(0, TestIPCClient::counter());
  }

  {
    launcher.Reset();
    launcher.set_can_connect(false);
    TestIPCClient::set_connected(false);
    TestIPCClient::Reset();

    EXPECT_TRUE(client.Shutdown(true));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(0, TestIPCClient::counter());
  }
}

TEST(RendererClient, ProtocolVersionMismatchNewer) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    TestIPCClient::set_server_protocol_version(IPC_PROTOCOL_VERSION - 1);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(0, TestIPCClient::counter());
  }
}

TEST(RendererClient, ProtocolVersionMismatchOlder) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    TestIPCClient::set_server_protocol_version(IPC_PROTOCOL_VERSION + 1);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(0, TestIPCClient::counter());
  }
}

TEST(RendererClient, MozcVersionMismatchNewer) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);
  g_server_product_version = UpdateVersion(-1);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    TestIPCClient::set_server_protocol_version(IPC_PROTOCOL_VERSION);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(1, TestIPCClient::counter());
  }
}

TEST(RendererClient, MozcVersionMismatchOlder) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);
  g_server_product_version = UpdateVersion(1);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    TestIPCClient::Reset();
    TestIPCClient::set_server_protocol_version(IPC_PROTOCOL_VERSION);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_force_terminate_renderer_called());
    EXPECT_EQ(1, TestIPCClient::counter());
  }
}

TEST(RendererClient, SetPendingCommandTest) {
  TestIPCClientFactory factory;
  TestRendererLauncher launcher;

  RendererClient client;

  client.SetIPCClientFactory(&factory);
  client.SetRendererLauncherInterface(&launcher);

  commands::RendererCommand command;
  command.set_type(commands::RendererCommand::NOOP);

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(false);
    command.set_visible(true);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(launcher.is_start_renderer_called());
    EXPECT_TRUE(launcher.is_set_pending_command_called());
  }

  {
    launcher.Reset();
    launcher.set_can_connect(false);
    TestIPCClient::set_connected(false);
    command.set_visible(true);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_TRUE(launcher.is_set_pending_command_called());
  }

  {
    launcher.Reset();
    launcher.set_can_connect(true);
    TestIPCClient::set_connected(true);
    command.set_visible(true);
    EXPECT_TRUE(client.ExecCommand(command));
    EXPECT_FALSE(launcher.is_set_pending_command_called());
  }
}
}  // namespace renderer
}  // namespace mozc
