/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2012, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/**
 *  \page jpeg_compression JPEG Compression Example
 *
 *  \section Purpose
 *
 *  The example is to compress the captured image data for transferring. An open
 *  source code from IJG could be used for this purpose. It should be optimized
 *  for maximum time requirement.
 *
 *  \section Requirements
 *
 *  This package can only be used with sam4s-wpird-rd.
 *
 *  \section Description
 *
 *  This example integrats another example "imagesensor_capture" with jpeg
 *  compression which could be used for efficiently transfering or store the
 *  captured images .
 *
 *  IJG is an informal group that writes and distributes a widely used free
 *  library for JPEG image compression. IJG's library could be easily ported
 *  to a wide range of platform thanks to the independency between the core
 *  algorithm and platform independent code (especially for memory and input/
 *  output).
 *
 *  The example caputres data from image sensor, compresses it using IJG's
 *  jpeg library and decompresses the compressed file inversely with different
 *  interface provided by the same library then displays it to LCD.
 *
 *  The statistics for the performance will be also output to the LCD after
 *  displaying an image. It includes compression time, decompression time,lcd
 *  writing time, SRAM writing time and comperession ratio.
 *
 *  \section Usage
 *
 *  -# Build the program and download it inside the evaluation board. Please
 *     refer to the
 *     <a href="http://www.atmel.com/dyn/resources/prod_documents/doc6224.pdf">
 *     SAM-BA User Guide</a>,
 *     application note or to the
 *     <a href="ftp://ftp.iar.se/WWWfiles/arm/Guides/EWARM_UserGuide.ENU.pdf">
 *     IAR EWARM User Guide</a>,
 *     depending on your chosen solution.
 *  -# After downloading through SAM-BA or IAR flash loader, run the application.
 *  -# Press BP1 to start capturing and displaying. The statistical data will
 *     be shown 1s after image displayed.
 */

/** \file
 *
 *  This file contains all the specific code for the jpeg_compression.
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include "board.h"
#include "usb_debug_output.h"

#include "libjpeg.h"


#include "CDCDSerialDriver.h"


#include "image_capture.h"

#include <stdio.h>
#include <stdint.h>
#include <string.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

#define IMAGE_SIZE_RGB_QVGA    (240*320*3)
#define IMAGE_SIZE_RGB_VGA     (480*640*3)
#define IMAGE_SIZE_YUV422_QVGA (240*320*2)

#define IMAGE_SIZE_DST  IMAGE_SIZE_YUV422_QVGA
#define IMAGE_SIZE_SRC   IMAGE_SIZE_RGB_VGA

#define STRING_RESULT "[%u] C:%u D:%u (ms)\n%u bytes %.2f%%"

//#define JPEG_CODING_ONLY
#define JPEG_DECODING

typedef struct _SJPEGTest
{
    const uint8_t* pucData ;
    uint32_t dwDataLength ;
    uint32_t dwTimeC ;
    uint32_t dwTimeD ;
    uint32_t dwFinalLength ;
} SJPEGTest ;


/*----------------------------------------------------------------------------
 *        Local variables
 *----------------------------------------------------------------------------*/

static uint32_t _dwTime ;
static uint32_t _dwTimeSRAM ;
static uint8_t* _pucIndex ;
static uint8_t _szResult[64] ;

static uint8_t* _pucImageSrc;
static uint8_t* _pucImageDst;

static uint8_t*_pucCapturedBuf;
static uint8_t _aucLine[2048] ;

/* stored in sram*/
const uint8_t _aucImage_Captured[1];

static SJPEGTest _pImage;

static bool isTriggered = false;

static const Pin pinBP1 = PIN_PUSHBUTTON_1;

/** all the pins init state*/
const Pin pinInitA = {0x200802, PIOA, ID_PIOA, PIO_INPUT, PIO_PULLUP};
const Pin pinInitB = {0xd8, PIOB, ID_PIOB, PIO_INPUT, PIO_PULLUP};
const Pin pinInitC = {0x13200, PIOC, ID_PIOC, PIO_INPUT, PIO_PULLUP};
const Pin pinInitAOut = {0xffbff7fb, PIOA, ID_PIOA, PIO_OUTPUT_0, PIO_DEFAULT};
const Pin pinInitBOut = {0xffffff27, PIOB, ID_PIOB, PIO_OUTPUT_0, PIO_DEFAULT};
const Pin pinInitCOut = {0xfffecdff, PIOC, ID_PIOC, PIO_OUTPUT_0, PIO_DEFAULT};

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
  * \brief copying decompressed data to display buffer
  */
