/*
 * Copyright (c) 2023 Arm Limited.
 *
 * SPDX-License-Identifier: MIT
 *
 * 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 "src/cl/helpers/CLMemoryOpImage2dHelper.h"

#include "ckw/Error.h"
#include "ckw/TensorSampler.h"
#include "ckw/types/MemoryOperation.h"
#include "ckw/types/TensorStorageType.h"

#include "src/cl/CLKernelWriter.h"
#include "src/cl/CLTensorArgument.h"
#include "src/cl/CLTile.h"
#include "src/ITensor.h"
#include "src/Tensor3dMapper.h"
#include "src/TileView.h"

namespace ckw
{
void CLMemoryOpImage2dHelper::initialize(const CLTile *x, const CLTile *z, const CLTile *b)
{
    _coord_x = x->scalar(0, 0).str;
    _coord_z = z->scalar(0, 0).str;
    _coord_b = b->scalar(0, 0).str;
}

void CLMemoryOpImage2dHelper::write_row(int32_t row_id, const std::string &coord_y)
{
    // The only check required is on Y.
    out_of_bound_initialize_y(coord_y);

    const std::string dst     = _dst.vector(row_id).str;
    const std::string sampler = to_ls_image2d_sampler();
    const std::string coord   = to_ls_image2d_address(_coord_x, coord_y, _coord_z, _coord_b);
    const std::string ls_buf  = to_ls_image2d(_op, _ls_width_full, dst, sampler, coord);

    _writer->op_write_raw_code(ls_buf + ";\n");

    out_of_bound_finalize_y();
}

void CLMemoryOpImage2dHelper::finalize()
{
}

bool CLMemoryOpImage2dHelper::validate(const CLKernelWriter   *writer,
                                       const ITensor          *tensor,
                                       const TensorSampler    *sampler,
                                       const Tensor3dMapper   *mapper,
                                       MemoryOperation         op,
                                       const TileView<CLTile> &dst)
{
    CKW_UNUSED(writer, tensor, mapper);

    if (dst.width() != 4)
    {
        return false;
    }
    if (sampler->address_mode_x() != TensorSamplerAddressModeX::None)
    {
        return false;
    }
    if (sampler->address_mode_z() != TensorSamplerAddressModeZ::None)
    {
        return false;
    }
    if (sampler->storage() != TensorStorageType::Texture2dReadOnly && op == MemoryOperation::Load)
    {
        return false;
    }
    if (sampler->storage() != TensorStorageType::Texture2dWriteOnly && op == MemoryOperation::Store)
    {
        return false;
    }
    if ((dst.data_type() != DataType::Fp32) && (dst.data_type() != DataType::Fp16))
    {
        return false;
    }
    return true;
}

void CLMemoryOpImage2dHelper::out_of_bound_initialize_y(const std::string &coord)
{
    CKW_UNUSED(coord);

    const TensorSamplerAddressModeY address_mode_y = _sampler->address_mode_y();
    switch (address_mode_y)
    {
        case TensorSamplerAddressModeY::SkipLessThanZero:
            _writer->op_write_raw_code("if(" + coord + " >= 0)\n{\n");
            break;
        case TensorSamplerAddressModeY::ClampToBorderMaxOnly:
        case TensorSamplerAddressModeY::None:
            break;
        default:
            CKW_THROW_MSG("Unsupported address mode for Y dimension");
    }
}

void CLMemoryOpImage2dHelper::out_of_bound_finalize_y()
{
    const TensorSamplerAddressModeY address_mode_y = _sampler->address_mode_y();
    switch (address_mode_y)
    {
        case TensorSamplerAddressModeY::SkipLessThanZero:
            _writer->op_write_raw_code("}\n");
            break;
        case TensorSamplerAddressModeY::ClampToBorderMaxOnly:
        case TensorSamplerAddressModeY::None:
            break;
        default:
            CKW_THROW_MSG("Unsupported address mode for Y dimension");
    }
}

std::string CLMemoryOpImage2dHelper::to_ls_image2d(MemoryOperation    op,
                                                   int32_t            vector_width,
                                                   const std::string &data,
                                                   const std::string &sampler,
                                                   const std::string &address) const
{
    CKW_UNUSED(vector_width);
    CKW_ASSERT_MSG(_dst.data_type() == DataType::Fp32 || _dst.data_type() == DataType::Fp16,
                   "Image2d only supports floating-point data type");

    const TensorStorageType tensor_storage = _sampler->storage();
    const std::string       image2d_obj    = _tensor->storage(tensor_storage).val;
    const std::string       post_fix       = _dst.data_type() == DataType::Fp32 ? "f" : "h";

    switch (op)
    {
        case MemoryOperation::Load:
            return data + " = read_image" + post_fix + "(" + image2d_obj + ", " + sampler + ", " + address + ")";
            break;
        case MemoryOperation::Store:
            return "write_image" + post_fix + "(" + image2d_obj + ", " + address + ", " + data + ")";
        default:
            CKW_THROW_MSG("Unsupported MemoryOperation");
    }
}

std::string CLMemoryOpImage2dHelper::to_ls_image2d_sampler() const
{
    const auto address_mode_y = _sampler->address_mode_y();

    switch (address_mode_y)
    {
        case TensorSamplerAddressModeY::None:
            return "CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST";
        case TensorSamplerAddressModeY::SkipLessThanZero:
        case TensorSamplerAddressModeY::ClampToBorderMaxOnly:
            return "CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST";
        default:
            CKW_THROW_MSG("Unsupported address_mode_coord");
    }
}

std::string CLMemoryOpImage2dHelper::to_ls_image2d_address(const std::string &x,
                                                           const std::string &y,
                                                           const std::string &z,
                                                           const std::string &b) const
{
    std::string coord_x = "(" + x + ") >> 2";
    std::string coord_y = "(";

    if (y != "0")
    {
        coord_y += y;
    }
    if (z != "0" && (_mapper->dim_z().str != "1"))
    {
        const std::string dim = _mapper->dim_y().str;
        coord_y += " + (";
        coord_y += z + ")";
        coord_y += " * ";
        coord_y += dim;
    }
    if (b != "0" && (_mapper->dim_batch().str != "1"))
    {
        const std::string dim0 = _mapper->dim_y().str;
        const std::string dim1 = _mapper->dim_z().str;
        coord_y += " + (";
        coord_y += b + ")";
        coord_y += " * ";
        coord_y += dim0;
        coord_y += " * ";
        coord_y += dim1;
    }
    coord_y += ")";
    return "(int2)(" + coord_x + ", " + coord_y + ")";
}

} // namespace ckw
