/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 "chpp/clients/gnss.h"

#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "chpp/app.h"
#include "chpp/clients.h"
#include "chpp/clients/discovery.h"
#include "chpp/common/gnss.h"
#include "chpp/common/gnss_types.h"
#include "chpp/common/standard_uuids.h"
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chpp/memory.h"
#include "chre/pal/gnss.h"
#include "chre_api/chre/gnss.h"

#ifndef CHPP_GNSS_DISCOVERY_TIMEOUT_MS
#define CHPP_GNSS_DISCOVERY_TIMEOUT_MS CHPP_DISCOVERY_DEFAULT_TIMEOUT_MS
#endif

/************************************************
 *  Prototypes
 ***********************************************/

static enum ChppAppErrorCode chppDispatchGnssResponse(void *clientContext,
                                                      uint8_t *buf, size_t len);
static enum ChppAppErrorCode chppDispatchGnssNotification(void *clientContext,
                                                          uint8_t *buf,
                                                          size_t len);
static bool chppGnssClientInit(void *clientContext, uint8_t handle,
                               struct ChppVersion serviceVersion);
static void chppGnssClientDeinit(void *clientContext);
static void chppGnssClientNotifyReset(void *clientContext);
static void chppGnssClientNotifyMatch(void *clientContext);

/************************************************
 *  Private Definitions
 ***********************************************/

/**
 * Structure to maintain state for the GNSS client and its Request/Response
 * (RR) functionality.
 */
struct ChppGnssClientState {
  struct ChppClientState client;     // GNSS client state
  const struct chrePalGnssApi *api;  // GNSS PAL API

  struct ChppRequestResponseState rRState[CHPP_GNSS_CLIENT_REQUEST_MAX + 1];

  uint32_t capabilities;           // Cached GetCapabilities result
  bool requestStateResyncPending;  // requestStateResync() is waiting to be
                                   // processed
  bool capabilitiesValid;  // Flag to indicate if the capabilities result
                           // is valid
};

// Note: This global definition of gGnssClientContext supports only one
// instance of the CHPP GNSS client at a time.
struct ChppGnssClientState gGnssClientContext;
static const struct chrePalSystemApi *gSystemApi;
static const struct chrePalGnssCallbacks *gCallbacks;

/**
 * Configuration parameters for this client
 */
static const struct ChppClient kGnssClientConfig = {
    .descriptor.uuid = CHPP_UUID_GNSS_STANDARD,

    // Version
    .descriptor.version.major = 1,
    .descriptor.version.minor = 0,
    .descriptor.version.patch = 0,

    // Notifies client if CHPP is reset
    .resetNotifierFunctionPtr = &chppGnssClientNotifyReset,

    // Notifies client if they are matched to a service
    .matchNotifierFunctionPtr = &chppGnssClientNotifyMatch,

    // Service response dispatch function pointer
    .responseDispatchFunctionPtr = &chppDispatchGnssResponse,

    // Service notification dispatch function pointer
    .notificationDispatchFunctionPtr = &chppDispatchGnssNotification,

    // Service response dispatch function pointer
    .initFunctionPtr = &chppGnssClientInit,

    // Service notification dispatch function pointer
    .deinitFunctionPtr = &chppGnssClientDeinit,

    // Number of request-response states in the rRStates array.
    .rRStateCount = ARRAY_SIZE(gGnssClientContext.rRState),

    // Min length is the entire header
    .minLength = sizeof(struct ChppAppHeader),
};

/************************************************
 *  Prototypes
 ***********************************************/

static bool chppGnssClientOpen(const struct chrePalSystemApi *systemApi,
                               const struct chrePalGnssCallbacks *callbacks);
static void chppGnssClientClose(void);
static uint32_t chppGnssClientGetCapabilities(void);
static bool chppGnssClientControlLocationSession(bool enable,
                                                 uint32_t minIntervalMs,
                                                 uint32_t minTimeToNextFixMs);
static void chppGnssClientReleaseLocationEvent(
    struct chreGnssLocationEvent *event);
static bool chppGnssClientControlMeasurementSession(bool enable,
                                                    uint32_t minIntervalMs);
static void chppGnssClientReleaseMeasurementDataEvent(
    struct chreGnssDataEvent *event);
static bool chppGnssClientConfigurePassiveLocationListener(bool enable);

