/*
* Copyright 2014-2015 Esri
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/******************************************************************************
*
* Project:  Meta Raster File Format Driver Implementation, overlay support
* Purpose:  Implementation overlay support for MRF
*
* Author:   Lucian Plesea, Lucian.Plesea@jpl.nasa.gov, lplesea@esri.com
*
******************************************************************************
*  This source file contains the non GDAL standard part of the MRF overview building
*  The PatchOverview method only handles powers of 2 overviews!!
****************************************************************************/

#include "marfa.h"
#include <vector>

CPL_CVSID("$Id: mrf_overview.cpp 35929 2016-10-25 16:09:00Z goatbar $");

using std::vector;

NAMESPACE_MRF_START

// Count the values in a buffer that match a specific value
template<typename T> static int MatchCount(T *buff, int sz, T val) {
    int ncount=0;
    for (int i=0; i < sz; i++)
        if (buff[i] == val)
            ncount++;
    return ncount;
}

//
// Scales by 2x2 a buffer in place, using Nearest resampling
// Always pick the top-left corner
//
template<typename T> static void NearByFour(T *buff, int xsz, int ysz) {
    T *obuff = buff;
    for (int line = 0; line < ysz; line++) {
                // Copy every other pixel
                for (int col = 0; col < xsz; col++, buff++) {
                        *obuff++ = *buff++;
                }
                // Skip every other line
                buff += xsz * 2;
    }
}

//
// If the NoData value exists, pick a valid pixel if possible
//
template<typename T> static void NearByFour(T *buff, int xsz, int ysz, T ndv) {
    T *obuff = buff;
    T *evenline = buff;

    for (int line = 0; line<ysz; line++) {
        T *oddline = evenline + xsz * 2;
        for (int col = 0; col<xsz; col++) {

            if (evenline[0] != ndv)
                *obuff++ = evenline[0];
            else if (evenline[1] != ndv)
                *obuff++ = evenline[1];
            else if (oddline[0] != ndv)
                *obuff++ = oddline[0];
            else
                *obuff++ = oddline[1];

            evenline += 2; oddline += 2;
        }
        evenline += xsz * 2;  // Skips the other input line
    }
}

// Scales by 2x2 using averaging
// There are lots of these AverageByFour templates, because some types have to be treated
// slightly different than others.  Some could be folded by using is_integral(),
// but support is not universal
// There are two categories, depending on NoData presence
//

// Integer data types shorter than 32 bit use integer math safely
template<typename T> static void AverageByFour(T *buff, int xsz, int ysz) {
    T *obuff=buff;
    T *evenline=buff;

    for (int line=0; line<ysz; line++) {
        T *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            *obuff++ = (2 + evenline[0] + evenline[1] + oddline[0] + oddline[1]) / 4;
            evenline +=2; oddline +=2;
        }
        evenline += xsz*2;  // Skips the other line
    }
}

// 32bit int specialization, avoiding overflow by using 64bit int math
template<> void AverageByFour<GInt32>(GInt32 *buff, int xsz, int ysz) {
    GInt32 *obuff=buff;
    GInt32 *evenline=buff;

    for (int line=0; line<ysz; line++) {
        GInt32 *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            *obuff++ = (GIntBig(2) + evenline[0] + evenline[1] + oddline[0] + oddline[1]) / 4;
            evenline +=2; oddline +=2;
        }
        evenline += xsz*2;  // Skips the other line
    }
}

// Same for 32bit unsigned int specialization
template<> void AverageByFour<GUInt32>(GUInt32 *buff, int xsz, int ysz) {
    GUInt32 *obuff=buff;
    GUInt32 *evenline=buff;

    for (int line=0; line<ysz; line++) {
        GUInt32 *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            *obuff++ = (GIntBig(2) + evenline[0] + evenline[1] + oddline[0] + oddline[1]) / 4;
            evenline +=2; oddline +=2;
        }
        evenline += xsz*2;  // Skips the other line
    }
}