static uint32_t _ijg_cbk( uint8_t* pucRows, uint32_t dwLength )
{
    memcpy( _pucIndex, pucRows, dwLength ) ;
    _pucIndex+=dwLength ;

    return 0 ;
}


#if defined JPEG_DECODING

/**
  * \brief yuv422 to yuv444 conversion
  *
  * expand yuv422 to yuv444 for jpeg compression, it's for complying with IJG's
  * default pre-processed routines
  */
static void _yuv422_yuv444(uint8_t*in,uint8_t *out, uint32_t width,uint32_t height)
{
    int i,j;
    int m,n;
    for(i=0;i<height;i++)
    {
        for(j=0;j<width/2;j++)
        {
            m = i * width * 3 + j*6;
            n = i * width * 2 + j*4;
            out[m+ 0] = in[n + 0];
            out[m + 1] = in[n + 1];
            out[m + 2] = in[n + 3];
            out[m + 3] = in[n + 2];
            out[m + 4] = in[n + 1];
            out[m + 5] = in[n + 3];
        }
    }
}

/**
  * Procedure for compression and decompression
  */
static void _jpeg_benchmark_pirrd_raw( SJPEGTest* pImage )
{
    uint8_t* pucCursor ;
    uint32_t dw ;
    SJpegData sJpegData ;

    pImage->dwTimeC=GetTickCount() ;

    /* Compress image*/
    JpegData_Init( &sJpegData ) ;

    /* use captured buffer as direct source*/
    JpegData_SetSource( &sJpegData, _pucCapturedBuf, 320*240*3 ) ;
    JpegData_SetDestination( &sJpegData, _pucImageDst, 320*240*3 ) ;
    JpegData_SetDimensions( &sJpegData, 320, 240, 3 ) ;
    JpegData_SetParameters( &sJpegData, 75, JPG_DATA_YCbCr, JPG_METHOD_IFAST ) ;

    if ( ijg_compress_raw_no_padding( &sJpegData ) == 0 )
    {
        pImage->dwTimeC=1+GetTickCount()-pImage->dwTimeC ;
        pImage->dwFinalLength=sJpegData.dwDstLength ;

        LCD_SetCursor( 0, 0 ) ;
        LCD_WriteRAM_Prepare() ;

        LCDD_Fill(COLOR_TURQUOISE);

        pImage->dwTimeD=GetTickCount() ;
        /*set decompression*/
        JpegData_Init( &sJpegData ) ;
        JpegData_SetSource( &sJpegData, _pucImageDst, 320*240*3 ) ;
        JpegData_SetDestination( &sJpegData, _aucLine, sizeof( _aucLine ) ) ;
        JpegData_SetDimensions( &sJpegData, 320, 240, 3 ) ;
        JpegData_SetParameters( &sJpegData, 75, JPG_DATA_RGB, JPG_METHOD_IFAST ) ;
        JpegData_SetCallback( &sJpegData, _ijg_cbk ) ;

        if ( ijg_decompress( &sJpegData ) == 0 )
        {
            pImage->dwTimeD=1+GetTickCount()-pImage->dwTimeD ;

            pucCursor=_pucImageSrc ;
            _dwTimeSRAM=GetTickCount() ;

            LCD_SetDisplayLandscape(0);
            LCD_SetWindow( 0, 0,240 ,320  ) ;

            LCD_SetCursor( 0,0 ) ;
            LCD_WriteRAM_Prepare() ;
            for ( dw=0 ; dw < IMAGE_SIZE_RGB_QVGA ; dw++ )
            {
                LCD_WriteRAMByte( *(pucCursor++) ) ;
            }
            _dwTimeSRAM=1+GetTickCount()-_dwTimeSRAM ;
        }
    }
}

/**
  * Procedure for compression and decompression
  */
