// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_hdlc/router.h"

#include "pw_allocator/testing.h"
#include "pw_async2/pend_func_task.h"
#include "pw_bytes/suffix.h"
#include "pw_channel/forwarding_channel.h"
#include "pw_channel/loopback_channel.h"
#include "pw_containers/inline_queue.h"
#include "pw_containers/vector.h"
#include "pw_multibuf/simple_allocator.h"

namespace pw::hdlc {
namespace {

using ::pw::allocator::test::AllocatorForTest;
using ::pw::async2::Context;
using ::pw::async2::Dispatcher;
using ::pw::async2::PendFuncTask;
using ::pw::async2::Pending;
using ::pw::async2::Poll;
using ::pw::async2::Ready;
using ::pw::async2::Task;
using ::pw::async2::Waker;
using ::pw::operator"" _b;
using ::pw::channel::DatagramReader;
using ::pw::channel::DatagramWriter;
using ::pw::channel::ForwardingByteChannelPair;
using ::pw::channel::ForwardingDatagramChannelPair;
using ::pw::channel::LoopbackByteChannel;
using ::pw::multibuf::MultiBuf;
using ::pw::multibuf::MultiBufAllocator;
using ::pw::multibuf::SimpleAllocator;

class SimpleAllocatorForTest {
 public:
  SimpleAllocatorForTest() : simple_allocator_(data_area_, meta_alloc_) {}
  MultiBufAllocator& operator*() { return simple_allocator_; }
  MultiBufAllocator* operator->() { return &simple_allocator_; }

 private:
  static constexpr size_t kArbitraryDataSize = 256;
  static constexpr size_t kArbitraryMetaSize = 2048;
  std::array<std::byte, kArbitraryDataSize> data_area_;
  AllocatorForTest<kArbitraryMetaSize> meta_alloc_;
  SimpleAllocator simple_allocator_;
};

class SendDatagrams : public Task {
 public:
  SendDatagrams(pw::InlineQueue<MultiBuf>& to_send, DatagramWriter& channel)
      : to_send_(to_send), channel_(channel) {}

 private:
  Poll<> DoPend(Context& cx) final {
    while (!to_send_.empty()) {
      if (channel_.PendReadyToWrite(cx).IsPending()) {
        return Pending();
      }
      EXPECT_EQ(channel_.Write(std::move(to_send_.front())).status(),
                pw::OkStatus());
      to_send_.pop();
    }
    return Ready();
  }

  pw::InlineQueue<MultiBuf>& to_send_;
  DatagramWriter& channel_;
};

static constexpr size_t kMaxReceiveDatagrams = 16;
class ReceiveDatagramsUntilClosed : public Task {
 public:
  ReceiveDatagramsUntilClosed(DatagramReader& channel) : channel_(channel) {}

  pw::Vector<MultiBuf, kMaxReceiveDatagrams> received;

 private:
  Poll<> DoPend(Context& cx) final {
    while (true) {
      Poll<Result<MultiBuf>> result = channel_.PendRead(cx);
      if (result.IsPending()) {
        return Pending();
      }
      if (!result->ok()) {
        EXPECT_EQ(result->status(), pw::Status::FailedPrecondition());
        return Ready();
      }
      received.push_back(std::move(**result));
    }
    // Unreachable.
    return Ready();
  }

