#include "buffered_peripheral.h"
#include <stdlib.h>
#include <string.h>

void buffered_transceiver_init(buffered_transceiver_t *transceiver,
                               queue_t *rx_queue, queue_t *tx_queue,
                               void *peripheral, buffered_transceiver_vtable_t vtable) {
  transceiver->rx_queue = rx_queue;
  transceiver->tx_queue = tx_queue;
  transceiver->peripheral = peripheral;
  transceiver->vtable = vtable;
}

uint16_t buffered_transceiver_transmit(buffered_transceiver_t *transceiver,
                                            void *buffer, uint16_t size) {
  uint16_t sent = 0;

  if (size == 0) {
    size = strlen((char*)buffer);
  }

  while (sent < size && queue_space(transceiver->tx_queue) > 0) {
    queue_enqueue(transceiver->tx_queue, buffer + sent * transceiver->tx_queue->element_size);
    sent++;
  }

  buffered_transceiver_trigger_transmit(transceiver, 1);

  return sent;
}

uint16_t buffered_transceiver_receive(buffered_transceiver_t *transceiver,
                                      void *buffer, uint16_t max_size) {
  // Get all data from buffer
  buffered_transceiver_trigger_receive(transceiver, 1);
  uint16_t received = 0;

  while (received < max_size && queue_count(transceiver->rx_queue) > 0) {
    queue_dequeue_safely(transceiver->rx_queue, buffer + received*transceiver->rx_queue->element_size);
    received++;
  }

  return received;
}

void buffered_transceiver_trigger_receive(buffered_transceiver_t *transceiver, uint16_t size) {
  void* element = alloca(transceiver->rx_queue->element_size);

  while (transceiver->vtable.can_receive(transceiver->peripheral) > 0 && queue_space(transceiver->rx_queue) > 0) {
    uint16_t received = transceiver->vtable.receive(transceiver->peripheral, element, 1);

    if (received > 0) {
      queue_enqueue(transceiver->rx_queue, element);
    }
  }
}

bool buffered_transceiver_trigger_transmit(buffered_transceiver_t *transceiver,
                                           uint16_t size) {
  while (transceiver->vtable.can_transmit(transceiver->peripheral) &&
         queue_count(transceiver->tx_queue) > 0) {
    void* data = alloca(transceiver->tx_queue->element_size);
    queue_dequeue_safely(transceiver->tx_queue, data);
    transceiver->vtable.transmit(transceiver->peripheral, data, 1);
    return true;
  }

  return false;
}
