/*******************************************************************************
* Copyright 2015-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.
*******************************************************************************/

/*
//   Purpose: Functions for splitting images on tiles
*/

#include "owni_tl.h"
#include "ownisplit_tl.h"

/* ////////////////////////////////////////////////////////////////////////////
//  Names:      owniSplit_LT
//
//  Purpose:    Splits the integral parameter splitSize on tiles according the following condition
//
//              ((splitSize / numTiles) + (splitSize % numTiles)) * multiplier <= threshold
//
//  Parameters:
//   splitSize        Size to split
//   multiplier         Formula's multiplier
//   threshold          Formula's threshold
//   splitOp            Approach of tile's number increasing
//                          ippSplitAdd     - increase tile's number on 1
//                          ippSplitSquare  - increase tile's number in 2 times
//   tileLength         Tile's length (in-out parameter)
//   residual           Residual - tail (in-out parameter)
//   numTiles           Number of tiles (in-out parameter)
*/
void owniSplit_LT(IppSizeL splitSize, IppSizeL multiplier, IppSizeL threshold, SplitOperation splitOp,
    IppSizeL *tileLength, IppSizeL *residual, IppSizeL *numTiles)
{
    IppSizeL k, size, res;
    size = *tileLength;
    res  = *residual;
    if (size * multiplier > threshold)
    {
        k  = *numTiles;

        while (size * multiplier > threshold)
        {
            if (splitOp == ippSplitAdd)
            {
                 if (splitSize / (k + 1) == 0) break;
                 k++;
            }
            if (splitOp == ippSplitSquare)
            {
                if (splitSize / (k * 2) == 0) break;
                k *= 2;
            }
            size = splitSize / k;
            res  = splitSize % k;
        }
        if (res > size)
        {
            k   += res / size;
            size = splitSize / k;
            res  = splitSize % k;
        }
        *tileLength = size;
        *residual   = res;
        *numTiles   = k;
    }
}

/* ////////////////////////////////////////////////////////////////////////////
//  Names:      owniGetTileSizeSimple_LT
//
//  Purpose:    Computes the tile size depending on input data
//
//  Parameters:
//   roiSize            Image size
//   minPixelNumber     Minimal number of pixels in the tile
//   minTileSize        Minimal size of tile along x and y axes
//   pTileSize          The size of a tile
//
*/
void owniGetTileSizeSimple_LT(IppiSizeL roiSize, IppSizeL minItemNumber, IppiSizeL minTileSize, IppiSizeL *pTileSize)
{
    Ipp32u numThreads;
    ippGetNumThreads_LT((int*)&numThreads);

    /* not splitting the destination image into the tiles */
    if (roiSize.width * roiSize.height <= minItemNumber || numThreads == 1)
    {
        pTileSize->width      = roiSize.width;
        pTileSize->height     = roiSize.height;
    }
    else
    {
        IppiSizeL tileSize;
        IppiSizeL residualSize = {0};
        IppSizeL  cols = 1, rows = 1;
        IppSizeL  desiredItemNumber;

        tileSize.width  = roiSize.width;
        tileSize.height = roiSize.height;

        desiredItemNumber = tileSize.width * tileSize.height * rows * cols / numThreads;
        desiredItemNumber = IPP_MAX(minItemNumber, desiredItemNumber);
        desiredItemNumber = IPP_MAX(tileSize.width * minTileSize.height, desiredItemNumber);

        /* splitting by row */
        owniSplit_LT(roiSize.height, tileSize.width, desiredItemNumber, ippSplitAdd, &tileSize.height, &residualSize.height, &rows);

        desiredItemNumber = tileSize.width * tileSize.height * rows * cols / numThreads;
        desiredItemNumber = IPP_MAX(minItemNumber, desiredItemNumber);
        desiredItemNumber = IPP_MAX(tileSize.height * minTileSize.width, desiredItemNumber);

        /* splitting by col */
        owniSplit_LT(roiSize.width, tileSize.height, desiredItemNumber, ippSplitAdd, &tileSize.width, &residualSize.width, &cols);

        pTileSize->width  = tileSize.width;
        pTileSize->height = tileSize.height;
    }

    return;
}

/* ////////////////////////////////////////////////////////////////////////////
//  Names:      owniSplitToTiles_L
//
//  Purpose:    Splits the image on tiles with the predefined tile size
//
//  Parameters:
//   roiSize            Image size
//   tileSize           Predefined tile size, it can be zero, in this case the tile size is set by the function
//   pSplit             Number of split parts along x and y axes
//   pTileSize          The size of a tile
//   pLastSize          The size of the last (corner) tile
//
*/
void ippiSplitToTiles_LT(IppiSizeL roiSize, IppiSizeL tileSize, IppiPointL *pSplit, IppiSizeL *pTileSize, IppiSizeL *pLastSize)
{
    IppSizeL width = roiSize.width;
    IppSizeL height = roiSize.height;
    IppSizeL widthT = tileSize.width;
    IppSizeL heightT = tileSize.height;
    IppSizeL widthL, heightL;
    IppSizeL additionX, additionY, additionLastX, additionLastY;
    if (widthT > width)   widthT = width;
    if (heightT > height) heightT = height;
    widthL = width % widthT;
    heightL = height % heightT;
    (*pSplit).x = (IppSizeL)(width / widthT);
    (*pSplit).y = (IppSizeL)(height / heightT);
    (*pTileSize).height = heightT;
    if ((heightL < (*pSplit).y) && (heightL > 0)) {
        (*pTileSize).width = widthT;
        (*pTileSize).height = heightT;
        (*pLastSize).width = widthT + widthL;
        (*pLastSize).height = heightT + heightL;
    }        
    else {
        additionX = widthL / (*pSplit).x;
        additionY = heightL / (*pSplit).y;
        (*pTileSize).width = widthT + additionX;
        (*pTileSize).height = heightT + additionY;
        additionLastX = widthL % ((*pSplit).x);
        additionLastY = heightL % ((*pSplit).y);
        (*pLastSize).width = (*pTileSize).width + additionLastX;
        (*pLastSize).height = heightT + additionY + additionLastY;
    }
}


