// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_CAST_CHANNEL_CAST_TEST_UTIL_H_
#define COMPONENTS_CAST_CHANNEL_CAST_TEST_UTIL_H_

#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/cast_channel/cast_message_handler.h"
#include "components/cast_channel/cast_socket.h"
#include "components/cast_channel/cast_socket_service.h"
#include "components/cast_channel/cast_transport.h"
#include "net/base/completion_once_callback.h"
#include "net/base/completion_repeating_callback.h"
#include "net/base/ip_endpoint.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/openscreen/src/cast/common/channel/proto/cast_channel.pb.h"

namespace cast_channel {

class MockCastTransport : public CastTransport {
 public:
  MockCastTransport();
  ~MockCastTransport() override;

  void SetReadDelegate(
      std::unique_ptr<CastTransport::Delegate> delegate) override;

  void SendMessage(const CastMessage& message,
                   net::CompletionOnceCallback callback) override {
    // GMock does not handle move-only types, we need to rely on a mock method
    // that takes a repeating callback, which will work well with GMock actions.
    SendMessage(message, base::AdaptCallbackForRepeating(std::move(callback)));
  }

  MOCK_METHOD2(SendMessage,
               void(const CastMessage& message,
                    const net::CompletionRepeatingCallback& callback));

  MOCK_METHOD0(Start, void(void));

  // Gets the read delegate that is currently active for this transport.
  CastTransport::Delegate* current_delegate() const;

 private:
  std::unique_ptr<CastTransport::Delegate> delegate_;

  DISALLOW_COPY_AND_ASSIGN(MockCastTransport);
};

class MockCastTransportDelegate : public CastTransport::Delegate {
 public:
  MockCastTransportDelegate();
  ~MockCastTransportDelegate() override;

  MOCK_METHOD1(OnError, void(ChannelError error));
  MOCK_METHOD1(OnMessage, void(const CastMessage& message));
  MOCK_METHOD0(Start, void(void));

 private:
  DISALLOW_COPY_AND_ASSIGN(MockCastTransportDelegate);
};

class MockCastSocketObserver : public CastSocket::Observer {
 public:
  MockCastSocketObserver();
  ~MockCastSocketObserver() override;

  MOCK_METHOD2(OnError, void(const CastSocket& socket, ChannelError error));
  MOCK_METHOD2(OnMessage,
               void(const CastSocket& socket, const CastMessage& message));
};

class MockCastSocketService : public CastSocketService {
 public:
  explicit MockCastSocketService(
      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
  ~MockCastSocketService() override;

  void OpenSocket(NetworkContextGetter network_context_getter,
                  const CastSocketOpenParams& open_params,
                  CastSocket::OnOpenCallback open_cb) override {
    // Unit test should not call |open_cb| more than once. Just use
    // base::AdaptCallbackForRepeating to pass |open_cb| to a mock method.
    OpenSocketInternal(open_params.ip_endpoint,
                       base::AdaptCallbackForRepeating(std::move(open_cb)));
  }

  MOCK_METHOD2(OpenSocketInternal,
               void(const net::IPEndPoint& ip_endpoint,
                    const base::RepeatingCallback<void(CastSocket*)>& open_cb));
  MOCK_CONST_METHOD1(GetSocket, CastSocket*(int channel_id));
};

class MockCastSocket : public CastSocket {
 public:
  using MockOnOpenCallback = base::RepeatingCallback<void(CastSocket* socket)>;

  MockCastSocket();
  ~MockCastSocket() override;

  void Connect(CastSocket::OnOpenCallback callback) override {
    // Unit test should not call |open_cb| more than once. Just use
    // base::AdaptCallbackForRepeating to pass |open_cb| to a mock method.
    ConnectInternal(base::AdaptCallbackForRepeating(std::move(callback)));
  }

  void Close(net::CompletionOnceCallback callback) override {
    // GMock does not handle move-only types, we need to rely on a mock method
    // that takes a repeating callback, which will work well with GMock actions.
    Close(base::AdaptCallbackForRepeating(std::move(callback)));
  }

  MOCK_METHOD1(ConnectInternal, void(const MockOnOpenCallback& callback));
  MOCK_METHOD1(Close, void(const net::CompletionRepeatingCallback& callback));
  MOCK_CONST_METHOD0(ready_state, ReadyState());
  MOCK_METHOD1(AddObserver, void(Observer* observer));
  MOCK_METHOD1(RemoveObserver, void(Observer* observer));

