/*******************************************************************************
* Copyright 2018-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#include <math.h>
#include <memory>

#include "base.h"
#include "base_image.h"
#include "base_renderer.h"
#include "base_ipp.h"

#include "ipps.h"

static void printVersion()
{
    const IppLibraryVersion *pVer = ippsGetLibVersion();

    printf("\nIntel(R) IPP Threading Layers Example: Sobel Filter (cache efficient single-thread approach)");
    printf("\nThis is an improved implementation of Sobel Filter pipeline for a single thread with added data decomposition.");
    printf("\nIt splits an image on slices and then runs full Sobel Filter pipeline for every slice.");
    printf("\nBased on: Classic API");

    printf("\nIntel(R) IPP: %s %s %s", pVer->Name, pVer->Version, pVer->BuildDate);
    printf("\n");
}

static void printHelp(const cmd::OptDef pOptions[], char* argv[])
{
    printf("\nUsage: %s [-i] InputFile [[-o] OutputFile] [Options]\n", GetProgName(argv));
    printf("Options:\n");
    cmd::OptUsage(pOptions);
}

/* /////////////////////////////////////////////////////////////////////////////
//  Name:               splitImageToSlices
//
//  Purpose:            Splits image to equal slices depending on ROI size and Sobel mask size.
//                      Last slice can be slightly bigger than others.
//
//  Parameters:
//
//   [in]  roiSize            Size of destination ROI in pixels.
//   [in]  maskHeigh          Height of Sobel mask
//   [out] pTileSize          Size of single slice
//   [out] lastTileSize       Size of last slice
//   [out] slicesCount        Count of slices
//
//  Return Values:
//   void
*/
static void splitImageToSlices(IppiSize roiSize, int maskHeight, IppiSize* const pSliceSize, IppiSize* const pLastSliceSize, int* const slicesCount);

