/* =========================================================================
   example.c -- Example code for QCBOR

   Copyright (c) 2020-2021, Laurence Lundblade. All rights reserved.
   Copyright (c) 2021, Arm Limited. All rights reserved.

   SPDX-License-Identifier: BSD-3-Clause

   See BSD-3-Clause license in file named "LICENSE"

   Created on 6/30/2020
  ========================================================================== */

#include <stdio.h>
#include "example.h"
#include "qcbor/qcbor_encode.h"
#include "qcbor/qcbor_decode.h"
#include "qcbor/qcbor_spiffy_decode.h"


/**
 * This is a simple example of encoding and decoding some CBOR from
 * and to a C structure.
 *
 * This also includes a comparison between the original structure
 * and the one decoded from the CBOR to confirm correctness.
 */


#define MAX_CYLINDERS 16

/**
 * The data structure representing a car engine that is encoded and
 * decoded in this example.
 */
typedef struct
{
   UsefulBufC Manufacturer;
   int64_t    uDisplacement;
   int64_t    uHorsePower;
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
   double     dDesignedCompresion;
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
   int64_t    uNumCylinders;
   bool       bTurboCharged;
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
   struct {
      double dMeasuredCompression;
   } cylinders[MAX_CYLINDERS];
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
} CarEngine;


/**
 * @brief Initialize the Engine data structure with values to encode.
 *
 * @param[out] pE   The Engine structure to fill in
 */
void EngineInit(CarEngine *pE)
{
   pE->Manufacturer        = UsefulBuf_FROM_SZ_LITERAL("Porsche");
   pE->uDisplacement       = 3296;
   pE->uHorsePower         = 210;
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
   pE->dDesignedCompresion = 9.1;
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
   pE->uNumCylinders       = 6;
   pE->bTurboCharged       = false;

#ifndef USEFULBUF_DISABLE_ALL_FLOAT
   pE->cylinders[0].dMeasuredCompression = 9.0;
   pE->cylinders[1].dMeasuredCompression = 9.2;
   pE->cylinders[2].dMeasuredCompression = 8.9;
   pE->cylinders[3].dMeasuredCompression = 8.9;
   pE->cylinders[4].dMeasuredCompression = 9.1;
   pE->cylinders[5].dMeasuredCompression = 9.0;
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
}


/**
 * @brief Compare two Engine structure for equality.
 *
 * @param[in] pE1  First Engine to compare.
 * @param[in] pE2  Second Engine to compare.
 *
 * @retval Return @c true if the two Engine data structures are exactly the
 *         same.
 */
static bool EngineCompare(const CarEngine *pE1, const CarEngine *pE2)
{
   if(pE1->uNumCylinders != pE2->uNumCylinders) {
      return false;
   }
   if(pE1->bTurboCharged != pE2->bTurboCharged) {
      return false;
   }
   if(pE1->uDisplacement != pE2->uDisplacement) {
      return false;
   }
   if(pE1->uHorsePower != pE2->uHorsePower) {
      return false;
   }
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
   if(pE1->dDesignedCompresion != pE2->dDesignedCompresion) {
      return false;
   }
   for(int64_t i = 0; i < pE2->uNumCylinders; i++) {
      if(pE1->cylinders[i].dMeasuredCompression !=
         pE2->cylinders[i].dMeasuredCompression) {
         return false;
      }
   }
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */

   if(UsefulBuf_Compare(pE1->Manufacturer, pE2->Manufacturer)) {
      return false;
   }

    return true;
}


/**
 * @brief Encode an initialized CarEngine data structure in CBOR.
 *
 * @param[in] pEngine  The data structure to encode.
 * @param[in] Buffer   Pointer and length of buffer to output to.
 *
 * @return  The pointer and length of the encoded CBOR or
 *          @ref NULLUsefulBufC on error.
 *
 * This encodes the input structure @c pEngine as a CBOR map of
 * label-value pairs. An array of float is one of the items in the
 * map.
 *
 * This uses the UsefulBuf convention of passing in a non-const empty
 * buffer to be filled in and returning a filled in const buffer. The
 * buffer to write into is given as a pointer and length in a
 * UsefulBuf. The buffer returned with the encoded CBOR is a
 * UsefulBufC also a pointer and length. In this implementation the
 * pointer to the returned data is exactly the same as that of the
 * empty buffer. The returned length will be smaller than or equal to
 * that of the empty buffer. This gives correct const-ness for the
 * buffer passed in and the data returned.
 *
 * @c Buffer must be big enough to hold the output. If it is not @ref
 * NULLUsefulBufC will be returned. @ref NULLUsefulBufC will be
 * returned for any other encoding errors.
 *
 * This can be called with @c Buffer set to @ref SizeCalculateUsefulBuf
 * in which case the size of the encoded engine will be calculated,
 * but no actual encoded CBOR will be output. The calculated size is
 * in @c .len of the returned @ref UsefulBufC.
 */
