/* Copyright (c) 2016-2019, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

#include <limits.h>
#include <math.h>

#include "capture.h"
#include "save.h"
#include "composite.h"

#define CONV_GET_X_OFFSET(xoffsets, red, green1, green2, blue) \
            xoffsets[red] = 0;\
            xoffsets[green1] = 1;\
            xoffsets[green2] = 0;\
            xoffsets[blue] = 1;

#define CONV_GET_Y_OFFSET(yoffsets, red, green1, green2, blue) \
            yoffsets[red] = 0;\
            yoffsets[green1] = 0;\
            yoffsets[green2] = 1;\
            yoffsets[blue] = 1;

#define CONV_CALCULATE_PIXEL(pSrcBuff, srcPitch, x, y, xOffset, yOffset) \
            (pSrcBuff[srcPitch*(y + yOffset) + 2*(x + xOffset) + 1] << 2) | \
            (pSrcBuff[srcPitch*(y + yOffset) + 2*(x + xOffset)] >> 6)

#define CONV_CALCULATE_PIXEL_UINT(pSrcBuff, srcPitch, x, y, xOffset, yOffset) \
            pSrcBuff[srcPitch*(y + yOffset) + 2*(x + xOffset) + 1]

enum PixelColor {
    RED,
    GREEN1,
    GREEN2,
    BLUE,
    NUM_PIXEL_COLORS
};

static NvMediaStatus
_ConvGetPixelOffsets(uint32_t pixelOrder,
                     uint32_t *xOffsets,
                     uint32_t *yOffsets)
{
    if (!xOffsets || !yOffsets)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    switch (pixelOrder) {
        case NVM_SURF_ATTR_COMPONENT_ORDER_RGGB:
            CONV_GET_X_OFFSET(xOffsets, RED, GREEN1, GREEN2, BLUE);
            CONV_GET_Y_OFFSET(yOffsets, RED, GREEN1, GREEN2, BLUE);
            break;
        case NVM_SURF_ATTR_COMPONENT_ORDER_GRBG:
            CONV_GET_X_OFFSET(xOffsets, GREEN1, RED, BLUE, GREEN2);
            CONV_GET_Y_OFFSET(yOffsets, GREEN1, RED, BLUE, GREEN2);
            break;
        case NVM_SURF_ATTR_COMPONENT_ORDER_GBRG:
            CONV_GET_X_OFFSET(xOffsets, GREEN1, BLUE, RED, GREEN2);
            CONV_GET_Y_OFFSET(yOffsets, GREEN1, BLUE, RED, GREEN2);
            break;
        case NVM_SURF_ATTR_COMPONENT_ORDER_BGGR:
        default:
            CONV_GET_X_OFFSET(xOffsets, BLUE, GREEN1, GREEN2, RED);
            CONV_GET_Y_OFFSET(yOffsets, BLUE, GREEN1, GREEN2, RED);
            break;
    }

    return NVMEDIA_STATUS_OK;
}

static NvMediaStatus
_ConvRawToRgba(NvMediaImage *imgSrc,
               NvMediaImage *imgDst,
               uint32_t rawBytesPerPixel,
               uint32_t pixelOrder)
{
    NvMediaImageSurfaceMap surfaceMap;
    uint32_t srcImageSize = 0, srcWidth, srcHeight;
    uint32_t dstImageSize = 0, dstWidth, dstHeight;
    uint8_t *pSrcBuff = NULL, *pDstBuff = NULL, *pTmp = NULL;
    uint32_t srcPitch = 0, dstPitch = 0;
    NvMediaStatus status;
    uint8_t alpha = 0xFF;
    uint32_t x = 0, y = 0;
    uint32_t xOffsets[NUM_PIXEL_COLORS] = {0}, yOffsets[NUM_PIXEL_COLORS] = {0};
    NVM_SURF_FMT_DEFINE_ATTR(srcAttr);
    NVM_SURF_FMT_DEFINE_ATTR(dstAttr);

    status = NvMediaSurfaceFormatGetAttrs(imgSrc->type,
                                    srcAttr,
                                    NVM_SURF_FMT_ATTR_MAX);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s:NvMediaSurfaceFormatGetAttrs failed\n", __func__);
        return NVMEDIA_STATUS_ERROR;
    }

    status = NvMediaSurfaceFormatGetAttrs(imgDst->type,
                                    dstAttr,
                                    NVM_SURF_FMT_ATTR_MAX);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s:NvMediaSurfaceFormatGetAttrs failed\n", __func__);
        return NVMEDIA_STATUS_ERROR;
    }

    if (NvMediaImageLock(imgSrc, NVMEDIA_IMAGE_ACCESS_WRITE, &surfaceMap) !=
        NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvMediaImageLock failed\n", __func__);
        return NVMEDIA_STATUS_ERROR;
    }

    srcHeight = surfaceMap.height;
    srcWidth  = surfaceMap.width;

    if (srcAttr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW) {
        srcPitch = srcWidth * rawBytesPerPixel;
        srcImageSize = srcPitch * srcHeight;
        srcImageSize += imgSrc->embeddedDataTopSize;
        srcImageSize += imgSrc->embeddedDataBottomSize;
    } else {
        LOG_ERR("%s: Unsupported source surface type\n", __func__);
        return NVMEDIA_STATUS_ERROR;
    }

    if (!(pSrcBuff = malloc(srcImageSize))) {
        LOG_ERR("%s: Out of memory\n", __func__);
        return NVMEDIA_STATUS_OUT_OF_MEMORY;
    }

    status = NvMediaImageGetBits(imgSrc, NULL, (void **)&pSrcBuff, &srcPitch);
    NvMediaImageUnlock(imgSrc);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvMediaImageGetBits() failed\n", __func__);
        goto done;
    }

    dstHeight = srcHeight / 2;
    dstWidth  = srcWidth / 2;

    if (dstAttr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RGBA) {
        dstPitch = dstWidth * 4;
        dstImageSize = dstHeight * dstPitch;
    } else {
        LOG_ERR("%s: Unsupported destination surface type\n", __func__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    if (!(pDstBuff = calloc(1, dstImageSize))) {
        LOG_ERR("%s: Out of memory\n", __func__);
        status = NVMEDIA_STATUS_OUT_OF_MEMORY;
        goto done;
    }

    pTmp = pDstBuff;
    /* Convert to rgba */

    /* Y is starting at valid pixel, skipping embedded lines from top */
    y = imgSrc->embeddedDataTopSize / srcPitch;

    /* Get offsets for each pixel color */
    status = _ConvGetPixelOffsets(pixelOrder, xOffsets, yOffsets);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get PixelOffsets\n", __func__);
        return status;
    }

    if ((srcAttr[NVM_SURF_ATTR_BITS_PER_COMPONENT].value == NVM_SURF_ATTR_BITS_PER_COMPONENT_12) &&
        (srcAttr[NVM_SURF_ATTR_DATA_TYPE].value == NVM_SURF_ATTR_DATA_TYPE_INT)) {
        for (; y < srcHeight; y += 2) {
            for (x = 0; x < srcWidth; x += 2) {
                /* R */
                *pTmp = CONV_CALCULATE_PIXEL(pSrcBuff, srcPitch, x, y, xOffsets[RED], yOffsets[RED]);
                pTmp++;
                /* G (average of green in BGGR) */
                *pTmp = ((CONV_CALCULATE_PIXEL(pSrcBuff, srcPitch, x, y, xOffsets[GREEN1], yOffsets[GREEN1])) +
                         (CONV_CALCULATE_PIXEL(pSrcBuff, srcPitch, x, y, xOffsets[GREEN2], yOffsets[GREEN2]))) /2 ;
                pTmp++;
                /* B */
                *pTmp = CONV_CALCULATE_PIXEL(pSrcBuff, srcPitch, x, y, xOffsets[BLUE], yOffsets[BLUE]);
                pTmp++;
                /* A */
                *pTmp = alpha;
                pTmp++;
            }
        }
    } else if (((srcAttr[NVM_SURF_ATTR_BITS_PER_COMPONENT].value == NVM_SURF_ATTR_BITS_PER_COMPONENT_10) ||
                (srcAttr[NVM_SURF_ATTR_BITS_PER_COMPONENT].value == NVM_SURF_ATTR_BITS_PER_COMPONENT_12) ||
                (srcAttr[NVM_SURF_ATTR_BITS_PER_COMPONENT].value == NVM_SURF_ATTR_BITS_PER_COMPONENT_16)) &&
               (srcAttr[NVM_SURF_ATTR_DATA_TYPE].value == NVM_SURF_ATTR_DATA_TYPE_UINT)) {
        for (; y < srcHeight; y += 2) {
            for (x = 0; x < srcWidth; x += 2) {
                /* R */
                *pTmp = CONV_CALCULATE_PIXEL_UINT(pSrcBuff, srcPitch, x, y, xOffsets[RED], yOffsets[RED]);
                pTmp++;
                /* G (average of green in BGGR) */
                *pTmp = ((CONV_CALCULATE_PIXEL_UINT(pSrcBuff, srcPitch, x, y, xOffsets[GREEN1], yOffsets[GREEN1])) +
                         (CONV_CALCULATE_PIXEL_UINT(pSrcBuff, srcPitch, x, y, xOffsets[GREEN2], yOffsets[GREEN2]))) /2 ;
                pTmp++;
                /* B */
                *pTmp = CONV_CALCULATE_PIXEL_UINT(pSrcBuff, srcPitch, x, y, xOffsets[BLUE], yOffsets[BLUE]);
                pTmp++;
                /* A */
                *pTmp = alpha;
                pTmp++;
            }
        }
    } else {
        LOG_ERR("%s: Unsupported input raw format\n", __func__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }
    memset(&surfaceMap, 0, sizeof(surfaceMap));

    if (NvMediaImageLock(imgDst, NVMEDIA_IMAGE_ACCESS_WRITE, &surfaceMap) !=
       NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvMediaImageLock failed\n", __func__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    status = NvMediaImagePutBits(imgDst, NULL, (void **)&pDstBuff, &dstPitch);
    NvMediaImageUnlock(imgDst);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvMediaImagePutBits() failed\n", __func__);
        goto done;
    }

    status = NVMEDIA_STATUS_OK;
done:
    if (pSrcBuff)
        free(pSrcBuff);
    if (pDstBuff)
        free(pDstBuff);

    return status;
}

