///////////////////////////////////////////////////////////////////////////////
//
// Project:  C++ Test Suite for GDAL/OGR
// Purpose:  Test constant and builtin arguments for C++ pixel functions
// Author:   Momtchil Momtchev <momtchil@momtchev.com>
//
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2022, Momtchil Momtchev <momtchil@momtchev.com>
/*
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "gdal_unit_test.h"

#include "cpl_string.h"
#include "gdal_alg.h"
#include "gdal_priv.h"
#include "gdal.h"
#include "../../frmts/vrt/vrtdataset.h"

#include <vector>

#include "gtest_include.h"

CPLErr CustomPixelFuncWithMetadata(void **papoSources, int nSources,
                                   void *pData, int nXSize, int nYSize,
                                   GDALDataType eSrcType, GDALDataType eBufType,
                                   int nPixelSpace, int nLineSpace,
                                   CSLConstList papszArgs);
CPLErr CustomPixelFunc(void **papoSources, int nSources, void *pData,
                       int nXSize, int nYSize, GDALDataType eSrcType,
                       GDALDataType eBufType, int nPixelSpace, int nLineSpace,
                       CSLConstList papszArgs);
CPLErr CustomPixelFuncNoArgs(void **papoSources, int nSources, void *pData,
                             int nXSize, int nYSize, GDALDataType eSrcType,
                             GDALDataType eBufType, int nPixelSpace,
                             int nLineSpace);

CPLErr CustomPixelFuncWithMetadata(void **papoSources, int nSources,
                                   void *pData, int nXSize, int nYSize,
                                   GDALDataType eSrcType, GDALDataType eBufType,
                                   int nPixelSpace, int nLineSpace,
                                   CSLConstList papszArgs)
{

    /* ---- Init ---- */
    if (nSources != 1)
        return CE_Failure;
    const char *pszConstant = CSLFetchNameValue(papszArgs, "customConstant");
    if (pszConstant == nullptr)
        return CE_Failure;
    if (strncmp(pszConstant, "something", strlen("something")))
        return CE_Failure;
    const char *pszScale = CSLFetchNameValue(papszArgs, "scale");
    if (pszScale == nullptr)
        return CE_Failure;

    /* ---- Set pixels ---- */
    size_t ii = 0;
    for (int iLine = 0; iLine < nYSize; ++iLine)
    {
        for (int iCol = 0; iCol < nXSize; ++iCol, ++ii)
        {
            const double dfPixVal = SRCVAL(papoSources[0], eSrcType, ii) * 2;
            GDALCopyWords(&dfPixVal, GDT_Float64, 0,
                          static_cast<GByte *>(pData) +
                              static_cast<GSpacing>(nLineSpace) * iLine +
                              iCol * nPixelSpace,
                          eBufType, nPixelSpace, 1);
        }
    }

    /* ---- Return success ---- */
    return CE_None;
}

CPLErr CustomPixelFunc(void **papoSources, int nSources, void *pData,
                       int nXSize, int nYSize, GDALDataType eSrcType,
                       GDALDataType eBufType, int nPixelSpace, int nLineSpace,
                       CSLConstList papszArgs)
{

    /* ---- Init ---- */
    if (nSources != 1)
        return CE_Failure;

    (void)papszArgs;

    /* ---- Set pixels ---- */
    size_t ii = 0;
    for (int iLine = 0; iLine < nYSize; ++iLine)
    {
        for (int iCol = 0; iCol < nXSize; ++iCol, ++ii)
        {
            const double dfPixVal = SRCVAL(papoSources[0], eSrcType, ii) * 3;
            GDALCopyWords(&dfPixVal, GDT_Float64, 0,
                          static_cast<GByte *>(pData) +
                              static_cast<GSpacing>(nLineSpace) * iLine +
                              iCol * nPixelSpace,
                          eBufType, nPixelSpace, 1);
        }
    }

    /* ---- Return success ---- */
    return CE_None;
}

