/*
 * Copyright (c) 2011 Intel Corporation. All Rights Reserved.
 * Copyright (c) Imagination Technologies Limited, UK
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Edward Lin <edward.lin@intel.com>
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include "psb_drv_video.h"
#include "psb_drv_debug.h"
#include "tng_hostdefs.h"
#include "tng_hostcode.h"
#include "tng_hostair.h"

/***********************************************************************************
 * Function Name     : functions of pi8AIR_Table table
 ************************************************************************************/
VAStatus tng_air_buf_create(context_ENC_p ctx)
{
    IMG_UINT32 ui32MbNum = (ctx->ui16PictureHeight * ctx->ui16Width) >> 8;
    ctx->sAirInfo.pi8AIR_Table = (IMG_INT8 *)malloc(ui32MbNum);
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: ui32MbNum = %d\n", __FUNCTION__, ui32MbNum);
    if (!ctx->sAirInfo.pi8AIR_Table) {
        drv_debug_msg(VIDEO_DEBUG_ERROR,
            "\nERROR: Error allocating Adaptive Intra Refresh table of Application context (APP_SetVideoParams)");
        return VA_STATUS_ERROR_ALLOCATION_FAILED;
    }
    memset(ctx->sAirInfo.pi8AIR_Table, 0, ui32MbNum);
    return VA_STATUS_SUCCESS;
}

static void tng_air_buf_clear(context_ENC_p ctx)
{
#if 0
    IMG_UINT32 ui32MbNum = (ctx->ui16PictureHeight * ctx->ui16Width) >> 8;
    drv_debug_msg(VIDEO_DEBUG_ERROR,"%s: ui32MbNum = %d, ctx->sAirInfo.pi8AIR_Table = 0x%08x\n", __FUNCTION__, ui32MbNum, ctx->sAirInfo.pi8AIR_Table);
    memset(ctx->sAirInfo.pi8AIR_Table, 0, ui32MbNum);    
    drv_debug_msg(VIDEO_DEBUG_ERROR,"%s: ui32MbNum = %d, ctx->sAirInfo.pi8AIR_Table = 0x%08x\n", __FUNCTION__, ui32MbNum, ctx->sAirInfo.pi8AIR_Table);
#endif
    tng_cmdbuf_insert_command(ctx->obj_context, ctx->ui32StreamID,
        MTX_CMDID_SW_AIR_BUF_CLEAR, 0, 0, 0);
    return ;
}

void tng_air_buf_free(context_ENC_p ctx)
{
    if (ctx->sAirInfo.pi8AIR_Table != NULL)
        free(ctx->sAirInfo.pi8AIR_Table);
    return ;
}

/***********************************************************************************
 * Function Name     : functions for input control
 ************************************************************************************/
static IMG_UINT16 tng__rand(context_ENC_p ctx) 
{
    IMG_UINT16 ui16ret = 0;
    ctx->ui32pseudo_rand_seed =  (IMG_UINT32) ((ctx->ui32pseudo_rand_seed * 1103515245 + 12345) & 0xffffffff); //Using mask, just in case
    ui16ret = (IMG_UINT16)(ctx->ui32pseudo_rand_seed / 65536) % 32768; 
    return ui16ret;
}

