/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "event_service_client.h"
#include "applib/applib_malloc.auto.h"
#include "plugin_service.h"
#include "plugin_service_private.h"
#include "syscall/syscall.h"

#include "process_state/app_state/app_state.h"
#include "process_state/worker_state/worker_state.h"
#include "system/logging.h"
#include "system/passert.h"

// ---------------------------------------------------------------------------------------------------------------
// Get our state variables
static PluginServiceState* prv_get_state(PebbleTask task) {
  if (task == PebbleTask_Unknown) {
    task = pebble_task_get_current();
  }
  if (task == PebbleTask_App) {
    return app_state_get_plugin_service();
  } else {
    PBL_ASSERTN(task == PebbleTask_Worker);
    return worker_state_get_plugin_service();
  }
}


// ---------------------------------------------------------------------------------------------------------------
// Lookup the plugin service index from the UUID. We store the index in the event structure instead of the UUID
//  so that we have payload room.
static uint16_t prv_get_service_index(Uuid *uuid) {
  return sys_event_service_get_plugin_service_index(uuid);
}


// ---------------------------------------------------------------------------------------------------------------
// Used by list_find to locate the handler for a specfic service index.
static bool prv_service_filter(ListNode *node, void *tp) {
  PluginServiceEntry *info = (PluginServiceEntry *)node;
  uint16_t service_idx = (uint16_t)(uintptr_t)tp;
  return (info->service_index == service_idx);
}


// ---------------------------------------------------------------------------------------------------------------
// Callback provided to the app_event_service. All events of type PEBBLE_PLUGIN_SERVICE_EVENT that get sent to
// this task trigger this callback. From here, we look up which user-supplied callback corresponds to the
// service index stored in the event structure and then pass control to that user-supplied callback.
static void prv_handle_event_service_event(PebbleEvent *e, void *context) {
  PluginServiceState *state = prv_get_state(PebbleTask_Unknown);
  uint16_t service_index = e->plugin_service.service_index;

  ListNode *found;
  ListNode *list = &state->subscribed_services;
  found = list_find(list, prv_service_filter, (void*)(uintptr_t)service_index);
  if (!found) {
    return;
  }

  // Call the handler provided by the client
  PluginServiceEntry *entry = (PluginServiceEntry *)found;
  entry->handler(e->plugin_service.type, &e->plugin_service.data);
}


// ---------------------------------------------------------------------------------------------------------------
// Subscribe to a specific plug-in service by uuid.
bool plugin_service_subscribe(Uuid *uuid, PluginServiceHandler handler) {
  PluginServiceState *state = prv_get_state(PebbleTask_Unknown);
  uint16_t service_index = prv_get_service_index(uuid);

  ListNode *list = &state->subscribed_services;
  if (list_find(list, prv_service_filter, (void*)(uintptr_t)service_index)) {
    PBL_LOG(LOG_LEVEL_DEBUG, "Plug service handler already subscribed");
    return false;
  }

  // Add to handlers list
  PluginServiceEntry *entry = applib_type_zalloc(PluginServiceEntry);
  if (!entry) {
    PBL_LOG(LOG_LEVEL_ERROR, "OOM in plugin_service_subscribe");
    return false;
  }
  entry->service_index = service_index;
  entry->handler = handler;
  list_append(list, &entry->list_node);

  // Subscribe to the app event service if we haven't already
  if (!state->subscribed_to_app_event_service) {
    state->subscribed_to_app_event_service = true;
    event_service_client_subscribe(&state->event_service_info);
  }
  return true;
}


// ---------------------------------------------------------------------------------------------------------------
// Unsubscribe from a specific plug-in service by uuid.
bool plugin_service_unsubscribe(Uuid *uuid) {
  PluginServiceState *state = prv_get_state(PebbleTask_Unknown);
  uint16_t service_index = prv_get_service_index(uuid);

  ListNode *found;
  ListNode *list = &state->subscribed_services;
  found = list_find(list, prv_service_filter, (void*)(uintptr_t)service_index);
  if (!found) {
    PBL_LOG(LOG_LEVEL_DEBUG, "Plug service handler already unsubscribed");
    return true;
  }

  list_remove(found, NULL, NULL);
  applib_free(found);
  return true;
}


// ---------------------------------------------------------------------------------------------------------------
// Send an event to all registered subscribers of the given plugin service identified by UUID.
void plugin_service_send_event(Uuid *uuid, uint8_t type, PluginEventData *data) {
  uint16_t service_index = prv_get_service_index(uuid);

  PebbleEvent event = {
    .type = PEBBLE_PLUGIN_SERVICE_EVENT,
    .plugin_service = {
        .service_index = service_index,
        .type = type,
        .data = *data
    },
  };
  sys_send_pebble_event_to_kernel(&event);
}


// ---------------------------------------------------------------------------------------------------------------
// Init our state variables.
void plugin_service_state_init(PluginServiceState *state) {
  *state = (PluginServiceState) {
    .event_service_info = {
        .type = PEBBLE_PLUGIN_SERVICE_EVENT,
        .handler = &prv_handle_event_service_event,
    },
  };
}

