/*
 * 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.
 */

typedef struct MockListNode {
  struct MockListNode *next;
  struct MockListNode *prev;
  const char *func;
  uintmax_t value;
  ssize_t count;
} MockListNode;

static MockListNode s_mock_list_head = {
  &s_mock_list_head, &s_mock_list_head,
  NULL, 0, -1,
};

uintmax_t clar__mock(const char *const func, const char *const file, const size_t line)
{
  MockListNode *node;
  // Walk the list backwards, for FIFO behavior.
  for (node = s_mock_list_head.prev; node != &s_mock_list_head; node = node->prev) {
    if (node->func == NULL) {
      continue;
    } else if (strcmp(node->func, func) == 0) {
      break;
    }
  }
  char error_msg[128];
  snprintf(error_msg, sizeof(error_msg), "No more mock values available for '%s'!", func);
  cl_assert_(node != &s_mock_list_head, error_msg);

	// Save the value.
  uintmax_t value = node->value;
  cl_assert_(node->count != 0, "Mock node count is invalid");
  // If the node isn't permanent, lower its counter.
  // If the result is zero, we need to remove this mock value.
  if (node->count > 0 && --node->count == 0) {
    node->prev->next = node->next;
    node->next->prev = node->prev;
    free(node);
  }
  return value;
}

void clar__will_return(const char *const func, const char *const file, const size_t line,
                       const uintmax_t value, const ssize_t count)
{
  MockListNode *node = calloc(1, sizeof(MockListNode));
  cl_assert_(func != NULL, "cl_will_return with invalid function name");
  node->func = func;
  node->value = value;
  node->count = count;

  // Add to beginning of list
  node->next = s_mock_list_head.next;
  node->prev = &s_mock_list_head;
  s_mock_list_head.next = node;
  // Fixup the next entry.
  // In the case that the list was empty before this call, this will make
  // s_mock_list_head.prev point to the new node, which is what we want.
  cl_assert_(node->next != NULL, "Mock list corrupted!");
  node->next->prev = node;
}

static void clar_mock_reset(void)
{
	s_mock_list_head.next = &s_mock_list_head;
	s_mock_list_head.prev = &s_mock_list_head;
	s_mock_list_head.func = NULL;
	s_mock_list_head.value = 0;
	s_mock_list_head.count = -1;
}

static void clar_mock_cleanup(void)
{
  MockListNode *node, *next;
  for (node = s_mock_list_head.next; node != &s_mock_list_head; node = next) {
    next = node->next;
    free(node);
  }
  clar_mock_reset();
}

// Who tests the test framework!
#if 0
int gack(void)
{
  return cl_mock_type(int);
}

int main(int argc, const char *argv[])
{
  cl_will_return(gack, 573);
  printf("gack() = %d\n", gack()); // 573
  cl_will_return(gack, 123);
  cl_will_return(gack, 456);
  cl_will_return(gack, 789);
  printf("gack() = %d\n", gack()); // 123
  printf("gack() = %d\n", gack()); // 456
  printf("gack() = %d\n", gack()); // 789
  cl_will_return_count(gack, 765, 3);
  printf("gack() = %d\n", gack()); // 765
  printf("gack() = %d\n", gack()); // 765
  printf("gack() = %d\n", gack()); // 765

  printf("gack() = %d\n", gack());
  return 0;
}
#endif