/*
 * Copyright (c) 2019, 2021 Qualcomm Technologies, Inc.
 * All Rights Reserved.
 * Confidential and Proprietary - Qualcomm Technologies, Inc.
 */

#include "CAdder_open.h"
#include "IAdder_invoke.h"
#include "object.h"
#include "qsee_heap.h"
#include "qsee_log.h"
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* Context struct for the IAdder object. By using the IAdder_OP_x codes for
 * the last call rather than function names, we can avoid copying strings around
 * since the caller also has access to these definitions. */
typedef struct CAdder {
  int refs;
  statusStruct status;
} CAdder;

/* Internal function to our service that performs a saturated addition. We can use this
 * to ensure that we return UINT32_MAX when our addition operations overflow. */
static int32_t saturated_add(uint32_t val1, uint32_t val2, uint32_t *result)
{
  if (val1 >= UINT32_MAX - val2) {
    *result = UINT32_MAX;
    return IAdder_OVERFLOW_TRUE;
  }

  else {
    *result = val1 + val2;
    return IAdder_OVERFLOW_FALSE;
  }
}

/* We'll keep this function internal to our IAdder service implementation,
 * and use it to update our context's status struct after each function call. */
static void update_results(CAdder *me, uint32_t result, int32_t lastCall, int32_t overflow)
{
  me->status.callCount++;
  me->status.lastCall = lastCall;
  me->status.lastResult = result;
  me->status.resultSaturated = overflow;

  /* Check for overflow in status.total, and clamp total to UINT32_MAX. */
  overflow = saturated_add(me->status.total, result, &me->status.total);
  if (overflow == IAdder_OVERFLOW_TRUE) {
    qsee_log(QSEE_LOG_MSG_ERROR, "IAdder total overflowed when updating results!");
    me->status.totalSaturated = IAdder_OVERFLOW_TRUE;
  }
}

/* Release is called to decrement the reference counter of this object. When the
 * reference count reaches 0 (i.e. everything retaining a reference to it has
 * called release), the object is freed. */
static int32_t CAdder_release(CAdder *me)
{
  if (--me->refs == 0) {
    QSEE_FREE_PTR(me);
  }
  return Object_OK;
}

/* When retain is called, this IAdder object's reference count is
 * incremented. This would be called when keeping a new reference to this
 * object. */
static int32_t CAdder_retain(CAdder *me)
{
  me->refs++;
  return Object_OK;
}

/* A simple example showing how to add two numeric values inside the TA and
 * give the result back to the caller. */
static int32_t CAdder_add(CAdder *me, uint32_t val1, uint32_t val2, uint32_t *result_ptr)
{
  int32_t overflow;

  overflow = saturated_add(val1, val2, result_ptr);

  update_results(me, *result_ptr, IAdder_OP_add, overflow);  // Update our object context
  return Object_OK;
}

/* An example showing how to utilize an array paramater; in this case we sum
 * the values of the array and set result_ptr to the result. */
static int32_t CAdder_addArray(CAdder *me,
                               const uint32_t *intArray_ptr,
                               size_t intArray_len,
                               uint32_t *result_ptr)
{
  uint32_t sum = 0;
  int32_t overflow;

  if (intArray_len == 0) {
    qsee_log(QSEE_LOG_MSG_ERROR, "intArray length received was zero!");
    return Object_ERROR;
  }

  for (size_t i = 0; i < intArray_len; i++) {
    overflow = saturated_add(sum, intArray_ptr[i], &sum);
    if (overflow == IAdder_OVERFLOW_TRUE) {
      break;
    }
  }
  *result_ptr = sum;

  update_results(me, sum, IAdder_OP_addArray, overflow);
  return Object_OK;
}

/* An example showing how to utilize a struct as a paramater. Defining the
 * struct inside IAdder.idl results in the struct typedef being compiled
 * automatically into the IAdder.h header. We can use this struct to pass
 * bundled information between the caller and callee. In this function we
 * use a pointer to the additionStruct struct we've defined to pass two values
 * in to be added together. */
static int32_t CAdder_addStruct(CAdder *me,
                                const additionStruct *inStruct_ptr,
                                uint32_t *result_ptr)
{
  int32_t overflow;

  overflow = saturated_add(inStruct_ptr->int1, inStruct_ptr->int2, result_ptr);

  update_results(me, *result_ptr, IAdder_OP_addStruct, overflow);
  return Object_OK;
}

static int32_t CAdder_getStatus(CAdder *me, statusStruct *status)
{
  *status = me->status;
  return Object_OK;
}

/* Function declaration for the invoke function of the IAdder interface.
 */
static IAdder_DEFINE_INVOKE(CAdder_invoke, CAdder_, CAdder *);

int32_t CAdder_open(Object *objOut)
{
  CAdder *me = QSEE_ZALLOC_TYPE(CAdder);
  if (!me) {
    qsee_log(QSEE_LOG_MSG_ERROR, "Memory allocation for CAdder failed!");
    return Object_ERROR_MEM;
  }

  me->refs = 1;
  me->status.callCount = 0;
  me->status.total = 0;
  me->status.totalSaturated = IAdder_OVERFLOW_FALSE;

  /* Set me->lastCall to -1, since we know that all OP codes start at 0. */
  me->status.lastCall = -1;

  *objOut = (Object){CAdder_invoke, me};

  return Object_OK;
}
