/*************************************************************************************************/
/*!
 *  \file
 *
 *  \brief  Glucose profile example record database and access functions.
 *
 *  Copyright (c) 2012-2018 Arm Ltd.
 *
 *  Copyright (c) 2019 Packetcraft, Inc.
 *
 *  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 "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "util/bstream.h"
#include "att_api.h"
#include "svc_ch.h"
#include "svc_gls.h"
#include "app_api.h"
#include "glps_api.h"
#include "glps_main.h"

/**************************************************************************************************
  Macros
**************************************************************************************************/

/*! Number of records in example database */
#define GLPS_DB_NUM_RECORDS     3

/**************************************************************************************************
  Local Variables
**************************************************************************************************/

/*! Control block */
static struct
{
  uint8_t     numRec;
} glpsDbCb;

/*! Example record database */
static glpsRec_t glpsDb[GLPS_DB_NUM_RECORDS] =
{
  /* record 1 */
  {
    /* measurement */
    {
      CH_GLM_FLAG_TIME_OFFSET | CH_GLM_FLAG_CONC_TYPE_LOC |     /* Flags */
      CH_GLM_FLAG_UNITS_MOL_L | CH_GLM_FLAG_SENSOR_STATUS,
      0x0001,                                                   /* Sequence number */
      {2012, 6, 15, 20, 52, 36},                                /* Base time */
      60,                                                       /* Time offset */
      SFLT_TO_UINT16(50, -3),                                   /* Glucose concentration (SFLOAT) */
      CH_GLM_LOC_FINGER | (CH_GLM_TYPE_ART_BLOOD << 4),         /* Sample type and sample location */
      CH_GLM_STATUS_BATT_LOW                                    /* Sensor status annunciation */
    },

    /* context */
    {
      CH_GLMC_FLAG_CARB |  CH_GLMC_FLAG_MEAL |                  /* Flags */
      CH_GLMC_FLAG_TESTER | CH_GLMC_FLAG_EXERCISE |
      CH_GLMC_FLAG_MED | CH_GLMC_FLAG_MED_KG |
      CH_GLMC_FLAG_HBA1C,
      0x0001,                                                   /* Sequence number */
      0,                                                        /* Extended Flags */
      CH_GLMC_CARB_DINNER,                                      /* Carbohydrate ID */
      SFLT_TO_UINT16(12, -3),                                   /* Carbohydrate (SFLOAT) */
      CH_GLMC_MEAL_POSTPRANDIAL,                                /* Meal */
      CH_GLMC_HEALTH_NONE | (CH_GLMC_TESTER_SELF << 4),         /* Tester and health */
      300,                                                      /* Exercise Duration */
      99,                                                       /* Exercise Intensity */
      CH_GLMC_MED_LONG,                                         /* Medication ID */
      SFLT_TO_UINT16(50, -6),                                   /* Medication (SFLOAT) */
      SFLT_TO_UINT16(10, 0)                                     /* HbA1c */
    },
  },

  /* record 2 */
  {
    /* measurement */
    {
      CH_GLM_FLAG_CONC_TYPE_LOC | CH_GLM_FLAG_UNITS_KG_L |      /* Flags */
      CH_GLM_FLAG_CONTEXT_INFO,
      0x0002,                                                   /* Sequence number */
      {2012, 6, 16, 6, 45, 20},                                 /* Base time */
      0,                                                        /* Time offset */
      SFLT_TO_UINT16(20, -5),                                   /* Glucose concentration (SFLOAT) */
      CH_GLM_LOC_FINGER | (CH_GLM_TYPE_ART_BLOOD << 4),         /* Sample type and sample location */
      0                                                         /* Sensor status annunciation */
    },

    /* context */
    {
      CH_GLMC_FLAG_CARB |  CH_GLMC_FLAG_MEAL |                  /* Flags */
      CH_GLMC_FLAG_TESTER | CH_GLMC_FLAG_EXERCISE |
      CH_GLMC_FLAG_MED | CH_GLMC_FLAG_MED_L |
      CH_GLMC_FLAG_HBA1C,
      0x0002,                                                   /* Sequence number */
      0,                                                        /* Extended Flags */
      CH_GLMC_CARB_BREAKFAST,                                   /* Carbohydrate ID */
      SFLT_TO_UINT16(3, -3),                                    /* Carbohydrate (SFLOAT) */
      CH_GLMC_MEAL_PREPRANDIAL,                                 /* Meal */
      CH_GLMC_HEALTH_NONE | (CH_GLMC_TESTER_SELF << 4),         /* Tester and health */
      1000,                                                     /* Exercise Duration */
      25,                                                       /* Exercise Intensity */
      CH_GLMC_MED_LONG,                                         /* Medication ID */
      SFLT_TO_UINT16(10, -3),                                   /* Medication (SFLOAT) */
      SFLT_TO_UINT16(11, 0)                                     /* HbA1c */
    },
  },

  /* record 3 */
  {
    /* measurement */
    {
      CH_GLM_FLAG_CONC_TYPE_LOC | CH_GLM_FLAG_UNITS_KG_L,       /* Flags */
      0x0003,                                                   /* Sequence number */
      {2012, 6, 16, 18, 45, 20},                                /* Base time */
      0,                                                        /* Time offset */
      SFLT_TO_UINT16(20, -5),                                   /* Glucose concentration (SFLOAT) */
      CH_GLM_LOC_FINGER | (CH_GLM_TYPE_ART_BLOOD << 4),         /* Sample type and sample location */
      0                                                         /* Sensor status annunciation */
    },

    /* context */
    {
      CH_GLMC_FLAG_TESTER | CH_GLMC_FLAG_EXERCISE |             /* Flags */
      CH_GLMC_FLAG_MED | CH_GLMC_FLAG_MED_L |
      CH_GLMC_FLAG_HBA1C,
      0x0003,                                                   /* Sequence number */
      0,                                                        /* Extended Flags */
      0,                                                        /* Carbohydrate ID */
      SFLT_TO_UINT16(0, 0),                                     /* Carbohydrate (SFLOAT) */
      0,                                                        /* Meal */
      CH_GLMC_HEALTH_NONE | (CH_GLMC_TESTER_SELF << 4),         /* Tester and health */
      1001,                                                     /* Exercise Duration */
      26,                                                       /* Exercise Intensity */
      CH_GLMC_MED_LONG,                                         /* Medication ID */
      SFLT_TO_UINT16(15, -3),                                   /* Medication (SFLOAT) */
      SFLT_TO_UINT16(12, 0)                                     /* HbA1c */
    },
  }
};