static void
_CreateOutputFileName(char *saveFilePrefix,
                      char *calSettings,
                      uint32_t virtualGroupIndex,
                      uint32_t frame,
                      NvMediaBool useNvRawFormat,
                      char *outputFileName)
{
    char buf[MAX_STRING_SIZE] = {0};

    memset(outputFileName, 0, MAX_STRING_SIZE);
    strncpy(outputFileName, saveFilePrefix, MAX_STRING_SIZE);
    if(calSettings)
        strcat(outputFileName, calSettings);
    strcat(outputFileName, "_vc");
    sprintf(buf, "%d", virtualGroupIndex);
    strcat(outputFileName, buf);
    strcat(outputFileName, "_");
    sprintf(buf, "%02d", frame);
    strcat(outputFileName, buf);
    if (useNvRawFormat)
        strcat(outputFileName, ".nvraw");
    else
        strcat(outputFileName, ".raw");
}

static NvMediaStatus
_CreateImageQueue(NvMediaDevice *device,
                  NvQueue **queue,
                  uint32_t queueSize,
                  uint32_t width,
                  uint32_t height,
                  NvMediaSurfaceType surfType,
                  NvMediaSurfAllocAttr *surfAllocAttrs,
                  uint32_t numSurfAllocAttrs)
{
    uint32_t j = 0;
    NvMediaImage *image = NULL;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (NvQueueCreate(queue,
                      queueSize,
                      sizeof(NvMediaImage *)) != NVMEDIA_STATUS_OK) {
       LOG_ERR("%s: Failed to create image Queue \n", __func__);
       goto failed;
    }

    for (j = 0; j < queueSize; j++) {
        LOG_DBG("%s: NvMediaImageCreateNew\n", __func__);
        image =  NvMediaImageCreateNew(device,           // device
                                    surfType,           // NvMediaSurfaceType type
                                    surfAllocAttrs,     // surf allocation attrs
                                    numSurfAllocAttrs,  // num attrs
                                    0);                 // flags
        if (!image) {
            LOG_ERR("%s: NvMediaImageCreate failed for image %d",
                        __func__, j);
            status = NVMEDIA_STATUS_ERROR;
            goto failed;
        }

        image->tag = *queue;

        if (IsFailed(NvQueuePut(*queue,
                                (void *)&image,
                                NV_TIMEOUT_INFINITE))) {
            LOG_ERR("%s: Pushing image to image queue failed\n", __func__);
            status = NVMEDIA_STATUS_ERROR;
            goto failed;
        }
        image = NULL;
    }

    return NVMEDIA_STATUS_OK;
failed:
    /*images already pushed in the Queue will be cleaned up in SaveFini*/
    if(image) {
        NvMediaImageDestroy(image);
        image = NULL;
    }
    return status;
}