static void chppGnssCloseResult(struct ChppGnssClientState *clientContext,
                                uint8_t *buf, size_t len);
static void chppGnssGetCapabilitiesResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);
static void chppGnssControlLocationSessionResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);
static void chppGnssControlMeasurementSessionResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);
static void chppGnssConfigurePassiveLocationListenerResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);

static void chppGnssStateResyncNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);
static void chppGnssLocationResultNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);
static void chppGnssMeasurementResultNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len);

/************************************************
 *  Private Functions
 ***********************************************/

/**
 * Dispatches a service response from the transport layer that is determined to
 * be for the GNSS client.
 *
 * This function is called from the app layer using its function pointer given
 * during client registration.
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 *
 * @return Indicates the result of this function call.
 */
static enum ChppAppErrorCode chppDispatchGnssResponse(void *clientContext,
                                                      uint8_t *buf,
                                                      size_t len) {
  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;
  enum ChppAppErrorCode error = CHPP_APP_ERROR_NONE;

  if (rxHeader->command > CHPP_GNSS_CLIENT_REQUEST_MAX) {
    error = CHPP_APP_ERROR_INVALID_COMMAND;

  } else if (!chppClientTimestampResponse(
                 &gnssClientContext->client,
                 &gnssClientContext->rRState[rxHeader->command], rxHeader)) {
    error = CHPP_APP_ERROR_UNEXPECTED_RESPONSE;

  } else {
    switch (rxHeader->command) {
      case CHPP_GNSS_OPEN: {
        chppClientProcessOpenResponse(&gnssClientContext->client, buf, len);
        if (gnssClientContext->requestStateResyncPending) {
          gCallbacks->requestStateResync();
          gnssClientContext->requestStateResyncPending = false;
        }
        break;
      }

      case CHPP_GNSS_CLOSE: {
        chppGnssCloseResult(gnssClientContext, buf, len);
        break;
      }

      case CHPP_GNSS_GET_CAPABILITIES: {
        chppGnssGetCapabilitiesResult(gnssClientContext, buf, len);
        break;
      }

      case CHPP_GNSS_CONTROL_LOCATION_SESSION: {
        chppGnssControlLocationSessionResult(gnssClientContext, buf, len);
        break;
      }

      case CHPP_GNSS_CONTROL_MEASUREMENT_SESSION: {
        chppGnssControlMeasurementSessionResult(gnssClientContext, buf, len);
        break;
      }

      case CHPP_GNSS_CONFIGURE_PASSIVE_LOCATION_LISTENER: {
        chppGnssConfigurePassiveLocationListenerResult(gnssClientContext, buf,
                                                       len);
        break;
      }

      default: {
        error = CHPP_APP_ERROR_INVALID_COMMAND;
        break;
      }
    }
  }

  return error;
}

/**
 * Dispatches a service notification from the transport layer that is determined
 * to be for the GNSS client.
 *
 * This function is called from the app layer using its function pointer given
 * during client registration.
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 *
 * @return Indicates the result of this function call.
 */
static enum ChppAppErrorCode chppDispatchGnssNotification(void *clientContext,
                                                          uint8_t *buf,
                                                          size_t len) {
  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;
  enum ChppAppErrorCode error = CHPP_APP_ERROR_NONE;

  switch (rxHeader->command) {
    case CHPP_GNSS_REQUEST_STATE_RESYNC_NOTIFICATION: {
      chppGnssStateResyncNotification(gnssClientContext, buf, len);
      break;
    }

    case CHPP_GNSS_LOCATION_RESULT_NOTIFICATION: {
      chppGnssLocationResultNotification(gnssClientContext, buf, len);
      break;
    }

    case CHPP_GNSS_MEASUREMENT_RESULT_NOTIFICATION: {
      chppGnssMeasurementResultNotification(gnssClientContext, buf, len);
      break;
    }

    default: {
      error = CHPP_APP_ERROR_INVALID_COMMAND;
      break;
    }
  }

  return error;
}

/**
 * Initializes the client and provides its handle number and the version of the
 * matched service when/if it the client is matched with a service during
 * discovery.
 *
 * @param clientContext Maintains status for each client instance.
 * @param handle Handle number for this client.
 * @param serviceVersion Version of the matched service.
 *
 * @return True if client is compatible and successfully initialized.
 */
