/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
 *      \file           phStbVideoDevice.c
 *
 *      \brief          Video Device image decoder component.
 */
/*
 Rev Date       Author      Comments
--------------------------------------------------------------------------------
  1 20070927    Mike Neill  Original
  2 20071023    Mike Neill  Changes after QAC checking
  3 20080208    batelaan    Update file copyright/license text
  4 20081219    Mike Neill  Add support for multiple graphics modes
  5 20090116    neill       Add support for YUV format output
--------------------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>

#include <sys/ioctl.h>

#include <phStbCommon.h>
#include "phStbVideoRenderer.h"

#include <unistd.h>

#include <sys/mman.h>

#include "phStbVideoDevice.h"
#include "phStbImageUtils.h"

/***********************************************************
 * Types and defines:
 **********************************************************/

/***********************************************************
 * Global data:
 **********************************************************/

/***********************************************************
 * Internal Functions:
 **********************************************************/

/******************* 
* LOCAL MACROS     *
********************/

#define PH_STB_VIDEO_DEVICE_CHECK(a) \
{ \
    phStbImageErrorCode_t err; \
    err = (a); \
    if (err != PH_STB_OK) \
    { \
        free(pContext); \
        return err; \
    } \
}

/** Maximum size of physical memory we support. */
#define MAX_SIZE 0x20000000UL

/** Device through which we access memory via mmap(). */
#define MEMDEVICE  "/dev/mem"

/** Indices into memory address array */
#define Y_ODD   (0)
#define Y_EVEN  (1)
#define UV_ODD  (2)
#define UV_EVEN (3)
#define STRIDE  (4)

/******************* 
* LOCAL TYPEDEFS   *
********************/

typedef struct
{
    /* Variables used for memory access */
    uint8_t * pMem;
    int32_t memFile;
} phStbVideoDevice_contextProperties_t;

/******************* 
* STATIC DATA      *
********************/

/********************** 
* FUNCTION PROTOTYPES *
***********************/

/*******************************************************************************
*@begin
* NAME: phStbVideoDevice_GetDimensions
*       Gets dimensions of a bitmap supplied in video device format
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* filename      IN      Image filename
* pWidth        Out     Width of Image
* pHeight       Out     Height of Image
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
phStbImageErrorCode_t phStbVideoDevice_GetDimensions(const char* filename, int32_t * pWidth, int32_t * pHeight )
{
    uint32_t dims[4];
    int32_t file;

    file = open(filename, O_RDWR);
    if (file >= 0) 
    {
        if (ioctl(file, PHSTBVIDEORENDERER_IOCTL_GET_SRC_RECT, dims)) 
        {
            return PH_STB_ERR_FILE_WRONG_FORMAT;
        }
        (void)close(file);
    }
    else
    {
        return PH_STB_ERR_FILE_WRONG_FORMAT;
    }

    if (pWidth)
    {
        *pWidth = (int32_t)(dims[2] - dims[0]);
    }

    if (pHeight)
    {
        *pHeight = (int32_t)(dims[3] - dims[1]);
    }

    return PH_STB_OK;
}

/*******************************************************************************
*@begin
* NAME: phStbVideoDevice_OpenMem
*       Opens the MEM device to allow access to system memory
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* pContext      IN      Pointer to context information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static phStbImageErrorCode_t phStbVideoDevice_OpenMem(phStbVideoDevice_contextProperties_t *pContext)
{
    /* Access the device */
    pContext->memFile = open(MEMDEVICE, O_RDWR);
    if (pContext->memFile < 0)
    {
        (void)printf("Error: Cannot open device " MEMDEVICE "\n" );
        return PH_STB_ERR_FILE_NOT_OPEN;
    }

    /* Memory map the memory space */
    pContext->pMem = (uint8_t *)mmap(0, MAX_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, pContext->memFile, 0);
    if (pContext->pMem == MAP_FAILED)
    {
        (void)printf("Error: Cannot mmap memory\n" );
        return PH_STB_ERR_MEM_MAP;
    }

    return PH_STB_OK;
}

/*******************************************************************************
*@begin
* NAME: phStbVideoDevice_CloseMem
*       Closes the MEM device after all accesses have been completed

*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* address       IN/OUT  Address to be checked / modified
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static phStbImageErrorCode_t phStbVideoDevice_CloseMem(phStbVideoDevice_contextProperties_t const *pContext)
{
    /* Clean up */
    if (munmap((void*)pContext->pMem, MAX_SIZE) != 0)
    {
        (void)printf("Error: Cannot munmap memory\n" );
        return PH_STB_ERR_MEM_MAP;
    }

    if (close( pContext->memFile ) != 0)
    {
        (void)printf("Error: Cannot close memory file\n" );
        return PH_STB_ERR_FILE_NOT_CLOSED;
    }

    return PH_STB_OK;
}