CPLErr CustomPixelFuncNoArgs(void **papoSources, int nSources, void *pData,
                             int nXSize, int nYSize, GDALDataType eSrcType,
                             GDALDataType eBufType, int nPixelSpace,
                             int nLineSpace)
{

    /* ---- Init ---- */
    if (nSources != 1)
        return CE_Failure;

    /* ---- Set pixels ---- */
    size_t ii = 0;
    for (int iLine = 0; iLine < nYSize; ++iLine)
    {
        for (int iCol = 0; iCol < nXSize; ++iCol, ++ii)
        {
            const double dfPixVal = SRCVAL(papoSources[0], eSrcType, ii) * 4;
            GDALCopyWords(&dfPixVal, GDT_Float64, 0,
                          static_cast<GByte *>(pData) +
                              static_cast<GSpacing>(nLineSpace) * iLine +
                              iCol * nPixelSpace,
                          eBufType, nPixelSpace, 1);
        }
    }

    /* ---- Return success ---- */
    return CE_None;
}

namespace
{
const char pszFuncMetadata[] =
    "<PixelFunctionArgumentsList>"
    "   <Argument name='customConstant' type='constant' value='something'>"
    "   </Argument>"
    "   <Argument type='builtin' value='scale'>"
    "   </Argument>"
    "</PixelFunctionArgumentsList>";

struct test_gdal_pixelfn : public ::testing::Test
{
    std::string src_;

    test_gdal_pixelfn()
    {
        src_ = tut::common::data_basedir;
        src_ += SEP;
        src_ += "pixelfn.vrt";
    }
};

// Test constant parameters in a custom pixel function
TEST_F(test_gdal_pixelfn, custom_pixel_fn_constant_parameters)
{
    GDALAddDerivedBandPixelFuncWithArgs("custom", CustomPixelFuncWithMetadata,
                                        pszFuncMetadata);
    GDALDatasetH ds = GDALOpen(src_.c_str(), GA_ReadOnly);
    ASSERT_TRUE(nullptr != ds);

    GDALRasterBandH band = GDALGetRasterBand(ds, 1);
    ASSERT_TRUE(nullptr != band);

    float buf[20 * 20];
    CPL_IGNORE_RET_VAL(GDALRasterIO(band, GF_Read, 0, 0, 20, 20, buf, 20, 20,
                                    GDT_Float32, 0, 0));

    EXPECT_EQ(buf[0], 107 * 2);

    GDALClose(ds);
}

// Test registering of a custom pixel function without metadata
TEST_F(test_gdal_pixelfn, custom_pixel_fn_without_metadata)
{
    GDALAddDerivedBandPixelFuncWithArgs("custom2", CustomPixelFunc, nullptr);
    GDALDatasetH ds = GDALOpen(src_.c_str(), GA_ReadOnly);
    ASSERT_TRUE(nullptr != ds);

    GDALRasterBandH band = GDALGetRasterBand(ds, 1);
    ASSERT_TRUE(nullptr != band);

    VRTDerivedRasterBand *derived = reinterpret_cast<VRTDerivedRasterBand *>(
        GDALRasterBand::FromHandle(band));
    derived->SetPixelFunctionName("custom2");

    float buf[20 * 20];
    CPL_IGNORE_RET_VAL(GDALRasterIO(band, GF_Read, 0, 0, 20, 20, buf, 20, 20,
                                    GDT_Float32, 0, 0));

    EXPECT_EQ(buf[0], 107 * 3);

    GDALClose(ds);
}

// Test the registering of a custom pixel function without args
TEST_F(test_gdal_pixelfn, custom_pixel_fn_without_args)
{
    GDALAddDerivedBandPixelFunc("custom3", CustomPixelFuncNoArgs);
    GDALDatasetH ds = GDALOpen(src_.c_str(), GA_ReadOnly);
    ASSERT_TRUE(nullptr != ds);

    GDALRasterBandH band = GDALGetRasterBand(ds, 1);
    ASSERT_TRUE(nullptr != band);

    VRTDerivedRasterBand *derived = reinterpret_cast<VRTDerivedRasterBand *>(
        GDALRasterBand::FromHandle(band));
    derived->SetPixelFunctionName("custom3");

    float buf[20 * 20];
    CPL_IGNORE_RET_VAL(GDALRasterIO(band, GF_Read, 0, 0, 20, 20, buf, 20, 20,
                                    GDT_Float32, 0, 0));

    EXPECT_EQ(buf[0], 107 * 4);

    GDALClose(ds);
}

}  // namespace