static bool chppGnssClientInit(void *clientContext, uint8_t handle,
                               struct ChppVersion serviceVersion) {
  UNUSED_VAR(serviceVersion);

  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;
  chppClientInit(&gnssClientContext->client, handle);

  return true;
}

/**
 * Deinitializes the client.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppGnssClientDeinit(void *clientContext) {
  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;
  chppClientDeinit(&gnssClientContext->client);
}

/**
 * Notifies the client of an incoming reset.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppGnssClientNotifyReset(void *clientContext) {
  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;

  chppClientCloseOpenRequests(&gnssClientContext->client, &kGnssClientConfig,
                              false /* clearOnly */);

  if (gnssClientContext->client.openState != CHPP_OPEN_STATE_OPENED &&
      !gnssClientContext->client.pseudoOpen) {
    CHPP_LOGW("GNSS client reset but wasn't open");
  } else {
    CHPP_LOGI("GNSS client reopening from state=%" PRIu8,
              gnssClientContext->client.openState);
    gnssClientContext->requestStateResyncPending = true;
    chppClientSendOpenRequest(&gGnssClientContext.client,
                              &gGnssClientContext.rRState[CHPP_GNSS_OPEN],
                              CHPP_GNSS_OPEN,
                              /*blocking=*/false);
  }
}

/**
 * Notifies the client of being matched to a service.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppGnssClientNotifyMatch(void *clientContext) {
  struct ChppGnssClientState *gnssClientContext =
      (struct ChppGnssClientState *)clientContext;

  if (gnssClientContext->client.pseudoOpen) {
    CHPP_LOGD("Pseudo-open GNSS client opening");
    chppClientSendOpenRequest(&gGnssClientContext.client,
                              &gGnssClientContext.rRState[CHPP_GNSS_OPEN],
                              CHPP_GNSS_OPEN,
                              /*blocking=*/false);
  }
}