  DatagramReader& channel_;
};

template <typename ActualIterable, typename ExpectedIterable>
void ExpectElementsEqual(const ActualIterable& actual,
                         const ExpectedIterable& expected) {
  auto actual_iter = actual.begin();
  auto expected_iter = expected.begin();
  for (; expected_iter != expected.end(); ++actual_iter, ++expected_iter) {
    ASSERT_NE(actual_iter, actual.end());
    EXPECT_EQ(*actual_iter, *expected_iter);
  }
}

template <typename ActualIterable, typename T>
void ExpectElementsEqual(const ActualIterable& actual,
                         std::initializer_list<T> expected) {
  ExpectElementsEqual<ActualIterable, std::initializer_list<T>>(actual,
                                                                expected);
}

// TODO: b/331285977 - Fuzz test this function.
void ExpectSendAndReceive(
    std::initializer_list<std::initializer_list<std::byte>> data) {
  SimpleAllocatorForTest alloc;

  LoopbackByteChannel io_loopback(*alloc);
  ForwardingDatagramChannelPair outgoing_pair(*alloc);
  ForwardingDatagramChannelPair incoming_pair(*alloc);

  static constexpr size_t kMaxSendDatagrams = 16;
  ASSERT_LE(data.size(), kMaxSendDatagrams);

  pw::InlineQueue<MultiBuf, kMaxSendDatagrams> datagrams_to_send;
  for (size_t i = 0; i < data.size(); i++) {
    std::optional<MultiBuf> buf = alloc->Allocate(std::data(data)[i].size());
    ASSERT_TRUE(buf.has_value());
    std::copy(
        std::data(data)[i].begin(), std::data(data)[i].end(), buf->begin());
    datagrams_to_send.push(std::move(*buf));
  }

  static constexpr uint64_t kAddress = 27;
  static constexpr uint64_t kArbitraryAddressOne = 13802183;
  static constexpr uint64_t kArbitraryAddressTwo = 4284900;
  static constexpr size_t kDecodeBufferSize = 256;

  std::array<std::byte, kDecodeBufferSize> decode_buffer;
  Router router(io_loopback, decode_buffer);
  PendFuncTask router_task([&router](Context& cx) { return router.Pend(cx); });

  SendDatagrams send_task(datagrams_to_send, outgoing_pair.first());
  ReceiveDatagramsUntilClosed recv_task(incoming_pair.first());

  EXPECT_EQ(
      router.AddChannel(outgoing_pair.second(), kArbitraryAddressOne, kAddress),
      OkStatus());
  EXPECT_EQ(
      router.AddChannel(incoming_pair.second(), kAddress, kArbitraryAddressTwo),
      OkStatus());

  Dispatcher dispatcher;
  dispatcher.Post(router_task);
  dispatcher.Post(send_task);
  dispatcher.Post(recv_task);

  EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());
  ASSERT_EQ(recv_task.received.size(), data.size());
  for (size_t i = 0; i < data.size(); i++) {
    ExpectElementsEqual(recv_task.received[i], std::data(data)[i]);
  }
}

TEST(Router, SendsAndReceivesSingleDatagram) {
  ExpectSendAndReceive({{2_b, 4_b, 6_b, 0_b, 1_b}});
}

TEST(Router, SendsAndReceivesMultipleDatagrams) {
  ExpectSendAndReceive({
      {1_b, 3_b, 5_b},
      {2_b, 4_b, 6_b, 7_b},
  });
}

TEST(Router, SendsAndReceivesReservedBytes) {
  ExpectSendAndReceive({
      // Control octets.
      {0x7D_b},
      {0x7E_b},
      {0x7D_b, 0x7E_b},
      {0x7D_b, 0x5E_b},
      // XON / XOFF
      {0x13_b},
      {0x11_b},
  });
}

TEST(Router, PendOnClosedIoChannelReturnsReady) {
  static constexpr size_t kDecodeBufferSize = 256;

  SimpleAllocatorForTest alloc;

  ForwardingByteChannelPair byte_pair(*alloc);
  std::array<std::byte, kDecodeBufferSize> decode_buffer;
  Router router(byte_pair.first(), decode_buffer);

  ForwardingDatagramChannelPair datagram_pair(*alloc);
  ReceiveDatagramsUntilClosed recv_task(datagram_pair.first());
  EXPECT_EQ(router.AddChannel(datagram_pair.second(),
                              /*arbitrary incoming address*/ 5017,
                              /*arbitrary outgoing address*/ 2019),
            OkStatus());

  PendFuncTask router_task([&router](Context& cx) { return router.Pend(cx); });

  Dispatcher dispatcher;
  dispatcher.Post(router_task);
  dispatcher.Post(recv_task);

  EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());

  // Close the underlying byte channel.
  Waker null_waker;
  Context null_cx(dispatcher, null_waker);
  EXPECT_EQ(byte_pair.second().PendClose(null_cx), Ready(OkStatus()));

  // Both the router and the receive task should complete.
  EXPECT_EQ(dispatcher.RunUntilStalled(), Ready());
}

}  // namespace
}  // namespace pw::hdlc
