/******************************************************************************
 *
 *  Copyright 2019 The Android Open Source Project
 *
 *  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:
 *
 *  http://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.
 *
 ******************************************************************************/

#pragma once

#include <bluetooth/log.h>

#include <memory>
#include <vector>

#include "common/bidi_queue.h"
#include "os/handler.h"
#include "packet/base_packet_builder.h"
#include "packet/bit_inserter.h"
#include "packet/packet_view.h"

namespace bluetooth {
namespace common {
namespace testing {

/* This class is a pair of BiDiQueues, that have down ends "wired" together. It can be used i.e. to
 * mock L2cap interface, and provide two queues, where each sends packets of type A, and receives
 * packets of type B */
template <class A, class B, std::unique_ptr<B> (*A_TO_B)(std::unique_ptr<A>)>
class WiredPairOfBiDiQueues {
  void dequeue_callback_a() {
    auto down_thing = queue_a_.GetDownEnd()->TryDequeue();
    if (!down_thing) {
      log::error("Received dequeue, but no data ready...");
    }

    down_buffer_b_.Enqueue(A_TO_B(std::move(down_thing)), handler_);
  }

  void dequeue_callback_b() {
    auto down_thing = queue_b_.GetDownEnd()->TryDequeue();
    if (!down_thing) {
      log::error("Received dequeue, but no data ready...");
    }

    down_buffer_a_.Enqueue(A_TO_B(std::move(down_thing)), handler_);
  }

  os::Handler* handler_;
  common::BidiQueue<B, A> queue_a_{10};
  common::BidiQueue<B, A> queue_b_{10};
  os::EnqueueBuffer<B> down_buffer_a_{queue_a_.GetDownEnd()};
  os::EnqueueBuffer<B> down_buffer_b_{queue_b_.GetDownEnd()};

public:
  WiredPairOfBiDiQueues(os::Handler* handler) : handler_(handler) {
    queue_a_.GetDownEnd()->RegisterDequeue(
            handler_,
            common::Bind(&WiredPairOfBiDiQueues::dequeue_callback_a, common::Unretained(this)));
    queue_b_.GetDownEnd()->RegisterDequeue(
            handler_,
            common::Bind(&WiredPairOfBiDiQueues::dequeue_callback_b, common::Unretained(this)));
  }

  ~WiredPairOfBiDiQueues() {
    queue_a_.GetDownEnd()->UnregisterDequeue();
    queue_b_.GetDownEnd()->UnregisterDequeue();
  }

  /* This methd returns the UpEnd of queue A */
  common::BidiQueueEnd<A, B>* GetQueueAUpEnd() { return queue_a_.GetUpEnd(); }

  /* This methd returns the UpEnd of queue B */
  common::BidiQueueEnd<A, B>* GetQueueBUpEnd() { return queue_b_.GetUpEnd(); }
};

namespace {
std::unique_ptr<packet::PacketView<packet::kLittleEndian>> BuilderToView(
        std::unique_ptr<packet::BasePacketBuilder> up_thing) {
  auto bytes = std::make_shared<std::vector<uint8_t>>();
  bluetooth::packet::BitInserter i(*bytes);
  bytes->reserve(up_thing->size());
  up_thing->Serialize(i);
  return std::make_unique<packet::PacketView<packet::kLittleEndian>>(bytes);
}
}  // namespace

using WiredPairOfL2capQueues =
        WiredPairOfBiDiQueues<packet::BasePacketBuilder, packet::PacketView<packet::kLittleEndian>,
                              BuilderToView>;

}  // namespace testing
}  // namespace common
}  // namespace bluetooth
