/*
 * 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 "clar.h"

#include "util/mbuf.h"
#include "util/mbuf_iterator.h"

#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_serial.h"

extern MBuf *s_free_list;


// Setup

void test_mbuf__initialize(void) {
}

void test_mbuf__cleanup(void) {
}


// Tests

void test_mbuf__length(void) {
  // test the mbuf_get_length()/mbuf_get_chain_length() functions
  char *data = __FILE_NAME__; // dummy data
  char data_length = sizeof(__FILE_NAME__);
  MBuf mbuf1 = MBUF_EMPTY;
  MBuf mbuf2 = MBUF_EMPTY;
  MBuf mbuf3 = MBUF_EMPTY;

  // empty mbuf chain
  cl_assert(mbuf_get_chain_length(NULL) == 0);

  // single empty mbuf in the chain
  mbuf1 = MBUF_EMPTY;
  cl_assert(mbuf_get_length(&mbuf1) == 0);
  cl_assert(mbuf_get_chain_length(&mbuf1) == 0);

  // single mbuf of non-zero length
  mbuf1 = MBUF_EMPTY;
  mbuf_set_data(&mbuf1, data, data_length);
  cl_assert(mbuf_get_length(&mbuf1) == data_length);
  cl_assert(mbuf_get_chain_length(&mbuf1) == data_length);

  // three mbufs of 0 length
  mbuf1 = MBUF_EMPTY;
  mbuf2 = MBUF_EMPTY;
  mbuf3 = MBUF_EMPTY;
  mbuf_append(&mbuf1, &mbuf2);
  mbuf_append(&mbuf1, &mbuf3);
  cl_assert(mbuf_get_length(&mbuf1) == 0);
  cl_assert(mbuf_get_chain_length(&mbuf1) == 0);

  // three mbufs of non-zero length in a chain
  mbuf1 = MBUF_EMPTY;
  mbuf2 = MBUF_EMPTY;
  mbuf3 = MBUF_EMPTY;
  mbuf_set_data(&mbuf1, data, data_length);
  mbuf_set_data(&mbuf2, data, data_length);
  mbuf_set_data(&mbuf3, data, data_length);
  mbuf_append(&mbuf1, &mbuf2);
  mbuf_append(&mbuf1, &mbuf3);
  cl_assert(mbuf_get_length(&mbuf1) == data_length);
  cl_assert(mbuf_get_length(&mbuf2) == data_length);
  cl_assert(mbuf_get_length(&mbuf3) == data_length);
  cl_assert(mbuf_get_chain_length(&mbuf1) == (3 * data_length));

  // three mbufs with one of zero length
  mbuf1 = MBUF_EMPTY;
  mbuf2 = MBUF_EMPTY;
  mbuf3 = MBUF_EMPTY;
  mbuf_set_data(&mbuf1, data, data_length);
  mbuf_set_data(&mbuf3, data, data_length);
  mbuf_append(&mbuf1, &mbuf2);
  mbuf_append(&mbuf1, &mbuf3);
  cl_assert(mbuf_get_length(&mbuf1) == data_length);
  cl_assert(mbuf_get_length(&mbuf2) == 0);
  cl_assert(mbuf_get_length(&mbuf3) == data_length);
  cl_assert(mbuf_get_chain_length(&mbuf1) == (2 * data_length));
}

void test_mbuf__iter_empty(void) {
  // test iteratoring over empty mbuf chains
  MBuf mbuf1 = MBUF_EMPTY;
  MBuf mbuf2 = MBUF_EMPTY;
  MBufIterator iter;
  mbuf_append(&mbuf1, &mbuf2);
  mbuf_iterator_init(&iter, NULL);
  cl_assert(mbuf_iterator_is_finished(&iter));
  mbuf_iterator_init(&iter, &mbuf2);
  cl_assert(mbuf_iterator_is_finished(&iter));
  mbuf_iterator_init(&iter, &mbuf1);
  cl_assert(mbuf_iterator_is_finished(&iter));
  uint8_t data;
  cl_assert(!mbuf_iterator_read_byte(&iter, &data));
  cl_assert(!mbuf_iterator_get_current_mbuf(&iter));
}

void test_mbuf__iter_modify(void) {
  // modify (read and then write) the data in an mbuf chain using an mbuf iterator
  // test reading from an mbuf chain via an mbuf iterator
  uint8_t data1[] = {10, 11, 12};
  uint8_t data3[] = {13, 14, 15};
  MBufIterator write_iter, read_iter;
  MBuf mbuf1 = MBUF_EMPTY;
  MBuf mbuf2 = MBUF_EMPTY;
  MBuf mbuf3 = MBUF_EMPTY;
  mbuf_set_data(&mbuf1, data1, 3);
  mbuf_set_data(&mbuf3, data3, 3);
  mbuf_append(&mbuf1, &mbuf2);
  mbuf_append(&mbuf1, &mbuf3);
  mbuf_iterator_init(&write_iter, &mbuf1);
  mbuf_iterator_init(&read_iter, &mbuf1);
  for (int i = 0; i < 6; i++) {
    cl_assert(!mbuf_iterator_is_finished(&write_iter));
    cl_assert(!mbuf_iterator_is_finished(&read_iter));
    // check we're on the exected mbuf
    if (i < 3) {
      cl_assert(mbuf_iterator_get_current_mbuf(&write_iter) == &mbuf1);
      cl_assert(mbuf_iterator_get_current_mbuf(&read_iter) == &mbuf1);
    } else {
      cl_assert(mbuf_iterator_get_current_mbuf(&write_iter) == &mbuf3);
      cl_assert(mbuf_iterator_get_current_mbuf(&read_iter) == &mbuf3);
    }
    uint8_t data_byte = 0;
    bool have_byte = mbuf_iterator_read_byte(&read_iter, &data_byte);
    cl_assert(have_byte);
    // check that the data is what we expect
    cl_assert(data_byte == (i + 10));
    // modify the data by increasing the value by 10
    cl_assert(mbuf_iterator_write_byte(&write_iter, data_byte + 10));
  }
  cl_assert(mbuf_iterator_is_finished(&write_iter));
  cl_assert(mbuf_iterator_is_finished(&read_iter));
  // verify the final value of the data
  for (int i = 0; i < 6; i++) {
    uint8_t *data;
    int index;
    if (i < 3) {
      data = data1;
      index = i;
    } else {
      data = data3;
      index = i - 3;
    }
    cl_assert(data[index] == (i + 20));
  }
}

static int prv_get_free_list_length(void) {
  int len = 0;
  for (MBuf *m = s_free_list; m; m = mbuf_get_next(m)) {
    len++;
  }
  return len;
}

void test_mbuf__mbuf_pool(void) {
  // get an MBuf and the pool should still be empty
  MBuf *mbuf1 = mbuf_get(NULL, 0, MBufPoolUnitTest);
  cl_assert(prv_get_free_list_length() == 0);

  // free the mbuf and the pool should now contain it
  mbuf_free(mbuf1);
  cl_assert(prv_get_free_list_length() == 1);
  cl_assert(s_free_list == mbuf1);

  // get another mbuf and expect that it's the same one and the pool is empty
  MBuf *mbuf2 = mbuf_get(NULL, 0, MBufPoolUnitTest);
  cl_assert(mbuf2 == mbuf1);
  cl_assert(prv_get_free_list_length() == 0);

  // get another mbuf and expect that it's not the same as the previous one
  MBuf *mbuf3 = mbuf_get(NULL, 0, MBufPoolUnitTest);
  cl_assert(mbuf3 != mbuf2);
  cl_assert(prv_get_free_list_length() == 0);

  // free both of the mbufs (one at a time)
  mbuf_free(mbuf2);
  cl_assert(prv_get_free_list_length() == 1);
  mbuf_free(mbuf3);
  cl_assert(prv_get_free_list_length() == 2);
}