/* ////////////////////////////////////////////////////////////////////////////
//  Names:      owniSplitUniform2D
//
//  Purpose:    Splits the image on tiles
//
//  Parameters:
//   roiSize            Image size
//   pixelSize          Size of image pixels in bytes
//   minTileSize        Minimum size of tile in pixels
//   pSplit             Number of split parts along x and y axes
//   pTileSize          The size of a tile
//   pTailSize          The size of the last (corner) tile
*/
IppStatus owniSplitUniform2D_LT(IppiSizeL roiSize, IppSizeL minItemNumber, IppiPointL *pSplit, IppiSizeL *pTileSize, IppiSizeL *pTailSize)
{
    IppiSizeL minTileSize;
    IppiSizeL tileSize = { 0 };
    minTileSize.width = 1;
    minTileSize.height = 1;
    owniGetTileSizeSimple_LT(roiSize, minItemNumber, minTileSize, &tileSize);

    ippiSplitToTiles_LT(roiSize, tileSize, pSplit, pTileSize, pTailSize);

    return ippStsNoErr;
}

IPPFUN(IppStatus, ippiSplitUniform2D_LT, (IppiSizeL roiSize, IppSizeL minItemNumber, IppiPointL *pSplit, IppiSizeL *pTileSize, IppiSizeL *pTailSize))
{
    if (pSplit == NULL || pTileSize == NULL || pTailSize == NULL) return ippStsNullPtrErr;
    if (minItemNumber < 0) return ippStsSizeErr;
    return owniSplitUniform2D_LT(roiSize, minItemNumber, pSplit, pTileSize, pTailSize);
}

/* ////////////////////////////////////////////////////////////////////////////
//  Names:      owniGetTileParamsByIndex
//
//  Purpose:    Get offset and size of the tile by the given index
//
//  Parameters:
//   index              ordinal index of a tile
//   splitImage         Split of the image by x- and y- axis correspondingly
//   tileSize           size of a tile
//   tailSize           size of the last right-bottom tile
//   pTileOffset        offset of the tile corresponding the left-top image corner
//   pTileSize          size of the tile
*/

IppStatus owniGetTileParamsByIndex_LT(IppSizeL index, IppiPointL splitImage, IppiSizeL tileSize, IppiSizeL tailSize, IppiPointL *pTileOffset, IppiSizeL *pTileSize)
{
    IppSizeL i, j;
    IppSizeL firstGreaterIndex = 1;
    int k;
    int add;
    if (pTileOffset == NULL || pTileSize == NULL) return ippStsNullPtrErr;
    i = index / splitImage.x;
    j = index % splitImage.x;

    if (i >= splitImage.y) return ippStsSizeErr;

    (*pTileOffset).x = j * tileSize.width;
    (*pTileOffset).y = i * tileSize.height;
    (*pTileSize).width = (j < splitImage.x - 1) ? tileSize.width : tailSize.width;
    firstGreaterIndex = tailSize.height - tileSize.height;
    k = splitImage.y - index;
    add = splitImage.y - firstGreaterIndex;
    if ((firstGreaterIndex < splitImage.y) && (tailSize.height > tileSize.height) && (firstGreaterIndex > 0)) {
        if (i < splitImage.y - firstGreaterIndex) {
            (*pTileSize).height = tileSize.height;
        }
        else {
            (*pTileSize).height = (tileSize.height + 1);
        if (k < firstGreaterIndex)
            (*pTileOffset).y = i * tileSize.height + (i - add) * 1;
        else
            (*pTileOffset).y = i * tileSize.height;
        }
    }
    else {
        (*pTileSize).height = (i < splitImage.y - 1) ? tileSize.height : tailSize.height;
    }
    return ippStsNoErr;
}

IPPFUN(IppStatus, ippiGetTileParamsByIndex_LT, (IppSizeL index, IppiPointL splitImage, IppiSizeL tileSize, IppiSizeL tailSize, IppiPointL *pTileOffset, IppiSizeL *pTileSize))
{
    return owniGetTileParamsByIndex_LT (index, splitImage, tileSize, tailSize, pTileOffset, pTileSize);
}

void ipprSplitToCubes_LT(IpprVolumeL roiVolume, IpprVolumeL cubeVolume, IpprPointL *pSplit, IpprVolumeL *pCubeVolume, IpprVolumeL *pLastVolume)
{
    IppSizeL width = roiVolume.width;
    IppSizeL height = roiVolume.height;
    IppSizeL depth = roiVolume.depth;
    IppSizeL widthT = cubeVolume.width;
    IppSizeL heightT = cubeVolume.height;
    IppSizeL depthT = cubeVolume.depth;
    IppSizeL widthL, heightL, depthL;
    if (widthT  > width)   widthT = width;
    if (heightT > height) heightT = height;
    if (depthT > depth) depthT = depth;
    (*pCubeVolume).width = widthT;
    (*pCubeVolume).height = heightT;
    (*pCubeVolume).depth = depthT;
    widthL = width % widthT;
    heightL = height % heightT;
    depthL = depth % depthT;
    (*pLastVolume).width = (widthL + widthT);
    (*pLastVolume).height = (heightL + heightT);
    (*pLastVolume).depth = (depthL + depthT);
    (*pSplit).x = (IppSizeL)(width / widthT);
    (*pSplit).y = (IppSizeL)(height / heightT);
    (*pSplit).z = (IppSizeL)(depth / depthT);
}