int main(int argc, char *argv[])
{
    /*
    // Variables initialization
    */
    Status       status          = STS_OK;
    DString      sInputFile      = CheckTestDirs( BMP_GRAYSCALE_FILE );
    DString      sOutputFile;
    DString      sIppCpu;

    bool          bPrintHelp     = false;

    Image srcData;
    Image dstData;

    // General timing
    vm_tick      tickStart   = 0;
    vm_tick      tickAcc     = 0;
    vm_tick      tickFreq    = vm_time_get_frequency()/1000;
    double       fTime       = 0;
    double       fTimeLimit  = 0;
    unsigned int iLoops      = 0;
    unsigned int iLoopsLimit = 0;

    /*
    // Cmd parsing
    */
    const cmd::OptDef cmdOpts[] =
    {
        { 'i', "", 1, cmd::KT_DSTRING,   cmd::KF_OPTIONAL,  &sInputFile,      "input file name" },
        { 'o', "", 1, cmd::KT_DSTRING,   cmd::KF_OPTIONAL,  &sOutputFile,     "output file name" },
        { 'w', "", 1, cmd::KT_DOUBLE,    0,                 &fTimeLimit,      "minimum test time in milliseconds" },
        { 'l', "", 1, cmd::KT_POSITIVE,  0,                 &iLoopsLimit,     "number of loops (overrides test time)" },
        { 'T', "", 1, cmd::KT_DSTRING,   0,                 &sIppCpu,         "target Intel IPP optimization (" IPP_OPT_LIST ")" },
        { 'h', "", 1, cmd::KT_BOOL,      0,                 &bPrintHelp,      "print help and exit" },
        {0}
    };

    if (cmd::OptParse(argc, argv, cmdOpts))
    {
        printHelp(cmdOpts, argv);
        PRINT_MESSAGE("Invalid input parameters");
        return 1;
    }

    InitPreferredCpu(sIppCpu.c_str());
	
    // Check default image availability
    if ( !strcmp(sInputFile.c_str(), BMP_GRAYSCALE_FILE) ) {
        bPrintHelp = ( -1 == vm_file_access(sInputFile.c_str(), 0) );
    }

    if(bPrintHelp)
    {
        printHelp(cmdOpts, argv);
        return 0;
    }

    if(!sInputFile.Size())
    {
        printHelp(cmdOpts, argv);
        PRINT_MESSAGE("Cannot open input file");
        return 1;
    }

    printVersion();

    while(1)
    {
        IppStatus       ippStatus;
        IppiSize        ippSrcSize;
        IppDataType     ippSrcType;
        IppDataType     ippDstType;
        IppiMaskSize    sobelMaskId     = ippMskSize3x3;
        int             sobelMaskHeight = 3;
        IppiBorderType  borderType      = (IppiBorderType)(ippBorderConst);
        Ipp8u           borderValue     = 0;
        IppNormType     normType        = ippNormL2;

        int cmnBufferSize = 0;
        int bufferSizeH   = 0;
        int bufferSizeV   = 0;

        IppiSize sliceSize;
        IppiSize lastSliceSize;
        int slicesCount = 0;
    
        AutoBuffer<Ipp8u>    ippFSobelTmpBuffer;
        AutoBuffer<Ipp16s>   ippFSobelIntermediateBuffer;
        
        // Read from file
        printf("\nInput file: %s\n", sInputFile.c_str());
        status = srcData.Read(sInputFile, CF_GRAY, ST_8U);
        CHECK_STATUS_PRINT_BR(status, "Image::Read()", GetBaseStatusString(status));
        printf("Input info: %dx%d %s\n", (int)srcData.m_size.width, (int)srcData.m_size.height, colorFormatName[srcData.m_color]);

        status = dstData.Alloc(srcData.m_size, srcData.m_color, ST_16S);
        CHECK_STATUS_PRINT_BR(status, "dstData.Alloc", GetBaseStatusString(status));

        printf("\nOutput file: %s\n", (sOutputFile.Size())?sOutputFile.c_str():"-");
        printf("Output info: %dx%d %s\n\n", (int)dstData.m_size.width, (int)dstData.m_size.height, colorFormatName[dstData.m_color]);

        ippSrcSize = ImageSizeToIppOld(srcData.m_size);
        ippSrcType = ImageFormatToIpp(srcData.m_sampleFormat);
        ippDstType = ImageFormatToIpp(dstData.m_sampleFormat);

        // Divide image on slices
        splitImageToSlices(ippSrcSize, sobelMaskHeight, &sliceSize, &lastSliceSize, &slicesCount);

        // Get size of slice buffer to store intermediate results of the calculations in the pipeline
        IppiSize maxSliceSize = { ippSrcSize.width, (sliceSize.height > lastSliceSize.height ? sliceSize.height : lastSliceSize.height) };

        // Allocate single slice buffer of intermediate results of the pipeline calculations
        ippFSobelIntermediateBuffer.Alloc(maxSliceSize.width * maxSliceSize.height);

        // Get single slice buffer sizes for Filter Sobel Horizontal and Filter Sobel Vertical
        ippStatus = ippiFilterSobelHorizBorderGetBufferSize(maxSliceSize, sobelMaskId, ippSrcType, ippDstType, 1 /* channels */, &bufferSizeH);
        CHECK_STATUS_PRINT_BR(ippStatus, "ippiFilterSobelHorizBorderGetBufferSize()", ippGetStatusString(ippStatus));

        ippStatus = ippiFilterSobelVertBorderGetBufferSize(maxSliceSize, sobelMaskId, ippSrcType, ippDstType, 1 /* channels */, &bufferSizeH);
        CHECK_STATUS_PRINT_BR(ippStatus, "ippiFilterSobelVertBorderGetBufferSize()", ippGetStatusString(ippStatus));

        // Allocate common buffer for temporary calculations
        ippFSobelTmpBuffer.Alloc(bufferSizeH > bufferSizeV ? bufferSizeH : bufferSizeV);

        printf("API:       Classic API\n");
        printf("Type:      Pipeline per Slice (cache efficient)\n");

        for(iLoops = 1, tickAcc = 0;; iLoops++)
        {
            tickStart = vm_time_get_tick();

            // Start of the pipeline
            
            IppiSize currentSliceRoi          = {sliceSize.width, sliceSize.height};
            IppiBorderType borderTypePerSlice = borderType;
            
            // Loop over slices
            for (int slice = 0; slice < slicesCount; slice++)
            {

                // Handle last slice
                if (slicesCount - 1 == slice) currentSliceRoi.height = lastSliceSize.height;

                // Handle borders
                if (slicesCount > 1)
                {
                    if (slice == 0) borderTypePerSlice = (IppiBorderType)((int)borderType | (int)ippBorderInMemBottom);
                    else if (slice == (slicesCount - 1)) borderTypePerSlice = (IppiBorderType)((int)borderType | (int)ippBorderInMemTop);
                    else  borderTypePerSlice = (IppiBorderType)((int)borderType | (int)ippBorderInMemBottom | (int)ippBorderInMemTop);
                }

                Ipp8u* pSrc  = (Ipp8u*)srcData.ptr() + slice * sliceSize.height * srcData.m_step;
                Ipp16s* pDst = (Ipp16s*)((Ipp8u*)dstData.ptr() + slice * sliceSize.height * dstData.m_step);

                // 1. Sobel filter Horizontal (Classic API function applied to one slice)
                ippStatus = ippiFilterSobelHorizBorder_8u16s_C1R((const Ipp8u*)pSrc, srcData.m_step, ippFSobelIntermediateBuffer, dstData.m_step,
                                                                currentSliceRoi, sobelMaskId, borderTypePerSlice, borderValue, ippFSobelTmpBuffer);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiFilterSobelHorizBorder_8u16s_C1R()", ippGetStatusString(ippStatus));

                // 2. Sobel filter Vertical (Classic API function applied to one slice)
                ippStatus = ippiFilterSobelVertBorder_8u16s_C1R((const Ipp8u*)pSrc, srcData.m_step, (Ipp16s*)pDst, dstData.m_step,
                                                                currentSliceRoi, sobelMaskId, borderTypePerSlice, borderValue, ippFSobelTmpBuffer);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiFilterSobelVertBorder_8u16s_C1R()", ippGetStatusString(ippStatus));

                // 3. Sqr of Sobel filter Horizontal values (Classic API function applied to one slice)
                ippStatus = ippiSqr_16s_C1IRSfs(ippFSobelIntermediateBuffer, dstData.m_step, currentSliceRoi, 0 /* scale */);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiSqr_16s_C1IRSfs()", ippGetStatusString(ippStatus));

                // 4. Sqr of Sobel filter Vertical values (Classic API function applied to one slice)
                ippStatus = ippiSqr_16s_C1IRSfs((Ipp16s*)pDst, dstData.m_step, currentSliceRoi, 0 /* scale */);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiSqr_16s_C1IRSfs()", ippGetStatusString(ippStatus));

                // 5. Addition of calculations from item 3. and 4. (Classic API function applied to one slice)
                ippStatus = ippiAdd_16s_C1IRSfs(ippFSobelIntermediateBuffer, dstData.m_step, (Ipp16s*)pDst, dstData.m_step, currentSliceRoi, 0 /* scale */);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiAdd_16s_C1IRSfs()", ippGetStatusString(ippStatus));

                // 6. Square root of calculations from item 5. (Classic API function applied to one slice)
                ippStatus = ippiSqrt_16s_C1IRSfs((Ipp16s*)pDst, dstData.m_step, currentSliceRoi, 0 /* scale */);
                CHECK_STATUS_PRINT_BR(ippStatus, "ippiSqrt_16s_C1IRSfs()", ippGetStatusString(ippStatus));
            }

            // End of the pipeline

            tickAcc += (vm_time_get_tick() - tickStart);

            fTime = (double)tickAcc/tickFreq;
            if(iLoopsLimit)
            {
                if(iLoops >= iLoopsLimit)
                    break;
            }
            else
            {
                if(fTime >= fTimeLimit)
                    break;
            }
        }

        if (status < 0) break;

        /*
        // Results output
        */
        printf("\nLoops:      %u\n", iLoops);
        printf("Time total: %0.3fms\n", fTime);
        printf("Loop avg:   %0.3fms\n", fTime/iLoops);

        if (sOutputFile.Size())
        {
            Image dstData_8u;
            status = dstData_8u.Alloc(dstData.m_size, dstData.m_color, ST_8U);
            
            Ipp16s* s = (Ipp16s*)dstData.ptr();
            Ipp8u*  d = (Ipp8u*) dstData_8u.ptr();
            for(int i = 0; i < dstData.m_size.width * dstData.m_size.height; i++)
            {
                d[i] = (s[i] < 0 ? 0 : (s[i] > 255 ? 255 : (Ipp8u)s[i]));
            }
            status = dstData_8u.Write(sOutputFile.c_str());
            CHECK_STATUS_PRINT_BR(status, "Image::Write()", GetBaseStatusString(status));
        }

        break;
    }

    if (status < 0)
        return status;

    return 0;
}

static void splitImageToSlices(IppiSize roiSize, int maskHeight, IppiSize* const pSliceSize, IppiSize* const pLastSliceSize, int* const slicesCount)
{
    int widthImage  = roiSize.width;
    int heightImage = roiSize.height;

    // Minimal slice height depends on filter mask size
    int heightSlice     = (maskHeight + 1 < heightImage ? maskHeight + 1 : heightImage);
    // Height of last slice can be slightly bigger than other slices height if image height is not factor of slice height.
    int heightLastSlice = heightImage % heightSlice;

    (*pSliceSize).width  = widthImage;
    (*pSliceSize).height = heightSlice;

    (*pLastSliceSize).width  = widthImage;
    (*pLastSliceSize).height = (heightLastSlice + heightSlice);

    *slicesCount = (int) (heightImage / heightSlice);
}