/**
 * Handles the service response for the close client request.
 *
 * This function is called from chppDispatchGnssResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssCloseResult(struct ChppGnssClientState *clientContext,
                                uint8_t *buf, size_t len) {
  // TODO
  UNUSED_VAR(clientContext);
  UNUSED_VAR(buf);
  UNUSED_VAR(len);
}

/**
 * Handles the service response for the get capabilities client request.
 *
 * This function is called from chppDispatchGnssResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssGetCapabilitiesResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  if (len < sizeof(struct ChppGnssGetCapabilitiesResponse)) {
    CHPP_LOGE("Bad GNSS capabilities len=%" PRIuSIZE, len);

  } else {
    struct ChppGnssGetCapabilitiesParameters *result =
        &((struct ChppGnssGetCapabilitiesResponse *)buf)->params;

    CHPP_LOGD("chppGnssGetCapabilitiesResult received capabilities=0x%" PRIx32,
              result->capabilities);

    CHPP_ASSERT((result->capabilities & CHPP_GNSS_DEFAULT_CAPABILITIES) ==
                CHPP_GNSS_DEFAULT_CAPABILITIES);
    if (result->capabilities != CHPP_GNSS_DEFAULT_CAPABILITIES) {
      CHPP_LOGE("GNSS capabilities 0x%" PRIx32 " != 0x%" PRIx32,
                result->capabilities, CHPP_GNSS_DEFAULT_CAPABILITIES);
    }

    clientContext->capabilitiesValid = true;
    clientContext->capabilities = result->capabilities;
  }
}

/**
 * Handles the service response for the Control Location Session client request.
 *
 * This function is called from chppDispatchGnssResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssControlLocationSessionResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);

  if (len < sizeof(struct ChppGnssControlLocationSessionResponse)) {
    // Short response length indicates an error
    gCallbacks->locationStatusChangeCallback(
        false, chppAppShortResponseErrorHandler(buf, len, "ControlLocation"));

  } else {
    struct ChppGnssControlLocationSessionResponse *result =
        (struct ChppGnssControlLocationSessionResponse *)buf;

    CHPP_LOGD(
        "chppGnssControlLocationSessionResult received enable=%d, "
        "errorCode=%" PRIu8,
        result->enabled, result->errorCode);

    gCallbacks->locationStatusChangeCallback(result->enabled,
                                             result->errorCode);
  }
}

/**
 * Handles the service response for the Control Measurement Session client
 * request.
 *
 * This function is called from chppDispatchGnssResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssControlMeasurementSessionResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);

  if (len < sizeof(struct ChppGnssControlMeasurementSessionResponse)) {
    // Short response length indicates an error
    gCallbacks->measurementStatusChangeCallback(
        false, chppAppShortResponseErrorHandler(buf, len, "Measurement"));

  } else {
    struct ChppGnssControlMeasurementSessionResponse *result =
        (struct ChppGnssControlMeasurementSessionResponse *)buf;

    CHPP_LOGD(
        "chppGnssControlMeasurementSessionResult received enable=%d, "
        "errorCode=%" PRIu8,
        result->enabled, result->errorCode);

    gCallbacks->measurementStatusChangeCallback(result->enabled,
                                                result->errorCode);
  }
}

/**
 * Handles the service response for the Configure Passive Location Listener
 * client request.
 *
 * This function is called from chppDispatchGnssResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssConfigurePassiveLocationListenerResult(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);
  UNUSED_VAR(len);

  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;

  if (rxHeader->error != CHPP_APP_ERROR_NONE) {
    CHPP_DEBUG_ASSERT_LOG(false, "Passive scan failed at service");

  } else {
    CHPP_LOGD(
        "WiFi ConfigurePassiveLocationListener request accepted at service");
  }
}

/**
 * Handles the State Resync service notification.
 *
 * This function is called from chppDispatchGnssNotification().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssStateResyncNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(buf);
  UNUSED_VAR(len);
  if (clientContext->client.openState == CHPP_OPEN_STATE_WAITING_TO_OPEN) {
    // If the GNSS client is waiting for the open to proceed, the CHRE handler
    // for requestStateResync() may fail, so we set a flag to process it later
    // when the open has succeeded.
    clientContext->requestStateResyncPending = true;
  } else {
    gCallbacks->requestStateResync();
    clientContext->requestStateResyncPending = false;
  }
}

/**
 * Handles the Location Result service notification.
 *
 * This function is called from chppDispatchGnssNotification().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssLocationResultNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);
  CHPP_LOGD("chppGnssLocationResultNotification received data len=%" PRIuSIZE,
            len);

  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct chreGnssLocationEvent *chre =
      chppGnssLocationEventToChre((struct ChppGnssLocationEvent *)buf, len);

  if (chre == NULL) {
    CHPP_LOGE("Location result conversion failed: len=%" PRIuSIZE, len);
  } else {
    gCallbacks->locationEventCallback(chre);
  }
}

/**
 * Handles the Measurement Result service notification.
 *
 * This function is called from chppDispatchGnssNotification().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppGnssMeasurementResultNotification(
    struct ChppGnssClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);
  CHPP_LOGD(
      "chppGnssMeasurementResultNotification received data len=%" PRIuSIZE,
      len);

  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct chreGnssDataEvent *chre =
      chppGnssDataEventToChre((struct ChppGnssDataEvent *)buf, len);

  if (chre == NULL) {
    CHPP_LOGE("Measurement result conversion failed len=%" PRIuSIZE, len);
  } else {
    gCallbacks->measurementEventCallback(chre);
  }
}

/**
 * Initializes the GNSS client upon an open request from CHRE and responds
 * with the result.
 *
 * @param systemApi CHRE system function pointers.
 * @param callbacks CHRE entry points.
 *
 * @return True if successful. False otherwise.
 */
static bool chppGnssClientOpen(const struct chrePalSystemApi *systemApi,
                               const struct chrePalGnssCallbacks *callbacks) {
  CHPP_DEBUG_ASSERT(systemApi != NULL);
  CHPP_DEBUG_ASSERT(callbacks != NULL);

  bool result = false;
  gSystemApi = systemApi;
  gCallbacks = callbacks;

  CHPP_LOGD("GNSS client opening");
  if (gGnssClientContext.client.appContext == NULL) {
    CHPP_LOGE("GNSS client app is null");
  } else {
    if (chppWaitForDiscoveryComplete(gGnssClientContext.client.appContext,
                                     CHPP_GNSS_DISCOVERY_TIMEOUT_MS)) {
      result = chppClientSendOpenRequest(
          &gGnssClientContext.client,
          &gGnssClientContext.rRState[CHPP_GNSS_OPEN], CHPP_GNSS_OPEN,
          /*blocking=*/true);
    }

    // Since CHPP_GNSS_DEFAULT_CAPABILITIES is mandatory, we can always
    // pseudo-open and return true. Otherwise, these should have been gated.
    chppClientPseudoOpen(&gGnssClientContext.client);
    result = true;
  }

  return result;
}