static uint32_t
_GetSettingNum(RuntimeSettings *rtSettings,
               uint32_t numRtSettings,
               uint32_t currentFrame)
{
    uint32_t i = 0, totalFrames = 0;
    RuntimeSettings *settings;

    for(i = 0; i < numRtSettings; i++) {
        settings = &rtSettings[i];
        totalFrames += settings->numFrames;
    }

    currentFrame = currentFrame % totalFrames;
    for(i = 0; i < numRtSettings; i++) {
        settings = &rtSettings[i];
        if(settings->numFrames > currentFrame) {
            return i;
        } else {
            currentFrame -= settings->numFrames;
        }
    }
    return -1;

}

static uint32_t
_SaveThreadFunc(void *data)
{
    SaveThreadCtx *threadCtx = (SaveThreadCtx *)data;
    NvMediaImage *image = NULL;
    NvMediaImageGroup *imageGroup = NULL;
    NvMediaImage *convertedImage = NULL;
    NvMediaStatus status;
    uint32_t totalSavedFrames=0;
    char outputFileName[MAX_STRING_SIZE];
    char buf[MAX_STRING_SIZE] = {0};
    char *calSettings = NULL;

    NVM_SURF_FMT_DEFINE_ATTR(attr);

    while (!(*threadCtx->quit)) {
        image=NULL;
        /* Wait for captured frames */
        while (NvQueueGet(threadCtx->inputQueue, &image, SAVE_DEQUEUE_TIMEOUT) !=
           NVMEDIA_STATUS_OK) {
            LOG_DBG("%s: saveThread input queue %d is empty\n",
                     __func__, threadCtx->virtualGroupIndex);
            if (*threadCtx->quit)
                goto loop_done;
        }

        if (threadCtx->saveEnabled) {
            if (*threadCtx->numRtSettings) {
                calSettings = threadCtx->rtSettings[_GetSettingNum(threadCtx->rtSettings,
                                                                  *threadCtx->numRtSettings,
                                                                  totalSavedFrames)
                                                  ].outputFileName;
            } else if (threadCtx->sensorInfo) {
                memset(buf, 0 , MAX_STRING_SIZE);
                status = threadCtx->sensorInfo->AppendOutputFilename(buf,
                                                                     threadCtx->sensorProperties);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to append output filename\n", __func__);
                    *threadCtx->quit = NVMEDIA_TRUE;
                    goto loop_done;
                }
                calSettings = buf;
            } else {
                calSettings = NULL;
            }

            /* Save image to file */
            _CreateOutputFileName(threadCtx->saveFilePrefix,
                                  calSettings,
                                  threadCtx->virtualGroupIndex,
                                  totalSavedFrames,
                                  threadCtx->useNvRawFormat,
                                  outputFileName);

            LOG_INFO("%s: Write image. res [%u:%u] (file: %s)\n",
                        __func__, image->width, image->height,
                        outputFileName);
            if (threadCtx->useNvRawFormat) {

                status = NvMediaSurfaceFormatGetAttrs(threadCtx->surfType,
                                                      attr,
                                                      NVM_SURF_FMT_ATTR_MAX);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s:NvMediaSurfaceFormatGetAttrs failed\n", __func__);
                   *threadCtx->quit = NVMEDIA_TRUE;
                    goto loop_done;
                }

                if (threadCtx->sensorInfo && (attr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW)) {
                    threadCtx->sensorInfo->WriteNvRawImage(&threadCtx->settingsCommands,
                                                           threadCtx->calParams,
                                                           image,
                                                           totalSavedFrames,
                                                           outputFileName);
                } else {
                    LOG_ERR("%s: NvRawFormat applicable only for RAW captured image \n", __func__);
                    *threadCtx->quit = NVMEDIA_TRUE;
                    goto loop_done;
                }
            } else {
                WriteImage(outputFileName,
                           image,
                           NVMEDIA_TRUE,
                           NVMEDIA_FALSE,
                           threadCtx->rawBytesPerPixel,
                           NULL);
           }
        }

        totalSavedFrames++;

        if (threadCtx->displayEnabled) {

            status = NvMediaSurfaceFormatGetAttrs(threadCtx->surfType,
                                                  attr,
                                                  NVM_SURF_FMT_ATTR_MAX);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s:NvMediaSurfaceFormatGetAttrs failed\n", __func__);
               *threadCtx->quit = NVMEDIA_TRUE;
                goto loop_done;
            }

            if (attr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW) {
                /* Acquire image for storing converting images */
                while (NvQueueGet(threadCtx->conversionQueue,
                                  (void *)&convertedImage,
                                  SAVE_DEQUEUE_TIMEOUT) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: conversionQueue is empty\n", __func__);
                    if (*threadCtx->quit)
                        goto loop_done;
                }

                status = _ConvRawToRgba(image,
                                        convertedImage,
                                        threadCtx->rawBytesPerPixel,
                                        threadCtx->pixelOrder);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: convRawToRgba failed for image %d in saveThread %d\n",
                            __func__, totalSavedFrames, threadCtx->virtualGroupIndex);
                    *threadCtx->quit = NVMEDIA_TRUE;
                    goto loop_done;
                }

                while (NvQueuePut(threadCtx->outputQueue,
                                  &convertedImage,
                                  SAVE_ENQUEUE_TIMEOUT) != NVMEDIA_STATUS_OK) {
                    LOG_DBG("%s: savethread output queue %d is full\n",
                             __func__, threadCtx->virtualGroupIndex);
                    if (*threadCtx->quit)
                        goto loop_done;
                }
                convertedImage = NULL;
            } else {
                while (NvQueuePut(threadCtx->outputQueue,
                                  &image,
                                  SAVE_ENQUEUE_TIMEOUT) != NVMEDIA_STATUS_OK) {
                    LOG_DBG("%s: savethread output queue %d is full\n",
                             __func__, threadCtx->virtualGroupIndex);
                    if (*threadCtx->quit)
                        goto loop_done;
                }
                image=NULL;
            }
        }

        if (threadCtx->numFramesToSave &&
           (totalSavedFrames == threadCtx->numFramesToSave)) {
            *threadCtx->quit = NVMEDIA_TRUE;
            goto loop_done;
        }
    loop_done:
        if (image) {
            imageGroup = (NvMediaImageGroup *)(image->tag);
            if (NvQueuePut((NvQueue *)imageGroup->tag,
                           (void *)&imageGroup,
                           0) != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to put image group back in queue\n", __func__);
                *threadCtx->quit = NVMEDIA_TRUE;
            };
            imageGroup = NULL;
        }
        if (convertedImage) {
            if (NvQueuePut((NvQueue *)convertedImage->tag,
                           (void *)&convertedImage,
                           0) != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to put image back in conversionQueue\n", __func__);
                *threadCtx->quit = NVMEDIA_TRUE;
            }
            convertedImage = NULL;
        }
    }
    LOG_INFO("%s: Save thread exited\n", __func__);
    threadCtx->exitedFlag = NVMEDIA_TRUE;
    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