// float specialization
template<> void AverageByFour<float>(float *buff, int xsz, int ysz) {
    float *obuff=buff;
    float *evenline=buff;

    for (int line=0; line<ysz; line++) {
        float *oddline = evenline + xsz*2;
        for (int col=0; col<xsz; col++) {
            *obuff++ = (evenline[0] + evenline[1] + oddline[0] + oddline[1]) * 0.25f;
            evenline +=2; oddline +=2;
        }
        evenline += xsz*2;  // Skips the other line
    }
}

// double specialization
template<> void AverageByFour<double>(double *buff, int xsz, int ysz) {
    double *obuff=buff;
    double *evenline=buff;

    for (int line=0; line<ysz; line++) {
        double *oddline = evenline + xsz*2;
        for (int col=0; col<xsz; col++) {
            *obuff++ = (evenline[0] + evenline[1] + oddline[0] + oddline[1]) * 0.25;
            evenline +=2; oddline +=2;
        }
        evenline += xsz*2;  // Skips the other line
    }
}

//
// Integer type specialization, with roundup and integer math, avoids overflow using GIntBig accumulator
// Speedup by specialization for smaller byte count int types is probably not worth much
// since there are so many conditions here
//
template<typename T> static void AverageByFour(T *buff, int xsz, int ysz, T ndv) {
    T *obuff=buff;
    T *evenline=buff;

    for (int line=0; line<ysz; line++) {
        T *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            GIntBig acc = 0;
            int count = 0;

// Temporary macro to accumulate the sum, uses the value, increments the pointer
// Careful with this one, it has side effects
#define use(valp) if (*valp != ndv) { acc += *valp; count++; }; valp++;
            use(evenline); use(evenline); use(oddline); use(oddline);
#undef use
            // The count/2 is the bias to obtain correct rounding
            *obuff++ = T((count != 0) ? ((acc + count/2) / count) : ndv);
        }
        evenline += xsz*2;  // Skips every other line
    }
}

// float specialization
template<> void AverageByFour<float>(float *buff, int xsz, int ysz, float ndv) {
    float *obuff=buff;
    float *evenline=buff;

    for (int line=0; line<ysz; line++) {
        float *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            double acc = 0;
            double count = 0;

// Temporary macro to accumulate the sum, uses the value, increments the pointer
// Careful with this one, it has side effects
#define use(valp) if (*valp != ndv) { acc += *valp; count += 1.0; }; valp++;
            use(evenline); use(evenline); use(oddline); use(oddline);
#undef use
            // Output value is eiher accumulator divided by count or the NoDataValue
            *obuff++ = float((count != 0.0) ? acc/count : ndv);
        }
        evenline += xsz*2;  // Skips every other line
    }
}

// double specialization, same as above
template<> void AverageByFour<double>(double *buff, int xsz, int ysz, double ndv) {
    double *obuff=buff;
    double *evenline=buff;

    for (int line=0; line<ysz; line++) {
        double *oddline=evenline+xsz*2;
        for (int col=0; col<xsz; col++) {
            double acc = 0;
            double count = 0;

// Temporary macro to accumulate the sum, uses the value, increments the pointer
// Careful with this one, it has side effects
#define use(valp) if (*valp != ndv) { acc += *valp; count += 1.0; }; valp++;
            use(evenline); use(evenline); use(oddline); use(oddline);
#undef use
            // Output value is eiher accumulator divided by count or the NoDataValue
            *obuff++ = ((count != 0.0) ? acc/count : ndv);
        }
        evenline += xsz*2;  // Skips every other line
    }
}

/*
 *\brief Patches an overview for the selected area
 * arguments are in blocks in the source level, if toTheTop is false it only does the next level
 * It will read adjacent blocks if they are needed, so actual area read might be padded by one block in
 * either side
 */

