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

#pragma once

#include "services/common/system_task.h"
#include "fake_pebble_tasks.h"

#include "util/list.h"

#include "clar_asserts.h"

#include <string.h>
#include <stdint.h>

typedef struct {
  ListNode node;
  SystemTaskEventCallback callback;
  void *data;
} SystemTaskCallbackNode;

static ListNode *s_system_task_callback_head = NULL;
static bool s_invoke_as_current = false;
static uint32_t system_task_available_space = ~(uint32_t)0;

bool system_task_add_callback(SystemTaskEventCallback cb, void *data) {
  SystemTaskCallbackNode *node = (SystemTaskCallbackNode *) malloc(sizeof(SystemTaskCallbackNode));
  cl_assert(node != NULL);
  list_init(&node->node);

  cl_assert(cb);
  node->callback = cb;
  node->data = data;

  s_system_task_callback_head = list_prepend(s_system_task_callback_head, &node->node);
  cl_assert(s_system_task_callback_head);
  system_task_available_space--;
  return true;
}

bool system_task_add_callback_from_isr(SystemTaskEventCallback cb, void *data,
                                       bool *should_context_switch) {
  *should_context_switch = false;
  return system_task_add_callback(cb, data);
}

uint32_t system_task_get_available_space(void) {
  return system_task_available_space;
}

void system_task_set_available_space(uint32_t space) {
  system_task_available_space = space;
}

////////////////////////////////////
// Stub:
//
void stub_invoke_system_task_as_current(void) {
  s_invoke_as_current = !s_invoke_as_current;
}

////////////////////////////////////
// Fake manipulation:
//
void *s_fake_system_task_current_cb;

void fake_system_task_callbacks_invoke(int num_to_invoke) {
  PebbleTask current_task = pebble_task_get_current();
  if (!s_invoke_as_current) {
    stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
  }

  // Start at tail ("oldest" callback):
  SystemTaskCallbackNode *node = (SystemTaskCallbackNode *) list_get_tail(s_system_task_callback_head);
  while (node && num_to_invoke) {
    // do callback first, in case callback enqueues more callbacks
    if (node->callback) {
      s_fake_system_task_current_cb = node->callback;
      node->callback(node->data);
      s_fake_system_task_current_cb = NULL;
    }
    SystemTaskCallbackNode *prev = (SystemTaskCallbackNode *) list_get_prev(&node->node);
    list_remove(&node->node, &s_system_task_callback_head, NULL);
    free(node);
    node = prev;

    system_task_available_space++;
    num_to_invoke--;
  }

  stub_pebble_tasks_set_current(current_task);
}

void fake_system_task_callbacks_invoke_pending(void) {
  // sometimes the cb's may add new jobs so we need to keep looping until no
  // more are left
  while (s_system_task_callback_head) {
    fake_system_task_callbacks_invoke(list_count(s_system_task_callback_head));
  }
}

void fake_system_task_callbacks_cleanup(void) {
  SystemTaskCallbackNode *node = (SystemTaskCallbackNode *) s_system_task_callback_head;
  while (node) {
    SystemTaskCallbackNode *next = (SystemTaskCallbackNode *) list_get_next(&node->node);
    list_remove(&node->node, &s_system_task_callback_head, NULL);
    free(node);
    node = next;
  }
  cl_assert(s_system_task_callback_head == NULL);
}

void system_task_watchdog_feed(void) {
}

uint32_t fake_system_task_count_callbacks(void) {
  return list_count(s_system_task_callback_head);
}

void system_task_enable_raised_priority(bool is_raised) {
}

bool system_task_is_ready_to_run(void) {
  return true;
}

void* system_task_get_current_callback(void) {
  return s_fake_system_task_current_cb;
}