UsefulBufC EncodeEngine(const CarEngine *pEngine, UsefulBuf Buffer)
{
   /* Set up the encoding context with the output buffer */
    QCBOREncodeContext EncodeCtx;
    QCBOREncode_Init(&EncodeCtx, Buffer);

    /* Proceed to output all the items, letting the internal error
     * tracking do its work */
    QCBOREncode_OpenMap(&EncodeCtx);
    QCBOREncode_AddTextToMapSZ(&EncodeCtx, "Manufacturer", pEngine->Manufacturer);
    QCBOREncode_AddInt64ToMapSZ(&EncodeCtx, "NumCylinders", pEngine->uNumCylinders);
    QCBOREncode_AddInt64ToMapSZ(&EncodeCtx, "Displacement", pEngine->uDisplacement);
    QCBOREncode_AddInt64ToMapSZ(&EncodeCtx, "Horsepower", pEngine->uHorsePower);
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
    QCBOREncode_AddDoubleToMapSZ(&EncodeCtx, "DesignedCompression", pEngine->dDesignedCompresion);
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
   QCBOREncode_OpenArrayInMapSZ(&EncodeCtx, "Cylinders");
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
    for(int64_t i = 0 ; i < pEngine->uNumCylinders; i++) {
        QCBOREncode_AddDouble(&EncodeCtx,
                              pEngine->cylinders[i].dMeasuredCompression);
    }
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
    QCBOREncode_CloseArray(&EncodeCtx);
    QCBOREncode_AddBoolToMapSZ(&EncodeCtx, "Turbo", pEngine->bTurboCharged);
    QCBOREncode_CloseMap(&EncodeCtx);

    /* Get the pointer and length of the encoded output. If there was
     * any encoding error, it will be returned here */
    UsefulBufC EncodedCBOR;
    QCBORError uErr;
    uErr = QCBOREncode_Finish(&EncodeCtx, &EncodedCBOR);
    if(uErr != QCBOR_SUCCESS) {
       return NULLUsefulBufC;
    } else {
       return EncodedCBOR;
    }
}


/**
 * Error results when decoding an Engine data structure.
 */
typedef enum  {
    EngineSuccess,
    CBORNotWellFormed,
    TooManyCylinders,
    EngineProtocolerror,
    WrongNumberOfCylinders
} EngineDecodeErrors;


/**
 * Convert @ref QCBORError to @ref EngineDecodeErrors.
 */
static EngineDecodeErrors ConvertError(QCBORError uErr)
{
    EngineDecodeErrors uReturn;

    switch(uErr)
    {
        case QCBOR_SUCCESS:
            uReturn = EngineSuccess;
            break;

        case QCBOR_ERR_HIT_END:
            uReturn = CBORNotWellFormed;
            break;

        default:
            uReturn = EngineProtocolerror;
            break;
    }

    return uReturn;
}


/**
 * @brief Simplest engine decode using spiffy decode features.
 *
 * @param[in] EncodedEngine  Pointer and length of CBOR-encoded engine.
 * @param[out] pE            The structure filled in from the decoding.
 *
 * @return The decode error or success.
 *
 * This decodes the CBOR into the engine structure.
 *
 * As QCBOR automatically supports both definite and indefinite maps
 * and arrays, this will decode either.
 *
 * This uses QCBOR's spiffy decode functions, so the implementation is
 * simple and closely parallels the encode implementation in
 * EncodeEngineDefiniteLength().
 *
 * Another way to decode without using spiffy decode functions is to
 * use QCBORDecode_GetNext() to traverse the whole tree.  This
 * requires a more complex implementation, but is faster and will pull
 * in less code from the CBOR library. The speed advantage is likely
 * of consequence when decoding much much larger CBOR on slow small
 * CPUs.
 *
 * A middle way is to use the spiffy decode
 * QCBORDecode_GetItemsInMap().  The implementation has middle
 * complexity and uses less CPU.
 */