//APP_FillSliceMap
IMG_UINT32 tng_fill_slice_map(context_ENC_p ctx, IMG_INT32 i32SlotNum, IMG_UINT32 ui32StreamIndex)
{
    context_ENC_mem *ps_mem = &(ctx->ctx_mem[ui32StreamIndex]);
    unsigned char *pvBuffer;
    IMG_UINT8 ui8SlicesPerPicture;
    IMG_UINT8 ui8HalfWaySlice;
    IMG_UINT32 ui32HalfwayBU;

    ui8SlicesPerPicture = ctx->ui8SlicesPerPicture;
    ui32HalfwayBU = 0;
    ui8HalfWaySlice=ui8SlicesPerPicture/2;
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "%s: slot num = %d, aso = %d\n", __FUNCTION__, i32SlotNum, ctx->bArbitrarySO);
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "%s: stream id = %d, addr = 0x%x\n", __FUNCTION__, ui32StreamIndex, ps_mem->bufs_slice_map.virtual_addr);

    psb_buffer_map(&(ps_mem->bufs_slice_map), &(ps_mem->bufs_slice_map.virtual_addr));
    if (ps_mem->bufs_slice_map.virtual_addr == NULL) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "%s error: mapping slice map\n", __FUNCTION__);
        goto out1;
    }

    pvBuffer = (unsigned char*)(ps_mem->bufs_slice_map.virtual_addr + (i32SlotNum * ctx->ctx_mem_size.slice_map));
    if (pvBuffer == NULL) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "%s: pvBuffer == NULL\n", __FUNCTION__);
        goto out1;
    }

    if (ctx->bArbitrarySO) {
        IMG_UINT8 ui8Index;
        IMG_UINT8 ui32FirstBUInSlice;
        IMG_UINT8 ui8SizeInKicks;
        IMG_UINT8 ui8TotalBUs;
        IMG_UINT8 aui8SliceNumbers[MAX_SLICESPERPIC];

	memset(aui8SliceNumbers, 0, MAX_SLICESPERPIC);

        ui8SlicesPerPicture = tng__rand(ctx) % ctx->ui8SlicesPerPicture + 1;
        // Fill slice map
        // Fill number of slices
        * pvBuffer = ui8SlicesPerPicture;
        pvBuffer++;

        for (ui8Index = 0; ui8Index < ui8SlicesPerPicture; ui8Index++)
            aui8SliceNumbers[ui8Index] = ui8Index;

	// randomise slice numbers
        for (ui8Index = 0; ui8Index < 20; ui8Index++) {
            IMG_UINT8 ui8FirstCandidate;
            IMG_UINT8 ui8SecondCandidate;
            IMG_UINT8 ui8Temp;

            ui8FirstCandidate = tng__rand(ctx) % ui8SlicesPerPicture;
            ui8SecondCandidate = tng__rand(ctx) % ui8SlicesPerPicture;

            ui8Temp = aui8SliceNumbers[ui8FirstCandidate];
            aui8SliceNumbers[ui8FirstCandidate] = aui8SliceNumbers[ui8SecondCandidate];
            aui8SliceNumbers[ui8SecondCandidate] = ui8Temp;
        }

        ui8TotalBUs = (ctx->ui16PictureHeight / 16) * (ctx->ui16Width / 16) / ctx->sRCParams.ui32BUSize;

        ui32FirstBUInSlice = 0;

        for (ui8Index = 0; ui8Index < ui8SlicesPerPicture - 1; ui8Index++) {
            IMG_UINT32 ui32BUsCalc;
            if (ui8Index==ui8HalfWaySlice) ui32HalfwayBU=ui32FirstBUInSlice;

            ui32BUsCalc=(ui8TotalBUs - 1 * (ui8SlicesPerPicture - ui8Index));
            if(ui32BUsCalc)
                ui8SizeInKicks = tng__rand(ctx) %ui32BUsCalc  + 1;
            else
                ui8SizeInKicks = 1;
            ui8TotalBUs -= ui8SizeInKicks;

            // slice number
            * pvBuffer = aui8SliceNumbers[ui8Index];
            pvBuffer++;

            // SizeInKicks BU
            * pvBuffer = ui8SizeInKicks;
            pvBuffer++;
            ui32FirstBUInSlice += (IMG_UINT32) ui8SizeInKicks;
        }
        ui8SizeInKicks = ui8TotalBUs;
        // slice number
        * pvBuffer = aui8SliceNumbers[ui8SlicesPerPicture - 1];
        pvBuffer++;

        // last BU
        * pvBuffer = ui8SizeInKicks;
        pvBuffer++;
    } else {
        // Fill standard Slice Map (non arbitrary)
        IMG_UINT8 ui8Index;
        IMG_UINT8 ui8SliceNumber;
        IMG_UINT8 ui32FirstBUInSlice;
        IMG_UINT8 ui8SizeInKicks;
        IMG_UINT32 ui32SliceHeight;

        // Fill number of slices
        * pvBuffer = ui8SlicesPerPicture;
        pvBuffer++;


        ui32SliceHeight = (ctx->ui16PictureHeight / ctx->ui8SlicesPerPicture) & ~15;

        ui32FirstBUInSlice = 0;
        ui8SliceNumber = 0;
        for (ui8Index = 0; ui8Index < ui8SlicesPerPicture - 1; ui8Index++) {
            if (ui8Index==ui8HalfWaySlice) ui32HalfwayBU=ui32FirstBUInSlice;
            ui8SizeInKicks = ((ui32SliceHeight / 16)*(ctx->ui16Width/16))/ctx->sRCParams.ui32BUSize;

            // slice number
            * pvBuffer = ui8SliceNumber;
            pvBuffer++;
            // SizeInKicks BU
            * pvBuffer = ui8SizeInKicks;
            pvBuffer++;

            ui8SliceNumber++;
            ui32FirstBUInSlice += (IMG_UINT32) ui8SizeInKicks;
        }
        ui32SliceHeight = ctx->ui16PictureHeight - ui32SliceHeight * (ctx->ui8SlicesPerPicture - 1);
        if (ui8Index==ui8HalfWaySlice) ui32HalfwayBU=ui32FirstBUInSlice;
        ui8SizeInKicks = ((ui32SliceHeight / 16)*(ctx->ui16Width/16))/ctx->sRCParams.ui32BUSize;

        // slice number
        * pvBuffer = ui8SliceNumber;    pvBuffer++;
        // last BU
        * pvBuffer = ui8SizeInKicks;    pvBuffer++;
    }

out1:
    psb_buffer_unmap(&(ps_mem->bufs_slice_map));
    ctx->ui32HalfWayBU[i32SlotNum] = ui32HalfwayBU;
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: ui32HalfWayBU = %d\n", __FUNCTION__, ctx->ui32HalfWayBU[i32SlotNum]);
    return ui32HalfwayBU;
}

//IMG_V_GetInpCtrlBuf
static VAStatus tng__map_inp_ctrl_buf( 
    context_ENC_p ctx,
    IMG_UINT8   ui8SlotNumber,
    IMG_UINT8 **ppsInpCtrlBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);
    context_ENC_mem_size *ps_mem_size = &(ctx->ctx_mem_size);
    if (ppsInpCtrlBuf == NULL) {
        drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: ppsInpCtrlBuf == NULL\n", __FUNCTION__);
        return VA_STATUS_ERROR_INVALID_PARAMETER;
    }

    *ppsInpCtrlBuf = NULL; // Not enabled

    // if enabled, return the input-control buffer corresponding to this slot
    if (ctx->bEnableInpCtrl) {
        vaStatus = psb_buffer_map(&(ps_mem->bufs_mb_ctrl_in_params), ppsInpCtrlBuf);
        if (vaStatus == VA_STATUS_SUCCESS)
            *ppsInpCtrlBuf += ui8SlotNumber * ps_mem_size->mb_ctrl_in_params;
        else
            psb_buffer_unmap(&(ps_mem->bufs_mb_ctrl_in_params));
    }

    return vaStatus;
}