CPLErr GDALMRFDataset::PatchOverview(int BlockX,int BlockY,
                                     int Width,int Height,
                                     int srcLevel, int recursive,
                                     int sampling_mode)
{
    GDALRasterBand *b0=GetRasterBand(1);
    if ( b0->GetOverviewCount() <= srcLevel)
        return CE_None;

    int BlockXOut = BlockX/2 ; // Round down
    Width += BlockX & 1; // Increment width if rounding down
    int BlockYOut = BlockY/2 ; // Round down
    Height += BlockY & 1; // Increment height if rounding down

    int WidthOut = Width/2 + (Width & 1); // Round up
    int HeightOut = Height/2 + (Height & 1); // Round up

    int bands = GetRasterCount();
    int tsz_x,tsz_y;
    b0->GetBlockSize(&tsz_x, &tsz_y);
    GDALDataType eDataType = b0->GetRasterDataType();

    int pixel_size = GDALGetDataTypeSize(eDataType)/8; // Bytes per pixel per band
    int line_size = tsz_x * pixel_size; // A line has this many bytes
    int buffer_size = line_size * tsz_y; // A block size in bytes

    // Build a vector of input and output bands
    vector<GDALRasterBand *> src_b;
    vector<GDALRasterBand *> dst_b;

    for (int band=1; band<=bands; band++) {
        if (srcLevel==0)
            src_b.push_back(GetRasterBand(band));
        else
            src_b.push_back(GetRasterBand(band)->GetOverview(srcLevel-1));
        dst_b.push_back(GetRasterBand(band)->GetOverview(srcLevel));
    }

    // Allocate space for four blocks
    void *buffer = CPLMalloc(buffer_size *4 );

    //
    // The inner loop is the band, so it is efficient for interleaved data.
    // There is no penalty for separate bands either.
    //
    for (int y=0; y<HeightOut; y++) {
        int dst_offset_y = BlockYOut+y;
        int src_offset_y = dst_offset_y *2;
        for (int x=0; x<WidthOut; x++) {
            int dst_offset_x = BlockXOut + x;
            int src_offset_x = dst_offset_x * 2;

            // Do it band at a time so we can work in grayscale
            for (int band=0; band<bands; band++) { // Counting from zero in a vector

                int sz_x = 2*tsz_x ,sz_y = 2*tsz_y ;
                GDALMRFRasterBand *bsrc = static_cast<GDALMRFRasterBand *>(src_b[band]);
                GDALMRFRasterBand *bdst = static_cast<GDALMRFRasterBand *>(dst_b[band]);

                //
                // Clip to the size to the input image
                // This is one of the worst features of GDAL, it doesn't tolerate any padding
                //
                bool adjusted = false;
                if (bsrc->GetXSize() < (src_offset_x + 2) * tsz_x) {
                    sz_x = bsrc->GetXSize() - src_offset_x * tsz_x;
                    adjusted = true;
                }
                if (bsrc->GetYSize() < (src_offset_y + 2) * tsz_y) {
                    sz_y = bsrc->GetYSize() - src_offset_y * tsz_y;
                    adjusted = true;
                }

                if (adjusted) { // Fill with no data for partial buffer, instead of padding afterwards
                    size_t bsb = bsrc->blockSizeBytes();
                    char *b=static_cast<char *>(buffer);
                    bsrc->FillBlock(b);
                    bsrc->FillBlock(b + bsb);
                    bsrc->FillBlock(b + 2*bsb);
                    bsrc->FillBlock(b + 3*bsb);
                }

                int hasNoData = 0;
                double ndv = bsrc->GetNoDataValue(&hasNoData);

                CPLErr eErr = bsrc->RasterIO( GF_Read,
                    src_offset_x*tsz_x, src_offset_y*tsz_y, // offset in input image
                    sz_x, sz_y, // Size in output image
                    buffer, sz_x, sz_y, // Buffer and size in buffer
                    eDataType, // Requested type
                    pixel_size, 2 * line_size
#if GDAL_VERSION_MAJOR >= 2
                    ,NULL
#endif
                    ); // Pixel and line space
                if( eErr != CE_None )
                {
                    // TODO ?
                    CPLError(CE_Failure, CPLE_AppDefined, "RasterIO() failed");
                }

                // Count the NoData values
                int count = 0; // Assume all points are data
                if (sampling_mode == SAMPLING_Avg) {

// Dispatch based on data type
// Use an ugly temporary macro to make it look easy
// Runs the optimized version if the page is full with data
#define resample(T)\
    if (hasNoData) {\
        count = MatchCount((T *)buffer, 4 * tsz_x * tsz_y, T(ndv));\
        if (4 * tsz_x * tsz_y == count)\
            bdst->FillBlock(buffer);\
        else if (0 != count)\
            AverageByFour((T *)buffer, tsz_x, tsz_y, T(ndv));\
        }\
    if (0 == count)\
        AverageByFour((T *)buffer, tsz_x, tsz_y);\
    break;

                    switch (eDataType) {
                    case GDT_Byte:      resample(GByte);
                    case GDT_UInt16:    resample(GUInt16);
                    case GDT_Int16:     resample(GInt16);
                    case GDT_UInt32:    resample(GUInt32);
                    case GDT_Int32:     resample(GInt32);
                    case GDT_Float32:   resample(float);
                    case GDT_Float64:   resample(double);
                    default: CPLAssert(false); break;
                    }
#undef resample
                }
                else if (sampling_mode == SAMPLING_Near) {

#define resample(T)\
    if (hasNoData) {\
        count = MatchCount((T *)buffer, 4 * tsz_x * tsz_y, T(ndv));\
        if (4 * tsz_x * tsz_y == count)\
            bdst->FillBlock(buffer);\
        else if (0 != count)\
            NearByFour((T *)buffer, tsz_x, tsz_y, T(ndv));\
        }\
    if (0 == count)\
        NearByFour((T *)buffer, tsz_x, tsz_y);\
    break;
                    switch (eDataType) {
                    case GDT_Byte:      resample(GByte);
                    case GDT_UInt16:    resample(GUInt16);
                    case GDT_Int16:     resample(GInt16);
                    case GDT_UInt32:    resample(GUInt32);
                    case GDT_Int32:     resample(GInt32);
                    case GDT_Float32:   resample(float);
                    case GDT_Float64:   resample(double);
                    default: CPLAssert(false); break;
                    }
#undef resample
                }

                // Done filling the buffer
                // Argh, still need to clip the output to the band size on the right and bottom
                // The offset should be fine, just the size might need adjustments
                sz_x = tsz_x;
                sz_y = tsz_y ;

                if ( bdst->GetXSize() < dst_offset_x * sz_x + sz_x )
                    sz_x = bdst->GetXSize() - dst_offset_x * sz_x;
                if ( bdst->GetYSize() < dst_offset_y * sz_y + sz_y )
                    sz_y = bdst->GetYSize() - dst_offset_y * sz_y;

                eErr = bdst->RasterIO( GF_Write,
                    dst_offset_x*tsz_x, dst_offset_y*tsz_y, // offset in output image
                    sz_x, sz_y, // Size in output image
                    buffer, sz_x, sz_y, // Buffer and size in buffer
                    eDataType, // Requested type
                    pixel_size, line_size
#if GDAL_VERSION_MAJOR >= 2
                    ,NULL
#endif
                ); // Pixel and line space
                if( eErr != CE_None )
                {
                    // TODO ?
                    CPLError(CE_Failure, CPLE_AppDefined, "RasterIO() failed");
                }
            }

            // Mark the input data as no longer needed, saves RAM
            for (int band=0; band<bands; band++)
                src_b[band]->FlushCache();
        }
    }

    CPLFree(buffer);

    for (int band=0; band<bands; band++)
        dst_b[band]->FlushCache(); // Commit the output to disk

    if (!recursive)
        return CE_None;
    return PatchOverview( BlockXOut, BlockYOut, WidthOut, HeightOut, srcLevel+1, true);
}

NAMESPACE_MRF_END
