/*
 * Copyright 2024 Google LLC
 *
 * 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.
 */

#include "fake_queue.h"

#include "util/circular_buffer.h"

#include "FreeRTOS.h"
#include "queue.h"

#include <stdlib.h>

typedef struct {
  uint16_t item_size;
  bool is_semph;

  //! @return ticks spent executing the callback
  TickType_t (*yield_cb)(QueueHandle_t);
  CircularBuffer circular_buffer;
  uint8_t storage[];
} FakeQueue;

signed portBASE_TYPE xQueueGenericReceive(QueueHandle_t xQueue, void * const pvBuffer,
                                          TickType_t xTicksToWait, portBASE_TYPE xJustPeeking) {
  FakeQueue *q = (FakeQueue *) xQueue;
  TickType_t ticks_waited = 0;
  while (true) {
    uint16_t read_space = circular_buffer_get_read_space_remaining(&q->circular_buffer);
    if (read_space >= q->item_size) {
      circular_buffer_consume(&q->circular_buffer, q->item_size);
      return pdTRUE;
    } else {
      if (!xTicksToWait || !q->yield_cb) {
        return pdFALSE;
      } else {
        ticks_waited += q->yield_cb(xQueue);
        if (ticks_waited >= xTicksToWait) {
          return pdFALSE;
        }
      }
    }
  }
}

signed portBASE_TYPE xQueueGenericSend(QueueHandle_t xQueue, const void * const pvItemToQueue,
                                       TickType_t xTicksToWait, portBASE_TYPE xCopyPosition) {
  FakeQueue *q = (FakeQueue *) xQueue;
  TickType_t ticks_waited = 0;
  while (true) {
    uint16_t write_space = circular_buffer_get_write_space_remaining(&q->circular_buffer);
    if (write_space >= q->item_size) {
      // Just write a zero for a semaphore
      const uint8_t semph_data = 0;
      const uint8_t *data = q->is_semph ? &semph_data : (const uint8_t *) pvItemToQueue;
      circular_buffer_write(&q->circular_buffer, data, q->item_size);
      return pdTRUE;
    } else {
      if (!xTicksToWait || !q->yield_cb) {
        return pdFALSE;
      } else {
        ticks_waited += q->yield_cb(xQueue);
        if (ticks_waited >= xTicksToWait) {
          return pdFALSE;
        }
      }
    }
  }
}

QueueHandle_t xQueueGenericCreate(unsigned portBASE_TYPE uxQueueLength,
                                 unsigned portBASE_TYPE uxItemSize, unsigned char ucQueueType) {
  uint16_t item_size;
  const bool is_semph = (ucQueueType == queueQUEUE_TYPE_BINARY_SEMAPHORE);
  if (is_semph) {
    item_size = 1;
  } else {
    item_size = uxItemSize;
  }
  const uint16_t storage_size = (item_size * uxQueueLength);
  uint8_t *buffer = malloc(sizeof(FakeQueue) + storage_size);
  FakeQueue *q = (FakeQueue *) buffer;
  *q = (const FakeQueue) {
    .item_size = item_size,
    .is_semph = is_semph,
  };
  circular_buffer_init(&q->circular_buffer, q->storage, storage_size);
  return q;
}

void vQueueDelete(QueueHandle_t xQueue) {
  free(xQueue);
}

QueueHandle_t xQueueCreateMutex(unsigned char ucQueueType) {
  return (QueueHandle_t)1;
}

portBASE_TYPE xQueueTakeMutexRecursive(QueueHandle_t pxMutex, TickType_t xBlockTime) {
  return pdTRUE;
}

portBASE_TYPE xQueueGiveMutexRecursive(QueueHandle_t xMutex) {
  return pdTRUE;
}

BaseType_t xQueueGenericReset(QueueHandle_t xQueue, BaseType_t xNewQueue) {
  return pdTRUE;
}

void fake_queue_set_yield_callback(QueueHandle_t queue,
                                   TickType_t (*yield_cb)(QueueHandle_t)) {
  FakeQueue *fake_queue = (FakeQueue *) queue;
  fake_queue->yield_cb = yield_cb;
}