static VAStatus tng__unmap_inp_ctrl_buf( 
    context_ENC_p ctx,
    IMG_UINT8   __maybe_unused ui8SlotNumber,
    IMG_UINT8 **ppsInpCtrlBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);

    // if enabled, return the input-control buffer corresponding to this slot
    if (*ppsInpCtrlBuf != NULL) {
        psb_buffer_unmap(&(ps_mem->bufs_mb_ctrl_in_params));
        *ppsInpCtrlBuf = NULL; // Not enabled
    }
    return vaStatus;
}

//APP_FillInpCtrlBuf
#define DEFAULT_INTER_INTRA_SCALE_TBL_IDX   (0)
#define DEFAULT_CODED_SKIPPED_SCALE_TBL_IDX (0)
#define DEFAULT_INPUT_QP                    (0xF)
#define SIZEOF_MB_IN_CTRL_PARAM     (2)

static void tng__fill_inp_ctrl_buf(
    context_ENC_p ctx,
    IMG_UINT8 *pInpCtrlBuf,
    IMG_INT16 i16IntraRefresh,
    IMG_INT8* pi8QP,
    IMG_UINT32 __maybe_unused ui32HalfWayBU)
{
    IMG_PVOID   pvBuffer;
    IMG_UINT32  ui32MBFrameWidth;
    IMG_UINT32  ui32MBPictureHeight;
    IMG_UINT32  ui32MBSliceHeight;
    IMG_UINT16  ui16DefaultParam;
    IMG_UINT16  ui16IntraParam;
    IMG_BOOL	bRefresh=IMG_FALSE;
    IMG_UINT32 ui32CurrentIndex;
    IMG_UINT32 ui32MBx, ui32MBy;
    IMG_UINT16 *pui16MBParam;
    IMG_INT8	i8QPInit;
    IMG_INT8	i8QP;
    IMG_INT8	iMaxQP;

#ifdef BRN_30324
    IMG_UINT32 ui32HalfWayMB=ui32HalfWayBU * ctx->sRCParams.ui32BUSize;
#endif

    if (pi8QP == NULL) {
        drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: start QP == NULL\n", __FUNCTION__);
        return ;
    }

    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: start QP = %d\n", __FUNCTION__, *pi8QP);

    if (i16IntraRefresh > 0) {
        bRefresh=IMG_TRUE;
    }

    iMaxQP = 31;
    if (ctx->eStandard == IMG_STANDARD_H264) {
        iMaxQP = 51;
    }
    if(pi8QP) {
        i8QPInit = * pi8QP;
    } else {
        i8QPInit = DEFAULT_INPUT_QP;
    }
    // get the buffer
    // IMG_C_GetBuffer(psActiveContext->hContext, pInpCtrlBuf, &pvBuffer,IMG_TRUE);
    pvBuffer = (IMG_PVOID) pInpCtrlBuf;

    //fill data
    ui32MBFrameWidth  = (ctx->ui16Width/16);
    ui32MBPictureHeight = (ctx->ui16PictureHeight/16);
    ui32MBSliceHeight = (ui32MBPictureHeight/ctx->ui8SlicesPerPicture);

    pui16MBParam = (IMG_UINT16 *)pvBuffer;
    ui32CurrentIndex=0;

    for(ui32MBy = 0; ui32MBy < (IMG_UINT32)(ctx->ui16PictureHeight / 16); ui32MBy++) {
        for(ui32MBx = 0; ui32MBx < ui32MBFrameWidth; ui32MBx++) {
            IMG_UINT16 ui16MBParam = 0;

#ifdef BRN_30324
            if (ui32HalfWayMB && ui32CurrentIndex == ui32HalfWayMB)
                if (ctx->ui8SlicesPerPicture > 1 && ctx->i32NumPipes > 1) {
                    ui32CurrentIndex=(((ui32CurrentIndex)+31)&(~31));
                }
#endif
            i8QP = i8QPInit + ((tng__rand(ctx)%6)-3);
            i8QP = tng__max(tng__min(i8QP, iMaxQP), ctx->sRCParams.iMinQP);

            ui16DefaultParam = ( i8QP<<10) | (3 <<7) |(3<<4);
            ui16IntraParam =  ( i8QP<<10)	| (0 <<7) |(0<<4);

            ui16MBParam = ui16DefaultParam;
            if (bRefresh) {
                if ((IMG_INT32)ui32CurrentIndex > ctx->i32LastCIRIndex) {
                    ctx->i32LastCIRIndex = ui32CurrentIndex;
                    ui16MBParam = ui16IntraParam;
                    i16IntraRefresh--;
                    if(i16IntraRefresh <= 0)
                        bRefresh = IMG_FALSE;
                }
            }
            pui16MBParam[ui32CurrentIndex++] = ui16MBParam;
        }
    }

    if (bRefresh) {
        ctx->i32LastCIRIndex = -1;
        while (i16IntraRefresh) {
            i8QP = i8QPInit + ((tng__rand(ctx)%6)-3);
            i8QP = tng__max(tng__min(i8QP, iMaxQP), ctx->sRCParams.iMinQP);
            ui16IntraParam = ( i8QP<<10) |(0 <<7) |(0<<4);
            pui16MBParam[++ctx->i32LastCIRIndex] = ui16IntraParam;
            i16IntraRefresh--;
        }
    }

    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: end QP = %d\n", __FUNCTION__, *pi8QP);
    //release buffer
    //IMG_C_ReleaseBuffer(psActiveContext->hContext, pInpCtrlBuf,IMG_TRUE);
    return ;
}