/**
 * Deinitializes the GNSS client.
 */
static void chppGnssClientClose(void) {
  // Remote
  struct ChppAppHeader *request = chppAllocClientRequestCommand(
      &gGnssClientContext.client, CHPP_GNSS_CLOSE);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else if (chppSendTimestampedRequestAndWait(
                 &gGnssClientContext.client,
                 &gGnssClientContext.rRState[CHPP_GNSS_CLOSE], request,
                 sizeof(*request))) {
    gGnssClientContext.client.openState = CHPP_OPEN_STATE_CLOSED;
    gGnssClientContext.capabilities = CHRE_GNSS_CAPABILITIES_NONE;
    gGnssClientContext.capabilitiesValid = false;
    chppClientCloseOpenRequests(&gGnssClientContext.client, &kGnssClientConfig,
                                true /* clearOnly */);
  }
}

/**
 * Retrieves a set of flags indicating the GNSS features supported by the
 * current implementation.
 *
 * @return Capabilities flags.
 */
static uint32_t chppGnssClientGetCapabilities(void) {
  uint32_t capabilities = CHPP_GNSS_DEFAULT_CAPABILITIES;

  if (gGnssClientContext.capabilitiesValid) {
    // Result already cached
    capabilities = gGnssClientContext.capabilities;

  } else {
    struct ChppAppHeader *request = chppAllocClientRequestCommand(
        &gGnssClientContext.client, CHPP_GNSS_GET_CAPABILITIES);

    if (request == NULL) {
      CHPP_LOG_OOM();
    } else {
      if (chppSendTimestampedRequestAndWait(
              &gGnssClientContext.client,
              &gGnssClientContext.rRState[CHPP_GNSS_GET_CAPABILITIES], request,
              sizeof(*request))) {
        // Success. gGnssClientContext.capabilities is now populated
        if (gGnssClientContext.capabilitiesValid) {
          capabilities = gGnssClientContext.capabilities;
        }
      }
    }
  }

  return capabilities;
}

/**
 * Start/stop/modify the GNSS location session used for clients of the CHRE
 * API.
 *
 * @param enable true to start/modify the session, false to stop the
 *        session. If false, other parameters are ignored.
 * @param minIntervalMs See chreGnssLocationSessionStartAsync()
 * @param minTimeToNextFixMs See chreGnssLocationSessionStartAsync()
 *
 * @return True indicates the request was sent off to the service.
 */

static bool chppGnssClientControlLocationSession(bool enable,
                                                 uint32_t minIntervalMs,
                                                 uint32_t minTimeToNextFixMs) {
  bool result = false;

  struct ChppGnssControlLocationSessionRequest *request =
      chppAllocClientRequestFixed(&gGnssClientContext.client,
                                  struct ChppGnssControlLocationSessionRequest);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else {
    request->header.command = CHPP_GNSS_CONTROL_LOCATION_SESSION;
    request->params.enable = enable;
    request->params.minIntervalMs = minIntervalMs;
    request->params.minTimeToNextFixMs = minTimeToNextFixMs;

    result = chppSendTimestampedRequestOrFail(
        &gGnssClientContext.client,
        &gGnssClientContext.rRState[CHPP_GNSS_CONTROL_LOCATION_SESSION],
        request, sizeof(*request), CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS);
  }

  return result;
}

/**
 * Releases the memory held for the location event callback.
 *
 * @param event Location event to be released.
 */
static void chppGnssClientReleaseLocationEvent(
    struct chreGnssLocationEvent *event) {
  CHPP_FREE_AND_NULLIFY(event);
}

/**
 * Start/stop/modify the raw GNSS measurement session used for clients of the
 * CHRE API.
 *
 * @param enable true to start/modify the session, false to stop the
 *        session. If false, other parameters are ignored.
 * @param minIntervalMs See chreGnssMeasurementSessionStartAsync()
 *
 * @return True indicates the request was sent off to the service.
 */

