/*
 * Copyright (C) 2019 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.
 */

/*=============================================================================
  @file sns_qmi_client.c

  ===========================================================================*/

/*=============================================================================
  Include Files
  ===========================================================================*/
#include <inttypes.h>
#include "pb_encode.h"
#include "qmi_client.h"
#include "sns_client.h"
#include "sns_client.pb.h"
#include "sns_client_api_v01.h"
#include "sns_osa.h"

/*=============================================================================
  Type Definitions
  ===========================================================================*/

/* An outgoing request which is awaiting the corresponding response message. */
typedef struct sns_request {
  struct sns_client *client;
  /* Client registered callback */
  sns_client_resp resp_cb;
  void *resp_cb_data;
} sns_request;

/* A client connection; a client process may open multiple QMI connections. */
typedef struct sns_client {
  /* Client registered callbacks */
  sns_client_ind ind_cb;
  void *ind_cb_data;
  sns_client_error error_cb;
  void *error_cb_data;

  /* QMI Client Handle */
  qmi_client_type qmi_handle;
} sns_client;

/*=============================================================================
  Static Function Definitions
  ===========================================================================*/

/* QMI indication callback.  See qmi_client_ind_cb. */
static void client_ind_cb(qmi_client_type user_handle, unsigned int msg_id,
                          void *ind_buf, unsigned int ind_buf_len,
                          void *ind_cb_data) {
  UNUSED_VAR(user_handle);
  sns_client *client = ind_cb_data;
  size_t ind_len = sizeof(sns_client_report_ind_msg_v01);
  sns_client_report_ind_msg_v01 *ind = sns_malloc(ind_len);
  int32_t qmi_err;

  SNS_ASSERT(NULL != ind);

  // Extract the Protocol Buffer encoded message from the outer QMI/IDL  message
  qmi_err = qmi_idl_message_decode(SNS_CLIENT_SVC_get_service_object_v01(),
                                   QMI_IDL_INDICATION, msg_id, ind_buf,
                                   ind_buf_len, ind, ind_len);
  if (QMI_IDL_LIB_NO_ERR != qmi_err) {
    SNS_LOG(ERROR, "QMI decode error %i", qmi_err);
  } else {
    SNS_LOG(VERBOSE, "Indication from client ID %" PRIu64, ind->client_id);

    client->ind_cb(client, ind->payload, ind->payload_len, client->ind_cb_data);
  }

  sns_free(ind);
}

/**
 * Allocate and initialize the response callback handler state for a request.
 *
 * @param[i] client
 * @param[i] resp_cb Callback to be called upon response receipt
 * @param[i] resp_cb_data Optional callback to be delivered
 *
 * @return Newly create request objct
 */
static sns_request *create_request(sns_client *client, sns_client_resp resp_cb,
                                   void *resp_cb_data) {
  sns_request *request;

  request = sns_malloc(sizeof(*request));
  SNS_ASSERT(NULL != request);

  request->resp_cb = resp_cb;
  request->resp_cb_data = resp_cb_data;
  request->client = client;

  return request;
}

/**
 * Handle an incoming QMI response message from the Sensors Service.
 */
static void client_resp_cb(qmi_client_type user_handle, unsigned int msg_id,
                           void *resp_c_struct, unsigned int resp_c_struct_len,
                           void *resp_cb_data,
                           qmi_client_error_type transp_err) {
  UNUSED_VAR(user_handle);
  UNUSED_VAR(msg_id);
  UNUSED_VAR(resp_c_struct_len);
  sns_request *request = resp_cb_data;
  sns_client_resp_msg_v01 *resp = resp_c_struct;
  sns_std_error err = SNS_STD_ERROR_NO_ERROR;

  if (NULL != resp && resp->result_valid) {
    err = resp->result;
    SNS_LOG(VERBOSE, "Response from client %" PRIu64, resp->client_id);
  } else if (QMI_NO_ERR != transp_err) {
    err = SNS_STD_ERROR_FAILED;
  }

  if (NULL != request->resp_cb)
    request->resp_cb(request->client, err, request->resp_cb_data);

  sns_free(request);
  sns_free(resp_c_struct);
}

