// Copyright 2019 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.

#include "cast/streaming/environment.h"

#include <algorithm>
#include <utility>

#include "cast/streaming/rtp_defines.h"
#include "platform/api/task_runner.h"
#include "util/osp_logging.h"

namespace openscreen {
namespace cast {

Environment::Environment(ClockNowFunctionPtr now_function,
                         TaskRunner* task_runner,
                         const IPEndpoint& local_endpoint)
    : now_function_(now_function), task_runner_(task_runner) {
  OSP_DCHECK(now_function_);
  OSP_DCHECK(task_runner_);
  ErrorOr<std::unique_ptr<UdpSocket>> result =
      UdpSocket::Create(task_runner_, this, local_endpoint);
  if (result.is_error()) {
    OSP_LOG_ERROR << "Unable to create a UDP socket bound to " << local_endpoint
                  << ": " << result.error();
    return;
  }
  const_cast<std::unique_ptr<UdpSocket>&>(socket_) = std::move(result.value());
  OSP_DCHECK(socket_);
  socket_->Bind();
}

Environment::~Environment() = default;

IPEndpoint Environment::GetBoundLocalEndpoint() const {
  if (socket_) {
    return socket_->GetLocalEndpoint();
  }
  return IPEndpoint{};
}

void Environment::SetSocketSubscriber(SocketSubscriber* subscriber) {
  socket_subscriber_ = subscriber;
}

void Environment::ConsumeIncomingPackets(PacketConsumer* packet_consumer) {
  OSP_DCHECK(packet_consumer);
  OSP_DCHECK(!packet_consumer_);
  packet_consumer_ = packet_consumer;
}

void Environment::DropIncomingPackets() {
  packet_consumer_ = nullptr;
}

int Environment::GetMaxPacketSize() const {
  // Return hard-coded values for UDP over wired Ethernet (which is a smaller
  // MTU than typical defaults for UDP over 802.11 wireless). Performance would
  // be more-optimized if the network were probed for the actual value. See
  // discussion in rtp_defines.h.
  switch (remote_endpoint_.address.version()) {
    case IPAddress::Version::kV4:
      return kMaxRtpPacketSizeForIpv4UdpOnEthernet;
    case IPAddress::Version::kV6:
      return kMaxRtpPacketSizeForIpv6UdpOnEthernet;
    default:
      OSP_NOTREACHED();
  }
}

void Environment::SendPacket(absl::Span<const uint8_t> packet) {
  OSP_DCHECK(remote_endpoint_.address);
  OSP_DCHECK_NE(remote_endpoint_.port, 0);
  if (socket_) {
    socket_->SendMessage(packet.data(), packet.size(), remote_endpoint_);
  }
}

Environment::PacketConsumer::~PacketConsumer() = default;

void Environment::OnBound(UdpSocket* socket) {
  OSP_DCHECK(socket == socket_.get());
  state_ = SocketState::kReady;

  if (socket_subscriber_) {
    socket_subscriber_->OnSocketReady();
  }
}

void Environment::OnError(UdpSocket* socket, Error error) {
  OSP_DCHECK(socket == socket_.get());
  // Usually OnError() is only called for non-recoverable Errors. However,
  // OnSendError() and OnRead() delegate to this method, to handle their hard
  // error cases as well. So, return early here if |error| is recoverable.
  if (error.ok() || error.code() == Error::Code::kAgain) {
    return;
  }

  state_ = SocketState::kInvalid;
  if (socket_subscriber_) {
    socket_subscriber_->OnSocketInvalid(error);
  } else {
    // Default behavior when there are no subscribers.
    OSP_LOG_ERROR << "For UDP socket bound to " << socket_->GetLocalEndpoint()
                  << ": " << error;
  }
}

void Environment::OnSendError(UdpSocket* socket, Error error) {
  OnError(socket, error);
}

void Environment::OnRead(UdpSocket* socket,
                         ErrorOr<UdpPacket> packet_or_error) {
  if (!packet_consumer_) {
    return;
  }

  if (packet_or_error.is_error()) {
    OnError(socket, packet_or_error.error());
    return;
  }

  // Ideally, the arrival time would come from the operating system's network
  // stack (e.g., by using the SO_TIMESTAMP sockopt on POSIX systems). However,
  // there would still be the problem of mapping the timestamp to a value in
  // terms of Clock::time_point. So, just sample the Clock here and call that
  // the "arrival time." While this can add variance within the system, it
  // should be minimal, assuming not too much time has elapsed between the
  // actual packet receive event and the when this code here is executing.
  const Clock::time_point arrival_time = now_function_();

  UdpPacket packet = std::move(packet_or_error.value());
  packet_consumer_->OnReceivedPacket(
      packet.source(), arrival_time,
      std::move(static_cast<std::vector<uint8_t>&>(packet)));
}

}  // namespace cast
}  // namespace openscreen