EngineDecodeErrors DecodeEngineSpiffy(UsefulBufC EncodedEngine, CarEngine *pE)
{
    QCBORError         uErr;
    QCBORDecodeContext DecodeCtx;

    /* Let QCBORDecode internal error tracking do its work. */
    QCBORDecode_Init(&DecodeCtx, EncodedEngine, QCBOR_DECODE_MODE_NORMAL);
    QCBORDecode_EnterMap(&DecodeCtx, NULL);
    QCBORDecode_GetTextStringInMapSZ(&DecodeCtx, "Manufacturer", &(pE->Manufacturer));
    QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "Displacement", &(pE->uDisplacement));
    QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "Horsepower", &(pE->uHorsePower));
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
    QCBORDecode_GetDoubleInMapSZ(&DecodeCtx, "DesignedCompression", &(pE->dDesignedCompresion));
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
    QCBORDecode_GetBoolInMapSZ(&DecodeCtx, "Turbo", &(pE->bTurboCharged));

    QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "NumCylinders", &(pE->uNumCylinders));

    /* Check the internal tracked error now before going on to
     * reference any of the decoded data, particularly
     * pE->uNumCylinders */
    uErr = QCBORDecode_GetError(&DecodeCtx);
    if(uErr != QCBOR_SUCCESS) {
        goto Done;
    }

    if(pE->uNumCylinders > MAX_CYLINDERS) {
        return TooManyCylinders;
    }

    QCBORDecode_EnterArrayFromMapSZ(&DecodeCtx, "Cylinders");
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
    for(int64_t i = 0; i < pE->uNumCylinders; i++) {
        QCBORDecode_GetDouble(&DecodeCtx,
                              &(pE->cylinders[i].dMeasuredCompression));
    }
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
    QCBORDecode_ExitArray(&DecodeCtx);
    QCBORDecode_ExitMap(&DecodeCtx);

    /* Catch further decoding error here */
    uErr = QCBORDecode_Finish(&DecodeCtx);

Done:
    return ConvertError(uErr);
}


int32_t RunQCborExample(void)
{
   CarEngine                 InitialEngine;
   CarEngine                 DecodedEngine;

   /* For every buffer used by QCBOR a pointer and a length are always
    * carried in a UsefulBuf. This is a secure coding and hygene
    * practice to help make sure code never runs off the end of a
    * buffer.
    *
    * UsefulBuf structures are passed as a stack parameter to make the
    * code prettier. The object code generated isn't much different
    * from passing a pointer parameter and a length parameter.
    *
    * This macro is equivalent to:
    *    uint8_t    __pBufEngineBuffer[300];
    *    UsefulBuf  EngineBuffer = {__pBufEngineBuffer, 300};
    */
   UsefulBuf_MAKE_STACK_UB(  EngineBuffer, 300);

   /* The pointer in UsefulBuf is not const and used for representing
    * a buffer to be written to. For UsefulbufC, the pointer is const
    * and is used to represent a buffer that has been written to.
    */
   UsefulBufC                EncodedEngine;
   EngineDecodeErrors        uErr;

   /* Initialize the structure with some values. */
   EngineInit(&InitialEngine);

   /* Encode the engine structure. */
   EncodedEngine = EncodeEngine(&InitialEngine, EngineBuffer);
   if(UsefulBuf_IsNULLC(EncodedEngine)) {
      printf("Engine encode failed\n");
      goto Done;
   }
   printf("Example: Definite Length Engine Encoded in %zu bytes\n",
          EncodedEngine.len);

   /* Decode the CBOR */
   uErr = DecodeEngineSpiffy(EncodedEngine, &DecodedEngine);
   printf("Example: Spiffy Engine Decode Result: %d\n", uErr);
   if(uErr) {
      goto Done;
   }

   /* Check the results */
   if(!EngineCompare(&InitialEngine, &DecodedEngine)) {
      printf("Example: Spiffy Engine Decode comparison fail\n");
   }


   /* Further example of how to calculate the encoded size, then allocate */
   UsefulBufC EncodedEngineSize;
   EncodedEngineSize = EncodeEngine(&InitialEngine, SizeCalculateUsefulBuf);
   if(UsefulBuf_IsNULLC(EncodedEngine)) {
      printf("Engine encode size calculation failed\n");
      goto Done;
   }
   (void)EncodedEngineSize; /* Supress unsed variable warning */
   /* Here malloc could be called to allocate a buffer. Then
    * EncodeEngine() can be called a second time to actually
    * encode. (The actual code is not live here to avoid a
    * dependency on malloc()).
    *  UsefulBuf  MallocedBuffer;
    *  MallocedBuffer.len = EncodedEngineSize.len;
    *  MallocedBuffer.ptr = malloc(EncodedEngineSize.len);
    *  EncodedEngine = EncodeEngine(&InitialEngine, MallocedBuffer);
    */

Done:
   printf("\n");

   return 0;
}