/**
 * An error occurred; typically means the SLPI has restarted, and the client
 * should attempt to reconnect.
 */
static void client_error_cb(qmi_client_type user_handle,
                            qmi_client_error_type error, void *err_cb_data) {
  UNUSED_VAR(user_handle);
  UNUSED_VAR(error);
  sns_client *client = err_cb_data;

  SNS_LOG(VERBOSE, "Error from client");

  // PEND: Convert QMI transport error
  client->error_cb(client, SNS_STD_ERROR_INVALID_STATE, client->error_cb_data);
}

/*=============================================================================
  Public Function Definitions
  ===========================================================================*/

int sns_client_init(sns_client **client_out, uint32_t timeout,
                    sns_client_ind ind_cb, void *ind_cb_data,
                    sns_client_error error_cb, void *error_cb_data) {
  qmi_idl_service_object_type service_obj =
      SNS_CLIENT_SVC_get_service_object_v01();
  qmi_service_instance instance_id = 0;
  qmi_client_error_type qmi_err;
  qmi_cci_os_signal_type os_params;
  int rv = -1;
  sns_client *client;

  client = sns_malloc(sizeof(*client));
  SNS_ASSERT(NULL != client);
  client->ind_cb = ind_cb;
  client->ind_cb_data = ind_cb_data;
  client->error_cb = error_cb;
  client->error_cb_data = error_cb_data;

  qmi_err =
      qmi_client_init_instance(service_obj, instance_id, client_ind_cb, client,
                               &os_params, timeout, &client->qmi_handle);
  if (QMI_NO_ERR != qmi_err) {
    SNS_LOG(ERROR, "qmi_client_init_instance error %i", qmi_err);
  } else {
    qmi_err =
        qmi_client_register_error_cb(client->qmi_handle, client_error_cb, NULL);

    if (QMI_NO_ERR != qmi_err)
      SNS_LOG(ERROR, "qmi_client_register_error_cb error %i", qmi_err);
    else
      rv = 0;
  }

  if (0 != rv) {
    if (NULL != client->qmi_handle) qmi_client_release(client->qmi_handle);

    sns_free(client);
    client = NULL;
  }

  SNS_LOG(VERBOSE, "sns_client_init %p", client);

  *client_out = client;
  return rv;
}

int sns_client_deinit(sns_client *client) {
  qmi_client_release(client->qmi_handle);
  sns_free(client);

  SNS_LOG(VERBOSE, "sns_client_deinit complete %p", client);
  return 0;
}

int sns_client_send(sns_client *client, sns_client_request_msg *msg,
                    sns_client_resp resp_cb, void *resp_cb_data) {
  int rv = 0;
  pb_ostream_t stream;
  sns_client_req_msg_v01 *req_msg;

  req_msg = sns_malloc(sizeof(*req_msg));
  SNS_ASSERT(NULL != req_msg);

  stream = pb_ostream_from_buffer(req_msg->payload, sizeof(req_msg->payload));
  if (!pb_encode(&stream, sns_client_request_msg_fields, msg)) {
    SNS_LOG(ERROR, "pb_encode error: %s", PB_GET_ERROR(&stream));
    rv = -1;
  } else {
    qmi_txn_handle txn_handle;
    qmi_client_error_type qmi_err;
    sns_request *request;
    size_t resp_len = sizeof(sns_client_resp_msg_v01);
    void *resp;

    resp = sns_malloc(resp_len);
    SNS_ASSERT(NULL != resp);
    request = create_request(client, resp_cb, resp_cb_data);

    req_msg->payload_len = stream.bytes_written;
    qmi_err = qmi_client_send_msg_async(
        client->qmi_handle, SNS_CLIENT_REQ_V01, req_msg, sizeof(*req_msg), resp,
        resp_len, client_resp_cb, request, &txn_handle);

    if (QMI_NO_ERR != qmi_err) {
      SNS_LOG(ERROR, "qmi_client_send_msg_async error %i", qmi_err);
      sns_free(resp);
      sns_free(request);
      rv = -2;
    }
  }

  sns_free(req_msg);
  return rv;
}