/*************************************************************************************************/
/*!
 *  \brief  Get last database record.
 *
 *  \return Pointer to record or NULL if no record.
 */
/*************************************************************************************************/
static glpsRec_t *glpsDbGetEnd(void)
{
  if (glpsDbCb.numRec == 0)
  {
    return NULL;
  }

  return (glpsRec_t *) &glpsDb[glpsDbCb.numRec - 1];
}

/*************************************************************************************************/
/*!
 *  \brief  Get the next database record after the given current record.
 *
 *  \param  pCurrRec    Pointer to current record.
 *
 *  \return Pointer to record or NULL if no record.
 */
/*************************************************************************************************/
static glpsRec_t *glpsDbGetNext(glpsRec_t *pCurrRec)
{
  if (glpsDbCb.numRec == 0 || pCurrRec == glpsDbGetEnd())
  {
    return NULL;
  }
  else if (pCurrRec == NULL)
  {
    return (glpsRec_t *) glpsDb;
  }
  else
  {
    return (pCurrRec + 1);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Get the next record filtered for "all".
 *
 *  \param  pCurrRec    Pointer to current record.
 *  \param  pRec        Return pointer to next record, if found.
 *
 *  \return CH_RACP_RSP_SUCCESS if a record is found, otherwise an error status is returned.
 */
/*************************************************************************************************/
static uint8_t glpsDbOpAll(glpsRec_t *pCurrRec,  glpsRec_t **pRec)
{
  *pRec = glpsDbGetNext(pCurrRec);
  return (*pRec != NULL) ? CH_RACP_RSP_SUCCESS : CH_RACP_RSP_NO_RECORDS;
}

/*************************************************************************************************/
/*!
 *  \brief  Get the next record filtered for "greater than or equal to sequence number".
 *
 *  \param  pFilter     Glucose service RACP filter parameters.
 *  \param  pCurrRec    Pointer to current record.
 *  \param  pRec        Return pointer to next record, if found.
 *
 *  \return CH_RACP_RSP_SUCCESS if a record is found, otherwise an error status is returned.
 */
/*************************************************************************************************/
static uint8_t glpsDbOpGteqSeqNum(uint8_t *pFilter, glpsRec_t *pCurrRec,  glpsRec_t **pRec)
{
  uint16_t seqNum;

  /* parse seq number */
  pFilter++;
  BYTES_TO_UINT16(seqNum, pFilter);

  /* find record */
  while ((*pRec = glpsDbGetNext(pCurrRec)) != NULL)
  {
    if ((*pRec)->meas.seqNum >= seqNum)
    {
      return CH_RACP_RSP_SUCCESS;
    }
    pCurrRec = *pRec;
  }

  return CH_RACP_RSP_NO_RECORDS;
}

/*************************************************************************************************/
/*!
 *  \brief  Get the next record filtered for "last".
 *
 *  \param  pCurrRec    Pointer to current record.
 *  \param  pRec        Return pointer to next record, if found.
 *
 *  \return CH_RACP_RSP_SUCCESS if a record is found, otherwise an error status is returned.
 */
/*************************************************************************************************/
static uint8_t glpsDbOpLast(glpsRec_t *pCurrRec, glpsRec_t **pRec)
{
  /* if current record is already last return failure */
  *pRec = glpsDbGetEnd();
  if (*pRec != NULL && *pRec != pCurrRec)
  {
    return CH_RACP_RSP_SUCCESS;
  }
  else
  {
    return CH_RACP_RSP_NO_RECORDS;
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize the glucose record database.
 *
 *  \return None.
 */
/*************************************************************************************************/
void glpsDbInit(void)
{
  glpsDbCb.numRec = GLPS_DB_NUM_RECORDS;
}

/*************************************************************************************************/
/*!
 *  \brief  Get the next record that matches the given filter parameters that follows
 *          the given current record.
 *
 *  \param  oper        Operator.
 *  \param  pFilter     Glucose service RACP filter parameters.
 *  \param  pCurrRec    Pointer to current record.
 *  \param  pRec        Return pointer to next record, if found.
 *
 *  \return CH_RACP_RSP_SUCCESS if a record is found, otherwise an error status is returned.
 */
/*************************************************************************************************/
uint8_t glpsDbGetNextRecord(uint8_t oper, uint8_t *pFilter, glpsRec_t *pCurrRec,  glpsRec_t **pRec)
{
  uint8_t status;

  switch (oper)
  {
    case CH_RACP_OPERATOR_ALL:
      status = glpsDbOpAll(pCurrRec, pRec);
      break;

    case CH_RACP_OPERATOR_GTEQ:
      /* check filter type */
      if (*pFilter == CH_RACP_GLS_FILTER_SEQ)
      {
        status = glpsDbOpGteqSeqNum(pFilter, pCurrRec, pRec);
      }
      else
      {
        status = CH_RACP_RSP_OPERAND_NOT_SUP;
      }
      break;

    case CH_RACP_OPERATOR_LAST:
      status = glpsDbOpLast(pCurrRec, pRec);
      break;

    case CH_RACP_OPERATOR_NULL:
      status = CH_RACP_RSP_INV_OPERATOR;
      break;

    default:
      status = CH_RACP_RSP_OPERATOR_NOT_SUP;
      break;

  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief  Delete records that match the given filter parameters.
 *
 *  \param  oper        Operator.
 *  \param  pFilter     Glucose service RACP filter parameters.
 *
 *  \return CH_RACP_RSP_SUCCESS if records deleted, otherwise an error status is returned.
 */
/*************************************************************************************************/
uint8_t glpsDbDeleteRecords(uint8_t oper, uint8_t *pFilter)
{
  /* only 'all records' is supported */
  if (oper == CH_RACP_OPERATOR_ALL)
  {
    glpsDbCb.numRec = 0;

    return CH_RACP_RSP_SUCCESS;
  }
  else
  {
    return CH_RACP_RSP_OPERATOR_NOT_SUP;
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Get the number of records matching the filter parameters.
 *
 *  \param  oper        Operator.
 *  \param  pFilter     Glucose service RACP filter parameters.
 *  \param  pNumRec     Returns number of records which match filter parameters.

 *
 *  \return RACP status.
 */
/*************************************************************************************************/
uint8_t glpsDbGetNumRecords(uint8_t oper, uint8_t *pFilter, uint8_t *pNumRec)
{
  glpsRec_t *pCurrRec = NULL;
  uint8_t   status;

  *pNumRec = 0;
  while ((status = glpsDbGetNextRecord(oper, pFilter, pCurrRec, &pCurrRec)) == CH_RACP_RSP_SUCCESS)
  {
    (*pNumRec)++;
  }

  if (status == CH_RACP_RSP_NO_RECORDS)
  {
    status = CH_RACP_RSP_SUCCESS;
  }

  return status;
}

/*************************************************************************************************/
/*!
*  \brief  Generate a new record.
*
*  \return None.
*/
/*************************************************************************************************/
void glpsDbGenerateRecord(void)
{
  if (glpsDbCb.numRec < GLPS_DB_NUM_RECORDS)
  {
    glpsDbCb.numRec++;
  }
}

/*************************************************************************************************/
/*!
*  \brief  For conformance testing only.  Toggle the sample data record number 2's medication
*          quantity unit flag between Kilograms and Liters.  Also modifies quantity.
*
*  \return None.
*/
 /*************************************************************************************************/
void glpsDbToggleMedicationUnits(void)
{
  if (glpsDb[1].context.flags & CH_GLMC_FLAG_MED_L)
  {
    /* Change medication quantity to Kilograms. */
    glpsDb[1].context.flags &= ~CH_GLMC_FLAG_MED_L;
    glpsDb[1].context.flags |= CH_GLMC_FLAG_MED_KG;
    glpsDb[1].context.medication = SFLT_TO_UINT16(50, -6);
  }
  else
  {
    /* Change medication quantiy to Liters. */
    glpsDb[1].context.flags &= ~CH_GLMC_FLAG_MED_KG;
    glpsDb[1].context.flags |= CH_GLMC_FLAG_MED_L;
    glpsDb[1].context.medication = SFLT_TO_UINT16(10, -3);
  }
}