/***********************************************************************************
 * Function Name     : APP_FillInputControl
 * Inputs                   : psContext
 * Description           : Fills input control buffer for a given source picture
 ************************************************************************************/
static void tng__fill_input_control(
    context_ENC_p ctx,
    IMG_UINT8 ui8SlotNum,
    IMG_UINT32 __maybe_unused ui32HalfWayBU)
{
    IMG_UINT8 * pInpCtrlBuf = NULL;
    IMG_INT8 i8InitialQp = ctx->sRCParams.ui32InitialQp;
    // Get pointer to MB Control buffer for current source buffer (if input control is enabled, otherwise buffer is NULL)
    // Please refer to kernel tng_setup_cir_buf()
    /*
    tng__map_inp_ctrl_buf(ctx, ui8SlotNum, &pInpCtrlBuf);
    if (pInpCtrlBuf!= IMG_NULL) {
        tng__fill_inp_ctrl_buf(ctx, pInpCtrlBuf,(IMG_INT16)(ctx->ui16IntraRefresh), &i8InitialQp, ui32HalfWayBU);
    }
    tng__unmap_inp_ctrl_buf(ctx, ui8SlotNum, &pInpCtrlBuf);
    */
    tng_cmdbuf_insert_command(ctx->obj_context, ctx->ui32StreamID,
        MTX_CMDID_SW_FILL_INPUT_CTRL, ui8SlotNum, 0, 0);

    return ;
}

static void tng__send_air_inp_ctrl_buf(context_ENC_p ctx, IMG_INT8 *pInpCtrlBuf)
{
    //IMG_PVOID pvICBuffer;
    IMG_UINT16 ui16IntraParam;
    //IMG_BOOL bRefresh = IMG_FALSE;
    IMG_UINT32 ui32CurrentCnt, ui32SentCnt;
    IMG_UINT32 ui32MBMaxSize;
    IMG_UINT16 *pui16MBParam;
    IMG_UINT32 ui32NewScanPos, ui32Skip;

#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
    IMG_CHAR TmpOutputTble[396]; //Debug only
#endif
    ui16IntraParam = (0 << 7) | (0 << 4);

    if (ctx->ui32FrameCount[0] < 1)
        return;
    
    // get the buffer
    pui16MBParam = (IMG_UINT16 *) pInpCtrlBuf;

    //fill data 
    ui32MBMaxSize = (IMG_UINT32)(ctx->ui16PictureHeight / 16) * (IMG_UINT32)(ctx->ui16Width / 16);
    ui32CurrentCnt = 0;
    if (ctx->sAirInfo.i16AIRSkipCnt >= 0)
        ui32Skip = ctx->sAirInfo.i16AIRSkipCnt;
    else
        //ui32Skip=APP_Rand() % psActiveContext->sAirInfo.i32NumAIRSPerFrame; // Pseudorandom skip.
        ui32Skip = (ctx->ui32FrameCount[0] & 0x7) + 1; // Pseudorandom skip.
    
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
        {
            IMG_UINT32 tsp;
            if (fp)
            {
                fp = fopen("SADvals.txt", "a");
            }
            else
            {
                fp = fopen("SADvals.txt", "w");
            }
    
            fprintf(fp, "\n---------------------------------------------------------------------------\n");
            fprintf(fp, "SENDING SADvals  (skip:%i)\n", ui32Skip);
    
            for (tsp = 0; tsp < ui32MBMaxSize; tsp++)
            {
                if (ctx->sAirInfo.pi8AIR_Table[tsp] > 0)
                {
                    TmpOutputTble[tsp] = 'x';
                }
                else
                {
                    TmpOutputTble[tsp] = 'o';
                }
            }
        }
#endif
    
    ui32NewScanPos = (IMG_UINT32) (ctx->sAirInfo.ui16AIRScanPos + ui32Skip) % ui32MBMaxSize;
    ui32CurrentCnt = ui32SentCnt = 0;

    while (ui32CurrentCnt < ui32MBMaxSize &&
        ((ctx->sAirInfo.i32NumAIRSPerFrame == 0) ||
        ui32SentCnt < (IMG_UINT32) ctx->sAirInfo.i32NumAIRSPerFrame)) {
        IMG_UINT16 ui16MBParam;

        if (ctx->sAirInfo.pi8AIR_Table[ui32NewScanPos] >= 0) {
            // Mark the entry as 'touched'
            ctx->sAirInfo.pi8AIR_Table[ui32NewScanPos] = -1 - ctx->sAirInfo.pi8AIR_Table[ui32NewScanPos];
    
            if (ctx->sAirInfo.pi8AIR_Table[ui32NewScanPos] < -1) {
                ui16MBParam = pui16MBParam[ui32NewScanPos] & (0xFF << 10);
                ui16MBParam |= ui16IntraParam;
                pui16MBParam[ui32NewScanPos] = ui16MBParam;
                ctx->sAirInfo.pi8AIR_Table[ui32NewScanPos]++;
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
                TmpOutputTble[ui32NewScanPos]='I';
#endif
                ui32NewScanPos += ui32Skip;
                ui32SentCnt++;
            }
            ui32CurrentCnt++;
        }
    
        ui32NewScanPos++;
        ui32NewScanPos = ui32NewScanPos % ui32MBMaxSize;
        if (ui32NewScanPos == ctx->sAirInfo.ui16AIRScanPos) {
            /* we have looped around */ 
            break;
        }
    }
    
    ctx->sAirInfo.ui16AIRScanPos = ui32NewScanPos;
    
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
        {
            IMG_UINT32 tsp;
            for (tsp = 0; tsp < ui32MBMaxSize; tsp++)
            {
                if (tsp % ((IMG_UINT32)(ctx->ui16Width/16)) == 0)
                {
                    fprintf(fp, "\n%c", TmpOutputTble[tsp]);
                }
                else
                {
                    fprintf(fp, "%c", TmpOutputTble[tsp]);
                }
            }
    
            fprintf(fp, "\n---------------------------------------------------------------------------\n");
            fclose(fp);
        }
#endif

    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: end\n", __FUNCTION__);
    return ;
}