/*******************************************************************************
*@begin
* NAME: phStbVideoDevice_YUV2RGB
*       Integer maths YUV to RGB conversion

*       
* Returns : Calculated RGB value
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* Y             IN      Y input value
* U             IN      U input value
* V             IN      V input value
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
static uint32_t phStbVideoDevice_YUV2RGB(int32_t Y, int32_t U, int32_t V)
{
    int32_t red;
    int32_t green;
    int32_t blue;

    Y -= 16;
    U -= 128;
    V -= 128;
    red   = (298 * Y           + 409 * V + 128) / 256;
    green = (298 * Y - 100 * U - 208 * V + 128) / 256;
    blue  = (298 * Y + 516 * U           + 128) / 256;

    CLAMP_VALUE(red, 255)
    CLAMP_VALUE(green, 255)
    CLAMP_VALUE(blue, 255)

    return RGBA8888(red, green, blue, 0xFF);
}


static uint32_t phStbVideoDevice_pixelWidth(phStbImage_GfxMode_t mode)
{
    uint32_t retValue;
    switch(mode)
    {
        case(phStbImage_GfxMode_ARGB8888) : 
            retValue = sizeof(uint32_t);
            break;
        case(phStbImage_GfxMode_RGB565) :
        case(phStbImage_GfxMode_ARGB1555) :
        case(phStbImage_GfxMode_ARGB4444) :
            retValue = sizeof(uint16_t);
            break;
        default :
            retValue = sizeof(uint8_t);
            break;
    }

    return retValue;
}

/*******************************************************************************
*@begin
* NAME: phStbVideoDevice_Decode
*       Decodes a bitmap supplied in video device format.
*       
* Returns : PH_STB_OK if successful - else error
* 
* Parameter     Flow    Description
* ------------------------------------------------------------------------------
* filename      IN      Image filename
* pSetup        IN      Pointer to set up information - holding decode 
*                       information
* 
* Externals     Flow    Usage
* ------------------------------------------------------------------------------
* 
* Additional information:
*
*@end
*******************************************************************************/
phStbImageErrorCode_t phStbVideoDevice_Decode( const char* filename, phStbImage_SetupParams_t const * pSetup)
{
    uint32_t address[5];
    uint32_t stride;

    int32_t imageWidth;
    int32_t imageHeight;

    uint8_t *pInBuffer;
    uint8_t *pInBuffer2;
    int32_t  bufferWidth;
    int32_t  bufferHeight;
    int32_t  outOffsetX;
    int32_t  outOffsetY;
    int32_t  outWidth;
    int32_t  outHeight;

    uint32_t pixelWidth;
    bool     scale;

    int32_t file;

    phStbVideoDevice_contextProperties_t *pContext = NULL;

    /* Check that a valid instance set up structure has been provided */
    if (pSetup == NULL)
    {      
        return PH_STB_ERR_NULL_PARAMETER;
    }

    pixelWidth = phStbVideoDevice_pixelWidth(pSetup->gfxMode);

    /* Take local copies of important variables */
    pInBuffer        = pSetup->pInBuffer;
    pInBuffer2       = pSetup->pInBuffer2;
    bufferWidth      = pSetup->InBufferWidth;
    bufferHeight     = pSetup->InBufferHeight;
    scale            = pSetup->bScalingEnabled;
    outOffsetX       = pSetup->OutImageOffsetX;
    outOffsetY       = pSetup->OutImageOffsetY;
    outWidth         = pSetup->OutImageWidth;
    outHeight        = pSetup->OutImageHeight;

    /* Check to see if a valid decode buffer has been provided */
    if (pInBuffer == NULL)
    {
        return PH_STB_ERR_NULL_PARAMETER;
    }

    pContext = (phStbVideoDevice_contextProperties_t*)calloc(1, sizeof(phStbVideoDevice_contextProperties_t));
    if (pContext == NULL)
    {
        return PH_STB_ERR_OUT_OF_MEMORY;
    }

    /* Get the dimensions of the video */
    PH_STB_VIDEO_DEVICE_CHECK( phStbVideoDevice_GetDimensions(filename, &imageWidth, &imageHeight) )

    /* Get the video buffer memory addresses */
    file = open(filename, O_RDWR);
    if (file >= 0) 
    {
        if (ioctl(file, PHSTBVIDEORENDERER_IOCTL_GET_MEM_PTRS, address)) 
        {
            free(pContext);
            return PH_STB_ERR_FILE_WRONG_FORMAT;
        }
        (void)close(file);

        if ((address[Y_ODD]   == 0) ||
            (address[Y_EVEN]  == 0) ||
            (address[UV_ODD]  == 0) ||
            (address[UV_EVEN] == 0) )
        {
            free(pContext);
            return PH_STB_ERR_NULL_MEM_PTR;
        }
    }
    else
    {
        free(pContext);
        return PH_STB_ERR_FILE_NOT_OPEN;
    }

    PH_STB_VIDEO_DEVICE_CHECK( phStbVideoDevice_OpenMem(pContext) )

    stride = address[STRIDE];
    if (scale) 
    {
        int32_t row;
        int32_t destWidth;
        int32_t destHeight;
        float heightScale;
        float widthScale;
        uint8_t * destRowPtr;
        
        /* Calculate the scaling required to fit the image into the given buffer */
        heightScale = (float)outHeight / (float)imageHeight;
        widthScale = (float)outWidth / (float)imageWidth;
        destWidth = (int32_t)((float)imageWidth * widthScale);
        destHeight = (int32_t)((float)imageHeight * heightScale);

        /* Clip destWidth to bufferWidth */
        if ((outOffsetX+destWidth) > bufferWidth)
        {
            destWidth = bufferWidth-outOffsetX;
        }
        /* Clip destHeight to bufferHeight */
        if ((outOffsetY+destHeight) > bufferHeight)
        {
            destHeight = bufferHeight-outOffsetY;
        }

        /* Set up the pointer to the write location in the bitmap */
        destRowPtr = pInBuffer + (((bufferWidth*outOffsetY) + outOffsetX) * pixelWidth);

        for(row=0; row<destHeight; row++)
        {
            int32_t col;
            int32_t srcRow;
            uint8_t *destPtr;
            uint8_t *srcYPtr;
            uint8_t *srcUVPtr;

            /* Work out the source row that contributes to the destination row pixel */
            srcRow = (row * imageHeight) / destHeight;
            CLAMP_VALUE(srcRow, (imageHeight-1))
            destPtr = destRowPtr;
            if ((srcRow % 2)==0)
            {
                srcYPtr = &pContext->pMem[address[Y_EVEN] + (stride*(uint32_t)srcRow/2)];
                srcUVPtr = &pContext->pMem[address[UV_EVEN] + (stride*((uint32_t)srcRow/4))];
            }
            else
            {
                srcYPtr = &pContext->pMem[address[Y_ODD] + (stride*(uint32_t)srcRow/2)];
                srcUVPtr = &pContext->pMem[address[UV_ODD] + (stride*((uint32_t)srcRow/4))];
            }

            if (pSetup->gfxMode == phStbImage_GfxMode_YUV)
            {
                uint8_t *destUVPtr;
                destUVPtr = (uint8_t *)pInBuffer2 + (bufferWidth*(outOffsetY/2)) + outOffsetX;

                for(col=0; col<destWidth; col++)
                {
                    uint8_t U;
                    uint8_t V;
                    int32_t offset;

                    offset = (imageWidth*col)/destWidth;
                    if (offset % 2)
                    {
                        U = srcUVPtr[offset-1];
                        V = srcUVPtr[offset];
                    }
                    else
                    {
                        U = srcUVPtr[offset];
                        V = srcUVPtr[offset+1];
                    }
                    if (outOffsetY%2 == 0)
                    {
                        if (col%2 == 1) 
                        {
                            *destUVPtr = V;
                        }
                        else
                        {
                            *destUVPtr = U;
                        }
                        destUVPtr++;
                    }
                    *destPtr = srcYPtr[offset];
                    destPtr++;
                }
            }
            else
            {
                for(col=0; col<destWidth; col++)
                {
                    uint8_t U;
                    uint8_t V;
                    int32_t offset;
                    uint32_t pixel;

                    offset = (imageWidth*col)/destWidth;
                    if (offset % 2)
                    {
                        U = srcUVPtr[offset-1];
                        V = srcUVPtr[offset];
                    }
                    else
                    {
                        U = srcUVPtr[offset];
                        V = srcUVPtr[offset+1];
                    }
                    pixel = phStbVideoDevice_YUV2RGB((int32_t)srcYPtr[offset], (int32_t)U, (int32_t)V);

                    pixel = phStbImage_convertPixel(pixel, pSetup->gfxMode, pSetup->colourMapFunction);

                    switch(pSetup->gfxMode)
                    {
                        case(phStbImage_GfxMode_ARGB8888) : 
                            *((uint32_t*)destPtr) = pixel;
                            break;
                        case(phStbImage_GfxMode_RGB565) :
                        case(phStbImage_GfxMode_ARGB1555) :
                        case(phStbImage_GfxMode_ARGB4444) :
                            *((uint16_t*)destPtr) = (uint16_t)pixel;
                            break;
                        default :
                            *destPtr = (uint8_t)pixel;
                            break;
                    }

                    destPtr += pixelWidth;
                }
            }
            outOffsetY++;
            destRowPtr += (bufferWidth * pixelWidth);
        }
    }
    else
    {
        int32_t srcRow = 0;
        int32_t row;
        int32_t min_width;
        int32_t min_height;
        uint8_t * destRowPtr;

        /* Clip outWidth to buffer */
        if ((outOffsetX+outWidth) > bufferWidth)
        {
            outWidth = bufferWidth-outOffsetX;
        }
        /* Clip outHeight to buffer */
        if ((outOffsetY+outHeight) > bufferHeight)
        {
            outHeight = bufferHeight-outOffsetY;
        }
        /* Work out the smaller area that needs to be written to */
        min_width = (outWidth < bufferWidth) ? outWidth : bufferWidth;
        min_width = (min_width<imageWidth) ? min_width : imageWidth;
        min_height = (outHeight < bufferHeight) ? outHeight : bufferHeight;
        min_height = (min_height<imageHeight) ? min_height : imageHeight;
        DBG_PRINT((dbgBmp,DBG_DEFAULT,"Copying %d,%d to %d,%d\n", min_width, min_height, outOffsetX, outOffsetY));

        /* Set up the pointer to the write location in the bitmap */
        destRowPtr = pInBuffer + (((bufferWidth*outOffsetY) + outOffsetX) * pixelWidth);

        /* Data in RGBA format */
        row = min_height;
        /* Go through each row to be written */
        while(row)
        {
            int32_t col;
            uint8_t *destPtr;
            uint8_t *srcYPtr;
            uint8_t *srcUVPtr;

            CLAMP_VALUE(srcRow, (imageHeight-1))
            destPtr = destRowPtr;

            if ((srcRow % 2)==0)
            {
                srcYPtr = &pContext->pMem[address[Y_EVEN] + (stride*(uint32_t)srcRow/2)];
                srcUVPtr = &pContext->pMem[address[UV_EVEN] + (stride*((uint32_t)srcRow/4))];
            }
            else
            {
                srcYPtr = &pContext->pMem[address[Y_ODD] + (stride*(uint32_t)srcRow/2)];
                srcUVPtr = &pContext->pMem[address[UV_ODD] + (stride*((uint32_t)srcRow/4))];
            }
            if (pSetup->gfxMode == phStbImage_GfxMode_YUV)
            {
                uint8_t *destUVPtr;
                destUVPtr = (uint8_t *)pInBuffer2 + (bufferWidth*(outOffsetY/2)) + outOffsetX;

                for(col=0; col<min_width; col++)
                {
                    uint8_t U;
                    uint8_t V;

                    if (col % 2)
                    {
                        U = srcUVPtr[col-1];
                        V = srcUVPtr[col];
                    }
                    else
                    {
                        U = srcUVPtr[col];
                        V = srcUVPtr[col+1];
                    }
                    if (outOffsetY%2 == 0)
                    {
                        if (col%2 == 1) 
                        {
                            *destUVPtr = V;
                        }
                        else
                        {
                            *destUVPtr = U;
                        }
                        destUVPtr++;
                    }
                    *destPtr = srcYPtr[col];
                    destPtr++;
                }
            }
            else
            {
                for(col=0; col<min_width; col++)
                {
                    uint32_t pixel;
                    uint8_t U;
                    uint8_t V;

                    if (col % 2)
                    {
                        U = srcUVPtr[col-1];
                        V = srcUVPtr[col];
                    }
                    else
                    {
                        U = srcUVPtr[col];
                        V = srcUVPtr[col+1];
                    }
                    pixel = phStbVideoDevice_YUV2RGB((int32_t)srcYPtr[col], (int32_t)U, (int32_t)V);

                    pixel = phStbImage_convertPixel(pixel, pSetup->gfxMode, pSetup->colourMapFunction);

                    switch(pSetup->gfxMode)
                    {
                        case(phStbImage_GfxMode_ARGB8888) : 
                            *((uint32_t*)destPtr) = pixel;
                            break;
                        case(phStbImage_GfxMode_RGB565) :
                        case(phStbImage_GfxMode_ARGB1555) :
                        case(phStbImage_GfxMode_ARGB4444) :
                            *((uint16_t*)destPtr) = (uint16_t)pixel;
                            break;
                        default :
                            *destPtr = (uint8_t)pixel;
                            break;
                    }

                    destPtr += pixelWidth;
                }
            }
            destRowPtr += (bufferWidth * pixelWidth);
            srcRow++;
            outOffsetY++;
            row--;
        }
    }
    PH_STB_VIDEO_DEVICE_CHECK( phStbVideoDevice_CloseMem(pContext) )

    free(pContext);

    return PH_STB_OK;
}