static void _jpeg_benchmark_pirrd_yuv( SJPEGTest* pImage )
{
    uint8_t* pucCursor ;
    uint32_t dw ;
    SJpegData sJpegData ;

    pImage->dwTimeC=GetTickCount() ;

    _yuv422_yuv444(_pucCapturedBuf,_pucImageSrc, 320,240);

    /* Compress image*/
    JpegData_Init( &sJpegData ) ;
    JpegData_SetSource( &sJpegData, _pucImageSrc, 320*240*3 ) ;
    JpegData_SetDestination( &sJpegData, _pucImageDst, 320*240*3 ) ;
    JpegData_SetDimensions( &sJpegData, 320, 240, 3 ) ;
    JpegData_SetParameters( &sJpegData, 75, JPG_DATA_YCbCr, JPG_METHOD_IFAST ) ;

    if ( ijg_compress( &sJpegData ) == 0 )
    {
        pImage->dwTimeC=1+GetTickCount()-pImage->dwTimeC ;
        pImage->dwFinalLength=sJpegData.dwDstLength ;

        LCD_SetCursor( 0, 0 ) ;
        LCD_WriteRAM_Prepare() ;

        LCDD_Fill(COLOR_TURQUOISE);

        pImage->dwTimeD=GetTickCount() ;
        /*set decompression*/
        JpegData_Init( &sJpegData ) ;
        JpegData_SetSource( &sJpegData, _pucImageDst, 320*240*3 ) ;
        JpegData_SetDestination( &sJpegData, _aucLine, sizeof( _aucLine ) ) ;
        JpegData_SetDimensions( &sJpegData, 320, 240, 3 ) ;
        JpegData_SetParameters( &sJpegData, 75, JPG_DATA_RGB, JPG_METHOD_IFAST ) ;
        JpegData_SetCallback( &sJpegData, _ijg_cbk ) ;

        if ( ijg_decompress( &sJpegData ) == 0 )
        {
            pImage->dwTimeD=1+GetTickCount()-pImage->dwTimeD ;

            pucCursor=_pucImageSrc ;
            _dwTimeSRAM=GetTickCount() ;

            LCD_SetDisplayLandscape(0);
            LCD_SetWindow( 0, 0,240 ,320  ) ;

            LCD_SetCursor( 0,0 ) ;
            LCD_WriteRAM_Prepare() ;
            for ( dw=0 ; dw < IMAGE_SIZE_RGB_QVGA ; dw++ )
            {
                LCD_WriteRAMByte( *(pucCursor++) ) ;
            }
            _dwTimeSRAM=1+GetTickCount()-_dwTimeSRAM ;
        }
    }
}
#endif /* defined JPEG_DECODING*/

#if defined JPEG_CODING_ONLY

/**
  * \brief Procedure for compression
  */
static void _jpeg_benchmark_pirrd( SJPEGTest* pImage )
{
    SJpegData sJpegData ;

    pImage->dwTimeC=GetTickCount() ;

     _yuv422_yuv444(_pucCapturedBuf,_pucImageSrc, 320,240);
    /* Compress image*/
    JpegData_Init( &sJpegData ) ;
    JpegData_SetSource( &sJpegData, _pucImageSrc, 320*240*3 ) ;
    JpegData_SetDestination( &sJpegData, _pucImageDst, 320*240*3 ) ;
    JpegData_SetDimensions( &sJpegData, 320, 240, 3 ) ;
    JpegData_SetParameters( &sJpegData, 75, JPG_DATA_YCbCr, JPG_METHOD_IFAST ) ;

    if ( ijg_compress( &sJpegData ) == 0 )
    {
        pImage->dwTimeC=1+GetTickCount()-pImage->dwTimeC ;
    }
}

#endif /* defined JPEG_CODING_ONLY*/

/**
 * \brief Set default master access for speed up.
 * Here assume code is put at flash, data is put at sram.
 */
static void _SetDefaultMaster( void )
{
    Matrix *pMatrix = MATRIX;

    /* Set default master: SRAM (slave 0)-> Cortex-M3 System (Master 1)*/
    pMatrix->MATRIX_SCFG[0] |= ((1 << 18) & MATRIX_SCFG_FIXED_DEFMSTR_Msk) | /* Master 1 */
                               ((2 << 16) & MATRIX_SCFG_DEFMSTR_TYPE_Msk);   /* Fixed Default Master */

    /* Set default master: Internal flash (slave 2) -> Cortex-M3 Instruction/Data (Master 0)*/
    pMatrix->MATRIX_SCFG[2] |= ((0 << 18) & MATRIX_SCFG_FIXED_DEFMSTR_Msk) | /* Master 0 */
                               ((2 << 16) & MATRIX_SCFG_DEFMSTR_TYPE_Msk);   /* Fixed Default Master */
}

/**
 *  \brief Handler for Button 1 rising edge interrupt.
 *
 *  Handle state transition.
 */