// Adaptive Intra Refresh (AIR) - send the AIR values to the next bufferk
// APP_UpdateAdaptiveIntraRefresh_Send
static void tng__update_air_send(context_ENC_p ctx, IMG_UINT8 ui8SlotNum)
{
    IMG_UINT8 *pInpCtrlBuf = NULL;
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: start\n", __FUNCTION__);
    // Get pointer to MB Control buffer for current source buffer (if input control is enabled, otherwise buffer is NULL)
#if 0
    tng__map_inp_ctrl_buf(ctx, ui8SlotNum, &pInpCtrlBuf);
    if(pInpCtrlBuf!= IMG_NULL) {
        tng__send_air_inp_ctrl_buf(ctx, (IMG_INT8 *)pInpCtrlBuf);
    }
    tng__unmap_inp_ctrl_buf(ctx, ui8SlotNum, &pInpCtrlBuf);
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: end\n", __FUNCTION__);
#endif
    tng_cmdbuf_insert_command(ctx->obj_context, ctx->ui32StreamID,
        MTX_CMDID_SW_UPDATE_AIR_SEND, ui8SlotNum, 0, 0);
    return ;
}

/***********************************************************************************
 * Function Name     : functions for output control
 ************************************************************************************/
//IMG_V_GetFirstPassOutBuf
VAStatus tng__map_first_pass_out_buf( 
    context_ENC_p ctx,
    IMG_UINT8   __maybe_unused ui8SlotNumber,
    IMG_UINT8 **ppsFirstPassOutBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);

    if (ppsFirstPassOutBuf == NULL) {
        drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: ppsFirstPassOutBuf == NULL\n", __FUNCTION__);
        return VA_STATUS_ERROR_INVALID_PARAMETER;
    }

    *ppsFirstPassOutBuf = NULL; // Not enabled

    // if enabled, return the input-control buffer corresponding to this slot
    if (ctx->bEnableInpCtrl) {
         vaStatus = psb_buffer_map(&(ps_mem->bufs_first_pass_out_params), ppsFirstPassOutBuf);
        if (vaStatus != VA_STATUS_SUCCESS)
            psb_buffer_unmap(&(ps_mem->bufs_first_pass_out_params));
    }

    return vaStatus;
}

VAStatus tng__unmap_first_pass_out_buf( 
    context_ENC_p ctx,
    IMG_UINT8 __maybe_unused ui8SlotNumber,
    IMG_UINT8 **ppsFirstPassOutBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);

    // if enabled, return the input-control buffer corresponding to this slot
    if (*ppsFirstPassOutBuf != NULL) {
        psb_buffer_unmap(&(ps_mem->bufs_first_pass_out_params));
        *ppsFirstPassOutBuf = NULL; // Not enabled
    }

    return vaStatus;
}

//IMG_V_GetBestMBDecisionOutBuf
VAStatus tng__map_best_mb_decision_out_buf( 
    context_ENC_p ctx,
    IMG_UINT8   __maybe_unused ui8SlotNumber,
    IMG_UINT8  **ppsBestMBDecisionOutBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);

    // if enabled, return the input-control buffer corresponding to this slot
    if (ctx->bEnableInpCtrl)
        vaStatus = psb_buffer_map(&(ps_mem->bufs_first_pass_out_best_multipass_param), ppsBestMBDecisionOutBuf);
    else
        *ppsBestMBDecisionOutBuf = NULL; // Not enabled

    return vaStatus;
}

VAStatus tng__unmap_best_mb_decision_out_buf( 
    context_ENC_p ctx,
    IMG_UINT8   __maybe_unused ui8SlotNumber,
    IMG_UINT8  **ppsBestMBDecisionOutBuf)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS; 
    context_ENC_mem* ps_mem = &(ctx->ctx_mem[0]);

    // if enabled, return the input-control buffer corresponding to this slot
    if (*ppsBestMBDecisionOutBuf != NULL) {
        psb_buffer_unmap(&(ps_mem->bufs_first_pass_out_best_multipass_param));
        *ppsBestMBDecisionOutBuf = NULL; // Not enabled
     }

    return vaStatus;
}