static bool chppGnssClientControlMeasurementSession(bool enable,
                                                    uint32_t minIntervalMs) {
  bool result = false;

  struct ChppGnssControlMeasurementSessionRequest *request =
      chppAllocClientRequestFixed(
          &gGnssClientContext.client,
          struct ChppGnssControlMeasurementSessionRequest);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else {
    request->header.command = CHPP_GNSS_CONTROL_MEASUREMENT_SESSION;
    request->params.enable = enable;
    request->params.minIntervalMs = minIntervalMs;

    result = chppSendTimestampedRequestOrFail(
        &gGnssClientContext.client,
        &gGnssClientContext.rRState[CHPP_GNSS_CONTROL_MEASUREMENT_SESSION],
        request, sizeof(*request), CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS);
  }

  return result;
}

/**
 * Releases the memory held for the measurement event callback.
 *
 * @param event Measurement event to be released.
 */
static void chppGnssClientReleaseMeasurementDataEvent(
    struct chreGnssDataEvent *event) {
  if (event->measurement_count > 0) {
    void *measurements = CHPP_CONST_CAST_POINTER(event->measurements);
    CHPP_FREE_AND_NULLIFY(measurements);
  }

  CHPP_FREE_AND_NULLIFY(event);
}

/**
 * Starts/stops opportunistic delivery of location fixes.
 *
 * @param enable true to turn the passive location listener on, false to
 *        turn it off.
 *
 * @return True indicates the request was sent off to the service.
 */
static bool chppGnssClientConfigurePassiveLocationListener(bool enable) {
  bool result = false;

  struct ChppGnssConfigurePassiveLocationListenerRequest *request =
      chppAllocClientRequestFixed(
          &gGnssClientContext.client,
          struct ChppGnssConfigurePassiveLocationListenerRequest);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else {
    request->header.command = CHPP_GNSS_CONFIGURE_PASSIVE_LOCATION_LISTENER;
    request->params.enable = enable;

    result = chppSendTimestampedRequestOrFail(
        &gGnssClientContext.client,
        &gGnssClientContext
             .rRState[CHPP_GNSS_CONFIGURE_PASSIVE_LOCATION_LISTENER],
        request, sizeof(*request), CHPP_CLIENT_REQUEST_TIMEOUT_DEFAULT);
  }

  return result;
}

/************************************************
 *  Public Functions
 ***********************************************/

void chppRegisterGnssClient(struct ChppAppState *appContext) {
  chppRegisterClient(appContext, (void *)&gGnssClientContext,
                     &gGnssClientContext.client, gGnssClientContext.rRState,
                     &kGnssClientConfig);
}

void chppDeregisterGnssClient(struct ChppAppState *appContext) {
  // TODO

  UNUSED_VAR(appContext);
}

struct ChppClientState *getChppGnssClientState(void) {
  return &gGnssClientContext.client;
}

#ifdef CHPP_CLIENT_ENABLED_GNSS

#ifdef CHPP_CLIENT_ENABLED_CHRE_GNSS
const struct chrePalGnssApi *chrePalGnssGetApi(uint32_t requestedApiVersion) {
#else
const struct chrePalGnssApi *chppPalGnssGetApi(uint32_t requestedApiVersion) {
#endif

  static const struct chrePalGnssApi api = {
      .moduleVersion = CHPP_PAL_GNSS_API_VERSION,
      .open = chppGnssClientOpen,
      .close = chppGnssClientClose,
      .getCapabilities = chppGnssClientGetCapabilities,
      .controlLocationSession = chppGnssClientControlLocationSession,
      .releaseLocationEvent = chppGnssClientReleaseLocationEvent,
      .controlMeasurementSession = chppGnssClientControlMeasurementSession,
      .releaseMeasurementDataEvent = chppGnssClientReleaseMeasurementDataEvent,
      .configurePassiveLocationListener =
          chppGnssClientConfigurePassiveLocationListener,
  };

  CHPP_STATIC_ASSERT(
      CHRE_PAL_GNSS_API_CURRENT_VERSION == CHPP_PAL_GNSS_API_VERSION,
      "A newer CHRE PAL API version is available. Please update.");

  if (!CHRE_PAL_VERSIONS_ARE_COMPATIBLE(api.moduleVersion,
                                        requestedApiVersion)) {
    return NULL;
  } else {
    return &api;
  }
}

#endif