static void _Button1_Handler( const Pin* pin )
{
   isTriggered = true;
}

/** initialize pushbutton for state transition*/
static void _Init_Pushbutton_Trigger(void)
{
    /* Configure pios as inputs. */
    PIO_Configure( &pinBP1, 1 ) ;

    /* Adjust pio debounce filter patameters, uses 10 Hz filter. */
    PIO_SetDebounceFilter( &pinBP1, 10 ) ;

    /* Initialize pios interrupt handlers, see PIO definition in board.h. */
    PIO_ConfigureIt( &pinBP1, _Button1_Handler ) ; /* Interrupt on rising edge  */

    /* Enable PIO controller IRQs. */
    NVIC_EnableIRQ( (IRQn_Type)pinBP1.id ) ;

    /* Enable PIO line interrupts. */
    PIO_EnableIt( &pinBP1 ) ;
}
/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/** Interrupt Handler for systick*/
void SysTick_Handler( void )
{
    TimeTick_Increment() ;
}

/** The main entry of the example*/
extern int main( void )
{
    float fRatio ;

    /* Disable watchdog*/
    WDT_Disable( WDT ) ;

    /* Interrupts intialization*/
    PIO_InitializeInterrupts( 0 ) ;

    /*Initialization*/
   _pucImageSrc = (uint8_t*) EBI_CS0_ADDR ;
   _pucImageDst =  (uint8_t*) (EBI_CS0_ADDR + IMAGE_SIZE_SRC + 1) ;
   _pucCapturedBuf =(uint8_t*) EBI_CS0_ADDR;

    /* Set Matrix Master*/
    _SetDefaultMaster() ;

    /* Configure systick for 1 ms.*/
    TimeTick_Configure( BOARD_MCK );
    Wait(1000); // wait for 1s to be sure LCD is ready for initialization

    /* Initialize LCD*/
    LCDD_Initialize() ;

    /* Turn on LCD*/
    LCD_On() ;

    /* Set default view of portrait*/
    LCD_SetDisplayPortrait( 0 ) ;

    LCDD_Fill(COLOR_TURQUOISE);

    LCDD_DrawString( 0, 10, "JPEG_compression \nexample ", COLOR_BLACK ) ;
    LCDD_DrawString( 0, 60, "Press BP1 button to\nstart capture and\ncompress/decompress "\
      , COLOR_BLACK ) ;

    /* Initialize image sensor*/
    Capture_Init();

    /* intialize pushbutton trigger*/
    _Init_Pushbutton_Trigger();

    /* while loop for capturing*/
    while(1)
    {
        while(!isTriggered);
        if(isTriggered)
        {
            /* Capture*/
            DoCapture(_pucCapturedBuf);

            /* Set this pointer for displaying*/
            _pucIndex=_pucImageSrc ;

            /* compression and decompression*/
            _jpeg_benchmark_pirrd_raw( &_pImage ) ;
            isTriggered = false;
        }
        Wait(1000);
        /* Display results*/
        _dwTime=GetTickCount() ;
        LCDD_Fill( COLOR_TURQUOISE ) ;
        _dwTime=1+GetTickCount()-_dwTime ;

        /* LCD fill timing*/
        LCD_SetDisplayPortrait( 0 ) ;
        LCD_SetWindow( 0, 0, 240, 320 ) ;
        LCD_SetCursor( 0, 0 ) ;
        LCDD_Fill(COLOR_TURQUOISE);
        snprintf( (char*)_szResult, sizeof( _szResult ), "Fill raw: %u ms", _dwTime ) ;
        LCDD_DrawString( 0, 2, (const uint8_t*)_szResult, COLOR_BLACK ) ;

        /* LCD fill from SRAM timing*/
        snprintf( (char*)_szResult, sizeof( _szResult ), "Fill SRAM: %u ms", _dwTimeSRAM ) ;
        LCDD_DrawString( 0, 22, (const uint8_t*)_szResult, COLOR_BLACK ) ;

        /* compression timings        */
        fRatio=(float)_pImage.dwFinalLength*100.0/(float)IMAGE_SIZE_YUV422_QVGA ;
        snprintf( (char*)_szResult, sizeof( _szResult ), STRING_RESULT, 0U, _pImage.dwTimeC, _pImage.dwTimeD, _pImage.dwFinalLength, fRatio ) ;
        LCDD_DrawString( 0, 42, (const uint8_t*)_szResult, COLOR_BLACK ) ;

    }
}