// Calculate Adaptive Intra Refresh (AIR)
static void tng__calc_air_inp_ctrl_buf(context_ENC_p ctx, IMG_UINT8 *pFirstPassOutBuf, IMG_UINT8 *pBestMBDecisionCtrlBuf)
{
    IMG_UINT8 *pSADPointer;
    IMG_UINT8 *pvSADBuffer;
    IMG_UINT8 ui8IsAlreadyIntra;
    IMG_UINT32 ui32MBFrameWidth;
    IMG_UINT32 ui32MBPictureHeight;
    IMG_UINT16 ui16IntraParam;
    IMG_UINT32 ui32MBx, ui32MBy;
    IMG_UINT32 ui32SADParam;
    IMG_UINT32 ui32tSAD_Threshold, ui32tSAD_ThresholdLo, ui32tSAD_ThresholdHi;
    IMG_UINT32 ui32MaxMBs, ui32NumMBsOverThreshold, ui32NumMBsOverLo, ui32NumMBsOverHi;
    IMG_BEST_MULTIPASS_MB_PARAMS *psBestMB_Params;
    IMG_FIRST_STAGE_MB_PARAMS *psFirstMB_Params;

    ui16IntraParam = (0 << 7) | (0 << 4);
    ui32NumMBsOverThreshold = ui32NumMBsOverLo = ui32NumMBsOverHi = 0;
    //drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: start\n", __FUNCTION__);

//    if (psActiveContext->ui32EncodeSent < (IMG_UINT32)psActiveContext->ui8MaxSourceSlots + 1)
//    if (ctx->ui32FrameCount[0] < (IMG_UINT32)(ctx->ui8SlotsInUse + 1))
//        return;
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: start\n", __FUNCTION__);
    //fill data 
    ui32MBFrameWidth  = (ctx->ui16Width/16);
    ui32MBPictureHeight = (ctx->ui16PictureHeight/16);
	
    // get the SAD results buffer (either IPE0 and IPE1 results or, preferably, the more accurate Best Multipass SAD results)
    if (pBestMBDecisionCtrlBuf) {
        pvSADBuffer = pBestMBDecisionCtrlBuf;
        drv_debug_msg(VIDEO_DEBUG_GENERAL,"AIR active: Using Best Multipass SAD values ");

//#ifdef  MULTIPASS_MV_PLACEMENT_ISSUE_FIXED
	if ((ctx->ui8EnableSelStatsFlags & ESF_MP_BEST_MOTION_VECTOR_STATS))
//#endif
	{
            // The actual Param structures (which contain SADs) are located after the Multipass Motion Vector entries
	    pvSADBuffer += (ui32MBPictureHeight * (ui32MBFrameWidth) * sizeof(IMG_BEST_MULTIPASS_MB_PARAMS_IPMV));
        }
    } else {
        pvSADBuffer = pFirstPassOutBuf;
        drv_debug_msg(VIDEO_DEBUG_GENERAL,"AIR active: Using IPE SAD values ");
    }

    if (ctx->sAirInfo.i32NumAIRSPerFrame == 0)
        ui32MaxMBs = ui32MBFrameWidth * ui32MBPictureHeight; // Default to ALL MB's in frame
    else if (ctx->sAirInfo.i32NumAIRSPerFrame < 0)
        ctx->sAirInfo.i32NumAIRSPerFrame = ui32MaxMBs = ((ui32MBFrameWidth * ui32MBPictureHeight) + 99) / 100; // Default to 1% of MB's in frame (min 1)
    else
        ui32MaxMBs = ctx->sAirInfo.i32NumAIRSPerFrame;

    pSADPointer = (IMG_UINT8 *)pvSADBuffer;
	
    if (ctx->sAirInfo.i32SAD_Threshold >= 0)
        ui32tSAD_Threshold = (IMG_UINT16)ctx->sAirInfo.i32SAD_Threshold;
    else {
        // Running auto adjust threshold adjust mode
        if (ctx->sAirInfo.i32SAD_Threshold == -1) {
            // This will occur only the first time
            if (pBestMBDecisionCtrlBuf) {
                psBestMB_Params=(IMG_BEST_MULTIPASS_MB_PARAMS *) pSADPointer; // Auto seed the threshold with the first value
                ui32SADParam = psBestMB_Params->ui32SAD_Inter_MBInfo & IMG_BEST_MULTIPASS_SAD_MASK;
            } else {
                psFirstMB_Params=(IMG_FIRST_STAGE_MB_PARAMS *) pSADPointer; // Auto seed the threshold with the first value
                ui32SADParam = (IMG_UINT32) psFirstMB_Params->ui16Ipe0Sad;
            }
            ctx->sAirInfo.i32SAD_Threshold = -1 - ui32SADParam; // Negative numbers indicate auto-adjusting threshold
        }
        ui32tSAD_Threshold = (IMG_UINT32) - (ctx->sAirInfo.i32SAD_Threshold + 1);
    }

    ui32tSAD_ThresholdLo = ui32tSAD_Threshold / 2;
    ui32tSAD_ThresholdHi = ui32tSAD_Threshold + ui32tSAD_ThresholdLo;

    drv_debug_msg(VIDEO_DEBUG_GENERAL,"Th:%u, MaxMbs:%u, Skp:%i\n", (unsigned int)ui32tSAD_Threshold, (unsigned int)ui32MaxMBs, ctx->sAirInfo.i16AIRSkipCnt);

#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
	if (fp)
		fp=fopen("SADvals.txt","a");
	else
		fp=fopen("SADvals.txt","w");

	if (ctx->sAirInfo.i32SAD_Threshold>=0)
		if (ctx->sAirInfo.i32NumAIRSPerFrame>0)
			fprintf(fp, "S_SADThreshold: %i	MaxMBs: %i\n", ui32tSAD_Threshold, ui32MaxMBs);
		else
			fprintf(fp, "S_SADThreshold: %i	MaxMBs: NA\n", ui32tSAD_Threshold, ui32MaxMBs);
	else
		fprintf(fp, "V_SADThreshold: %i	MaxMBs: %i\n", ui32tSAD_Threshold, ui32MaxMBs);

	if (pBestMBDecisionCtrlBuf)
		fprintf(fp, "Using Best Multipass SAD values\n");
	else
		fprintf(fp, "Using Motion Search Data IPE SAD values\n");
#endif

    // This loop could be optimised to a single counter if necessary, retaining for clarity
    for (ui32MBy = 0; ui32MBy < ui32MBPictureHeight; ui32MBy++) {
        for( ui32MBx=0; ui32MBx<ui32MBFrameWidth; ui32MBx++) {
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT	
            IMG_CHAR cMarked;
            cMarked='_';
#endif
            // Turn all negative table values to positive (reset 'touched' state of a block that may have been set in APP_SendAIRInpCtrlBuf())
	    if (ctx->sAirInfo.pi8AIR_Table[ui32MBy *  ui32MBFrameWidth + ui32MBx] < 0)
                ctx->sAirInfo.pi8AIR_Table[ui32MBy *  ui32MBFrameWidth + ui32MBx] = -1 - ctx->sAirInfo.pi8AIR_Table[ui32MBy *  ui32MBFrameWidth + ui32MBx];

            // This will read the SAD value from the buffer (either IPE0 SAD or the superior Best multipass parameter structure SAD value)
            if (pBestMBDecisionCtrlBuf) {
                psBestMB_Params = (IMG_BEST_MULTIPASS_MB_PARAMS *) pSADPointer;
                ui32SADParam = psBestMB_Params->ui32SAD_Inter_MBInfo & IMG_BEST_MULTIPASS_SAD_MASK;

                if ((psBestMB_Params->ui32SAD_Intra_MBInfo & IMG_BEST_MULTIPASS_MB_TYPE_MASK) >> IMG_BEST_MULTIPASS_MB_TYPE_SHIFT == 1)
                    ui8IsAlreadyIntra = 1;
                else
                    ui8IsAlreadyIntra = 0;

                pSADPointer=(IMG_UINT8 *) &(psBestMB_Params[1]);
            } else {
                psFirstMB_Params=(IMG_FIRST_STAGE_MB_PARAMS *) pSADPointer;
                ui32SADParam = (IMG_UINT32) psFirstMB_Params->ui16Ipe0Sad;
                ui32SADParam += (IMG_UINT32) psFirstMB_Params->ui16Ipe1Sad;
                ui32SADParam /= 2;
                ui8IsAlreadyIntra = 0; // We don't have the information to determine this
                pSADPointer=(IMG_UINT8 *) &(psFirstMB_Params[1]);
            }

            if (ui32SADParam >= ui32tSAD_ThresholdLo) {
                ui32NumMBsOverLo++;

                if (ui32SADParam >= ui32tSAD_Threshold) {
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
                    cMarked='i';
#endif	

                    // if (!ui8IsAlreadyIntra) // Don't mark this block if it's just been encoded as an Intra block anyway
                    // (results seem better without this condition anyway)
                    {
                        ctx->sAirInfo.pi8AIR_Table[ui32MBy *  ui32MBFrameWidth + ui32MBx]++;
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT	
                        cMarked='I';
#endif	
                    }
                    ui32NumMBsOverThreshold++;
                    if (ui32SADParam >= ui32tSAD_ThresholdHi)
                        ui32NumMBsOverHi++;
                }
            }

#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT	
            fprintf(fp,"%4x[%i]%c,	",ui32SADParam, ctx->sAirInfo.pi8AIR_Table[ui32MBy * ui32MBFrameWidth + ui32MBx], cMarked);
#endif
        }
        pSADPointer=(IMG_UINT8 *) ALIGN_64(((IMG_UINT32) pSADPointer));
#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
	fprintf(fp,"\n");
#endif
    }

    // Test and process running adaptive threshold case
    if (ctx->sAirInfo.i32SAD_Threshold < 0) {
        // Adjust our threshold (to indicate it's auto-adjustable store it as a negative value minus 1)
        if (ui32NumMBsOverLo <= ui32MaxMBs)
            ctx->sAirInfo.i32SAD_Threshold = (IMG_INT32) - ((IMG_INT32)ui32tSAD_ThresholdLo) - 1;
        else
            if (ui32NumMBsOverHi >= ui32MaxMBs)
                ctx->sAirInfo.i32SAD_Threshold = (IMG_INT32) - ((IMG_INT32)ui32tSAD_ThresholdHi) - 1;
            else {
                if (ui32MaxMBs < ui32NumMBsOverThreshold) {
                    ctx->sAirInfo.i32SAD_Threshold = ((IMG_INT32)ui32tSAD_ThresholdHi - (IMG_INT32)ui32tSAD_Threshold);
                    ctx->sAirInfo.i32SAD_Threshold *= ((IMG_INT32)ui32MaxMBs - (IMG_INT32)ui32NumMBsOverThreshold);
                    ctx->sAirInfo.i32SAD_Threshold /= ((IMG_INT32)ui32NumMBsOverHi - (IMG_INT32)ui32NumMBsOverThreshold);
                    ctx->sAirInfo.i32SAD_Threshold += ui32tSAD_Threshold;
                } else {
                    ctx->sAirInfo.i32SAD_Threshold = ((IMG_INT32)ui32tSAD_Threshold - (IMG_INT32)ui32tSAD_ThresholdLo);
                    ctx->sAirInfo.i32SAD_Threshold *= ((IMG_INT32)ui32MaxMBs - (IMG_INT32)ui32NumMBsOverLo);
                    ctx->sAirInfo.i32SAD_Threshold /= ((IMG_INT32)ui32NumMBsOverThreshold - (IMG_INT32)ui32NumMBsOverLo);
                    ctx->sAirInfo.i32SAD_Threshold += ui32tSAD_ThresholdLo;
                }
                ctx->sAirInfo.i32SAD_Threshold = -ctx->sAirInfo.i32SAD_Threshold - 1;
            }

#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
            fprintf(fp,"THRESHOLDS ADJUSTMENT\nThrLo:%i	ThrMid:%i	ThrHi:%i\nMBsLo:%i	MBsMid:%i	MBsHi:%i\n",ui32tSAD_ThresholdLo, ui32tSAD_Threshold, ui32tSAD_ThresholdHi, ui32NumMBsOverLo, ui32NumMBsOverThreshold, ui32NumMBsOverHi);
            fprintf(fp,"Target No. MB's:%i\nThreshold adjusted to: %i\n",ui32MaxMBs, -(ctx->sAirInfo.i32SAD_Threshold));
#endif
    }

#ifdef ADAPTIVE_INTRA_REFRESH_DEBUG_OUTPUT
    fprintf(fp,"\n MBs tagged:%i\n", ui32NumMBsOverThreshold);
    fclose(fp);
#endif
    drv_debug_msg(VIDEO_DEBUG_GENERAL,"%s: end\n", __FUNCTION__);
    return;
}