SaveInit(NvMainContext *mainCtx)
{
    NvSaveContext *saveCtx  = NULL;
    NvCaptureContext   *captureCtx = NULL;
    NvRuntimeSettingsContext *runtimeCtx = NULL;
    TestArgs           *testArgs = mainCtx->testArgs;
    uint32_t i = 0;
    NvMediaStatus status = NVMEDIA_STATUS_ERROR;
    NvMediaSurfAllocAttr surfAllocAttrs[8];
    uint32_t numSurfAllocAttrs;

    /* allocating save context */
    mainCtx->ctxs[SAVE_ELEMENT]= malloc(sizeof(NvSaveContext));
    if (!mainCtx->ctxs[SAVE_ELEMENT]){
        LOG_ERR("%s: Failed to allocate memory for save context\n", __func__);
        return NVMEDIA_STATUS_OUT_OF_MEMORY;
    }

    saveCtx = mainCtx->ctxs[SAVE_ELEMENT];
    memset(saveCtx,0,sizeof(NvSaveContext));
    captureCtx = mainCtx->ctxs[CAPTURE_ELEMENT];
    runtimeCtx = mainCtx->ctxs[RUNTIME_SETTINGS_ELEMENT];

    /* initialize context */
    saveCtx->quit      =  &mainCtx->quit;
    saveCtx->testArgs  = testArgs;
    saveCtx->numVirtualChannels = testArgs->numVirtualChannels;
    saveCtx->displayEnabled = testArgs->displayEnabled;
    saveCtx->inputQueueSize = testArgs->bufferPoolSize;
    /* Create NvMedia Device */
    saveCtx->device = NvMediaDeviceCreate();
    if (!saveCtx->device) {
        status = NVMEDIA_STATUS_ERROR;
        LOG_ERR("%s: Failed to create NvMedia device\n", __func__);
        goto failed;
    }

    /* Create save input Queues and set thread data */
    for (i = 0; i < saveCtx->numVirtualChannels; i++) {
        saveCtx->threadCtx[i].quit = saveCtx->quit;
        saveCtx->threadCtx[i].exitedFlag = NVMEDIA_TRUE;
        saveCtx->threadCtx[i].displayEnabled = testArgs->displayEnabled;
        saveCtx->threadCtx[i].saveEnabled = testArgs->useFilePrefix;
        saveCtx->threadCtx[i].saveFilePrefix = testArgs->filePrefix;
        saveCtx->threadCtx[i].useNvRawFormat = testArgs->useNvRawFormat;
        saveCtx->threadCtx[i].sensorInfo = testArgs->sensorInfo;
        saveCtx->threadCtx[i].calParams = &captureCtx->calParams;
        saveCtx->threadCtx[i].virtualGroupIndex = captureCtx->threadCtx[i].virtualGroupIndex;
        saveCtx->threadCtx[i].numFramesToSave = (testArgs->frames.isUsed)?
                                                 testArgs->frames.uIntValue : 0;
        saveCtx->threadCtx[i].surfType = captureCtx->threadCtx[i].surfType;
        saveCtx->threadCtx[i].pixelOrder = captureCtx->threadCtx[i].pixelOrder;
        saveCtx->threadCtx[i].rawBytesPerPixel = captureCtx->threadCtx[i].rawBytesPerPixel;
        NVM_SURF_FMT_DEFINE_ATTR(attr);
        status = NvMediaSurfaceFormatGetAttrs(captureCtx->threadCtx[i].surfType,
                                              attr,
                                              NVM_SURF_FMT_ATTR_MAX);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s:NvMediaSurfaceFormatGetAttrs failed\n", __func__);
            goto failed;
        }
        saveCtx->threadCtx[i].width =  (attr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW )?
                                           captureCtx->threadCtx[i].width/2 : captureCtx->threadCtx[i].width;
        saveCtx->threadCtx[i].height = (attr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW )?
                                           captureCtx->threadCtx[i].height/2 : captureCtx->threadCtx[i].height;
        saveCtx->threadCtx[i].rtSettings = runtimeCtx->rtSettings;
        saveCtx->threadCtx[i].numRtSettings = &runtimeCtx->numRtSettings;
        saveCtx->threadCtx[i].sensorProperties = testArgs->sensorProperties;
        if (NvQueueCreate(&saveCtx->threadCtx[i].inputQueue,
                         saveCtx->inputQueueSize,
                         sizeof(NvMediaImage *)) != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to create save inputQueue %d\n",
                    __func__, i);
            status = NVMEDIA_STATUS_ERROR;
            goto failed;
        }
        if (testArgs->displayEnabled) {
            if (attr[NVM_SURF_ATTR_SURF_TYPE].value == NVM_SURF_ATTR_SURF_TYPE_RAW ) {
                /* For RAW images, create conversion queue for converting RAW to RGB images */

                surfAllocAttrs[0].type = NVM_SURF_ATTR_WIDTH;
                surfAllocAttrs[0].value = saveCtx->threadCtx[i].width;
                surfAllocAttrs[1].type = NVM_SURF_ATTR_HEIGHT;
                surfAllocAttrs[1].value = saveCtx->threadCtx[i].height;
                surfAllocAttrs[2].type = NVM_SURF_ATTR_CPU_ACCESS;
                surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_UNCACHED;
                numSurfAllocAttrs = 3;

                NVM_SURF_FMT_DEFINE_ATTR(surfFormatAttrs);
                NVM_SURF_FMT_SET_ATTR_RGBA(surfFormatAttrs,RGBA,UINT,8,PL);
                status = _CreateImageQueue(saveCtx->device,
                                           &saveCtx->threadCtx[i].conversionQueue,
                                           saveCtx->inputQueueSize,
                                           saveCtx->threadCtx[i].width,
                                           saveCtx->threadCtx[i].height,
                                           NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX),
                                           surfAllocAttrs,
                                           numSurfAllocAttrs);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: conversionQueue creation failed\n", __func__);
                    goto failed;
                }

                LOG_DBG("%s: Save Conversion Queue %d: %ux%u, images: %u \n",
                        __func__, i, saveCtx->threadCtx[i].width,
                        saveCtx->threadCtx[i].height,
                        saveCtx->inputQueueSize);
            }
        }
    }
    return NVMEDIA_STATUS_OK;