  const net::IPEndPoint& ip_endpoint() const override { return ip_endpoint_; }
  void SetIPEndpoint(const net::IPEndPoint& ip_endpoint) {
    ip_endpoint_ = ip_endpoint;
  }

  int id() const override { return channel_id_; }
  void set_id(int id) override { channel_id_ = id; }

  ChannelError error_state() const override { return error_state_; }
  void SetErrorState(ChannelError error_state) override {
    error_state_ = error_state;
  }

  bool keep_alive() const override { return keep_alive_; }
  void SetKeepAlive(bool keep_alive) { keep_alive_ = keep_alive; }

  bool audio_only() const override { return audio_only_; }
  void SetAudioOnly(bool audio_only) { audio_only_ = audio_only; }

  CastTransport* transport() const override { return mock_transport_.get(); }
  MockCastTransport* mock_transport() const { return mock_transport_.get(); }

 private:
  net::IPEndPoint ip_endpoint_;
  int channel_id_;
  ChannelError error_state_;
  bool keep_alive_;
  bool audio_only_;

  std::unique_ptr<MockCastTransport> mock_transport_;
  std::unique_ptr<Observer> observer_;

  DISALLOW_COPY_AND_ASSIGN(MockCastSocket);
};

class MockCastMessageHandler : public CastMessageHandler {
 public:
  explicit MockCastMessageHandler(MockCastSocketService* socket_service);
  ~MockCastMessageHandler() override;

  MOCK_METHOD3(EnsureConnection,
               void(int, const std::string&, const std::string&));
  MOCK_METHOD3(CloseConnection,
               void(int, const std::string&, const std::string&));
  MOCK_METHOD3(RequestAppAvailability,
               void(CastSocket* socket,
                    const std::string& app_id,
                    GetAppAvailabilityCallback callback));
  MOCK_METHOD1(RequestReceiverStatus, void(int channel_id));
  MOCK_METHOD3(SendBroadcastMessage,
               Result(int,
                      const std::vector<std::string>&,
                      const BroadcastRequest&));
  MOCK_METHOD6(LaunchSession,
               void(int,
                    const std::string&,
                    base::TimeDelta,
                    const std::vector<std::string>&,
                    const base::Optional<base::Value>&,
                    LaunchSessionCallback callback));
  MOCK_METHOD4(StopSession,
               void(int channel_id,
                    const std::string& session_id,
                    const base::Optional<std::string>& client_id,
                    ResultCallback callback));
  MOCK_METHOD2(SendAppMessage,
               Result(int channel_id, const CastMessage& message));
  MOCK_METHOD2(SendCastMessage,
               Result(int channel_id, const CastMessage& message));
  MOCK_METHOD4(SendMediaRequest,
               base::Optional<int>(int channel_id,
                                   const base::Value& body,
                                   const std::string& source_id,
                                   const std::string& destination_id));
  MOCK_METHOD4(SendSetVolumeRequest,
               void(int channel_id,
                    const base::Value& body,
                    const std::string& source_id,
                    ResultCallback callback));

 private:
  DISALLOW_COPY_AND_ASSIGN(MockCastMessageHandler);
};

// Creates the IPEndpoint 192.168.1.1.
net::IPEndPoint CreateIPEndPointForTest();

// Checks if two proto messages are the same.
// From
// third_party/cacheinvalidation/overrides/google/cacheinvalidation/deps/gmock.h
// TODO(kmarshall): promote to a shared testing library.
MATCHER_P(EqualsProto, message, "") {
  std::string expected_serialized, actual_serialized;
  message.SerializeToString(&expected_serialized);
  arg.SerializeToString(&actual_serialized);
  return expected_serialized == actual_serialized;
}

ACTION_TEMPLATE(PostCompletionCallbackTask,
                HAS_1_TEMPLATE_PARAMS(int, cb_idx),
                AND_1_VALUE_PARAMS(rv)) {
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(testing::get<cb_idx>(args), rv));
}

}  // namespace cast_channel

#endif  // COMPONENTS_CAST_CHANNEL_CAST_TEST_UTIL_H_