// Adaptive Intra Refresh (AIR) - Calculate the new AIR values based upon Motion Search feedback
// APP_UpdateAdaptiveIntraRefresh_Calc
static void tng_update_air_calc(context_ENC_p ctx, IMG_UINT8 ui8SlotNum)
{
    IMG_UINT8  *pFirstPassOutBuf = NULL;
    IMG_UINT8  *pBestMBDecisionCtrlBuf = NULL;
#if 0
    // Get pointer to MB Control buffer for current source buffer (if input control is enabled, otherwise buffer is NULL)
    tng__map_first_pass_out_buf(ctx, ui8SlotNum, &pFirstPassOutBuf);
    tng__map_best_mb_decision_out_buf(ctx, ui8SlotNum, &pBestMBDecisionCtrlBuf);

    if(pFirstPassOutBuf || pBestMBDecisionCtrlBuf)
	tng__calc_air_inp_ctrl_buf (ctx, pFirstPassOutBuf, pBestMBDecisionCtrlBuf);

    tng__unmap_first_pass_out_buf(ctx, ui8SlotNum, &pFirstPassOutBuf);
    tng__unmap_best_mb_decision_out_buf(ctx, ui8SlotNum, &pBestMBDecisionCtrlBuf);
#endif
    tng_cmdbuf_insert_command(ctx->obj_context, ctx->ui32StreamID,
        MTX_CMDID_SW_UPDATE_AIR_CALC, ui8SlotNum, 0, 0);
}