failed:
    LOG_ERR("%s: Failed to initialize Save\n",__func__);
    return status;
}

NvMediaStatus
SaveFini(NvMainContext *mainCtx)
{
    NvSaveContext *saveCtx = NULL;
    NvMediaImage *image = NULL;
    uint32_t i;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    NvMediaImageGroup *imageGroup;

    if (!mainCtx)
        return NVMEDIA_STATUS_OK;

    saveCtx = mainCtx->ctxs[SAVE_ELEMENT];
    if (!saveCtx)
        return NVMEDIA_STATUS_OK;

    /* Wait for threads to exit */
    for (i = 0; i < saveCtx->numVirtualChannels; i++) {
        if (saveCtx->saveThread[i]) {
            while (!saveCtx->threadCtx[i].exitedFlag) {
                LOG_DBG("%s: Waiting for save thread %d to quit\n",
                        __func__, i);
            }
        }
    }

    *saveCtx->quit = NVMEDIA_TRUE;

    /* Destroy threads */
    for (i = 0; i < saveCtx->numVirtualChannels; i++) {
        if (saveCtx->saveThread[i]) {
            status = NvThreadDestroy(saveCtx->saveThread[i]);
            if (status != NVMEDIA_STATUS_OK)
                LOG_ERR("%s: Failed to destroy save thread %d\n",
                        __func__, i);
        }
    }

    for (i = 0; i < saveCtx->numVirtualChannels; i++) {
        /*For RAW Images, destroy the conversion queue */
        if (saveCtx->threadCtx[i].conversionQueue) {
            while (IsSucceed(NvQueueGet(saveCtx->threadCtx[i].conversionQueue, &image, 0))) {
                if (image) {
                    NvMediaImageDestroy(image);
                    image = NULL;
                }
            }
            LOG_DBG("%s: Destroying conversion queue \n",__func__);
            NvQueueDestroy(saveCtx->threadCtx[i].conversionQueue);
        }

        /*Flush and destroy the input queues*/
        if (saveCtx->threadCtx[i].inputQueue) {
            LOG_DBG("%s: Flushing the save input queue %d\n", __func__, i);
            while (IsSucceed(NvQueueGet(saveCtx->threadCtx[i].inputQueue, &image, 0))) {
                if (image) {
                    imageGroup = (NvMediaImageGroup *)(image->tag);
                    if (NvQueuePut((NvQueue *)imageGroup->tag,
                                   (void *)&imageGroup,
                                   0) != NVMEDIA_STATUS_OK) {
                        LOG_ERR("%s: Failed to put image back in queue\n", __func__);
                        break;
                    }
                }
                imageGroup=NULL;
            }
            NvQueueDestroy(saveCtx->threadCtx[i].inputQueue);
        }
    }

    if (saveCtx->device)
        NvMediaDeviceDestroy(saveCtx->device);

    if (saveCtx)
        free(saveCtx);

    LOG_INFO("%s: SaveFini done\n", __func__);
    return NVMEDIA_STATUS_OK;
}