/***********************************************************************************
 * Function Name     : 
 * Inputs                   : 
 * Description           : 
 ************************************************************************************/
void tng_air_set_input_control(context_ENC_p ctx, IMG_UINT8 __maybe_unused ui8StreamID)
{
    IMG_UINT8 ui8SlotIndex = ctx->ui8SlotsCoded;
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "%s: slot index = %d\n", __FUNCTION__, ctx->ui8SlotsCoded);
    //IMG_UINT32 ui32HalfWayBU;
    //ui32HalfWayBU = tng_fill_slice_map(ctx, ui8SlotIndex, ui8StreamID);

    ////////////////////////////// INPUT CONTROL
    // Add input control stuff here
    tng__fill_input_control(ctx, ui8SlotIndex, ctx->ui32HalfWayBU[ui8SlotIndex]);

    // Adaptive Intra Refresh (AIR) - send the AIR values to the next buffer
    if (ctx->bEnableAIR)
        tng__update_air_send(ctx, ui8SlotIndex);
}


/***********************************************************************************
 * Function Name     : 
 * Inputs                   : 
 * Description           : 
 ************************************************************************************/
void tng_air_set_output_control(context_ENC_p ctx, IMG_UINT8 __maybe_unused ui8StreamID)
{
    IMG_UINT8 ui8SlotIndex = ctx->ui8SlotsCoded;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "%s: slot index = %d\n", __FUNCTION__, ctx->ui8SlotsCoded);

    if ((ctx->eFrameType == IMG_INTRA_IDR) ||
        (ctx->eFrameType == IMG_INTRA_FRAME))
        tng_air_buf_clear(ctx);
    else
        tng_update_air_calc(ctx, ui8SlotIndex);

    return;
}