NvMediaStatus
SaveProc(NvMainContext *mainCtx)
{
    NvSaveContext        *saveCtx = NULL;
    NvCompositeContext   *compositeCtx = NULL;
    uint32_t i;
    NvMediaStatus status= NVMEDIA_STATUS_OK;

    if (!mainCtx) {
        LOG_ERR("%s: Bad parameter\n", __func__);
        return NVMEDIA_STATUS_ERROR;
    }
    saveCtx = mainCtx->ctxs[SAVE_ELEMENT];
    compositeCtx = mainCtx->ctxs[COMPOSITE_ELEMENT];

    /* Setting the queues */
    if (saveCtx->displayEnabled) {
        for (i = 0; i < saveCtx->numVirtualChannels; i++) {
            saveCtx->threadCtx[i].outputQueue = compositeCtx->inputQueue[i];
        }
    }

    /* Create thread to save images */
    for (i = 0; i < saveCtx->numVirtualChannels; i++) {
        saveCtx->threadCtx[i].exitedFlag = NVMEDIA_FALSE;
        status = NvThreadCreate(&saveCtx->saveThread[i],
                                &_SaveThreadFunc,
                                (void *)&saveCtx->threadCtx[i],
                                NV_THREAD_PRIORITY_NORMAL);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to create save Thread\n",
                    __func__);
            saveCtx->threadCtx[i].exitedFlag = NVMEDIA_TRUE;
        }
    }
    return status;
}
