/*
 * Copyright (c) 2016-2020 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 "arm_compute/core/TensorInfo.h"

#include "arm_compute/core/Error.h"
#include "arm_compute/core/HOGInfo.h"
#include "arm_compute/core/Helpers.h"
#include "arm_compute/core/TensorInfo.h"
#include "arm_compute/core/Validate.h"
#include "support/MemorySupport.h"

using namespace arm_compute;

TensorInfo::TensorInfo()
    : _total_size(0), _offset_first_element_in_bytes(0), _strides_in_bytes(), _num_channels(0), _tensor_shape(), _data_type(DataType::UNKNOWN), _format(Format::UNKNOWN), _is_resizable{ true }, _is_dynamic{ false },
      _valid_region{ Coordinates(), _tensor_shape }, _padding{ 0 }, _quantization_info(), _data_layout(DataLayout::NCHW)
{
}

TensorInfo::TensorInfo(const ITensorInfo &info)
    : TensorInfo()
{
    _total_size                    = info.total_size();
    _offset_first_element_in_bytes = info.offset_first_element_in_bytes();
    _strides_in_bytes              = info.strides_in_bytes();
    _num_channels                  = info.num_channels();
    _tensor_shape                  = info.tensor_shape();
    _data_type                     = info.data_type();
    _format                        = info.format();
    _is_resizable                  = info.is_resizable();
    _is_dynamic                    = info.is_dynamic();
    _valid_region                  = info.valid_region();
    _padding                       = info.padding();
    _quantization_info             = info.quantization_info();
    _data_layout                   = info.data_layout();
}

TensorInfo::TensorInfo(Format format)
    : TensorInfo(TensorShape(), format)
{
}

TensorInfo::TensorInfo(unsigned int width, unsigned int height, Format format)
    : TensorInfo(TensorShape(width, height), format)
{
}

TensorInfo::TensorInfo(const TensorShape &tensor_shape, Format format)
    : TensorInfo()
{
    init(tensor_shape, format);
}

TensorInfo::TensorInfo(size_t num_channels, DataType data_type)
    : TensorInfo()
{
    init(TensorShape(), num_channels, data_type);
}

TensorInfo::TensorInfo(const TensorShape &tensor_shape, size_t num_channels, DataType data_type)
    : TensorInfo()
{
    init(tensor_shape, num_channels, data_type);
}

TensorInfo::TensorInfo(const TensorShape &tensor_shape, size_t num_channels, DataType data_type, QuantizationInfo quantization_info)
    : TensorInfo()
{
    init(tensor_shape, num_channels, data_type);
    _quantization_info = std::move(quantization_info);
}

TensorInfo::TensorInfo(const TensorShape &tensor_shape, size_t num_channels, DataType data_type, DataLayout data_layout)
    : TensorInfo()
{
    init(tensor_shape, num_channels, data_type);
    _data_layout = data_layout;
}

TensorInfo::TensorInfo(const HOGInfo &hog_info, unsigned int width, unsigned int height)
    : TensorInfo()
{
    init(hog_info, width, height);
}

void TensorInfo::init(Format format)
{
    init(TensorShape(), format);
}

void TensorInfo::init(const TensorShape &tensor_shape, Format format)
{
    size_t         num_channels = num_channels_from_format(format);
    const DataType type         = data_type_from_format(format);

    init(tensor_shape, num_channels, type);

    _format = format;
}

void TensorInfo::init(const TensorShape &tensor_shape, Format format,
                      const Strides &strides_in_bytes, size_t offset_first_element_in_bytes,
                      size_t total_size_in_bytes)
{
    size_t         num_channels = num_channels_from_format(format);
    const DataType type         = data_type_from_format(format);

    init(tensor_shape, num_channels, type, strides_in_bytes, offset_first_element_in_bytes, total_size_in_bytes);

    _format = format;
}

void TensorInfo::init(size_t num_channels, DataType data_type)
{
    init(TensorShape(), num_channels, data_type);
}

void TensorInfo::init(const TensorShape &tensor_shape, size_t num_channels, DataType data_type)
{
    ARM_COMPUTE_ERROR_ON(num_channels == 0);

    _data_type    = data_type;
    _num_channels = num_channels;
    _format       = Format::UNKNOWN;

    set_tensor_shape(tensor_shape);
}

void TensorInfo::init(const TensorShape &tensor_shape, size_t num_channels, DataType data_type,
                      const Strides &strides_in_bytes, size_t offset_first_element_in_bytes,
                      size_t total_size_in_bytes)
{
    ARM_COMPUTE_ERROR_ON(num_channels == 0);

    _data_type                     = data_type;
    _num_channels                  = num_channels;
    _format                        = Format::UNKNOWN;
    _tensor_shape                  = tensor_shape;
    _offset_first_element_in_bytes = offset_first_element_in_bytes;
    _strides_in_bytes              = strides_in_bytes;
    _total_size                    = total_size_in_bytes;

    _valid_region = ValidRegion{ Coordinates(), _tensor_shape };
}

void TensorInfo::init(const HOGInfo &hog_info, unsigned int width, unsigned int height)
{
    // Number of cells for each block
    const Size2D num_cells_per_block = hog_info.num_cells_per_block();

    // Tensor Size = (Number of horizontal block positions) * (Number of vertical block positions)
    const Size2D num_block_positions_per_img = hog_info.num_block_positions_per_image(Size2D(width, height));

    // Number of tensor channels = (Number of cells per block) * (Number of bins per cell)
    const size_t num_channels = num_cells_per_block.area() * hog_info.num_bins();

    init(TensorShape(num_block_positions_per_img.width, num_block_positions_per_img.height), num_channels, DataType::F32);
}

size_t TensorInfo::init_auto_padding(const TensorShape &tensor_shape, Format format)
{
    const size_t   num_channels = num_channels_from_format(format);
    const DataType type         = data_type_from_format(format);
    size_t         total_size   = init_auto_padding(tensor_shape, num_channels, type);

    _format = format;

    return total_size;
}

size_t TensorInfo::init_auto_padding(const TensorShape &tensor_shape, size_t num_channels, DataType data_type)
{
    ARM_COMPUTE_ERROR_ON(num_channels == 0);

    _data_type    = data_type;
    _num_channels = num_channels;
    _format       = Format::UNKNOWN;
    _tensor_shape = tensor_shape;

    _valid_region = ValidRegion{ Coordinates(), _tensor_shape };

    auto_padding();

    return _total_size;
}

size_t TensorInfo::init_auto_padding(const HOGInfo &hog_info, unsigned int width, unsigned int height)
{
    // Number of cells for each block
    const Size2D num_cells_per_block = hog_info.num_cells_per_block();

    // Tensor Size = (Number of horizontal block positions) * (Number of vertical block positions)
    const Size2D num_block_positions_per_img = hog_info.num_block_positions_per_image(Size2D(width, height));

    // Number of tensor channels = (Number of cells per block) * (Number of bins per cell)
    const size_t num_channels = num_cells_per_block.area() * hog_info.num_bins();

    return init_auto_padding(TensorShape(num_block_positions_per_img.width, num_block_positions_per_img.height), num_channels, DataType::F32);
}

bool TensorInfo::auto_padding()
{
    ARM_COMPUTE_ERROR_ON(!_is_resizable);

    // Some kernels compute 32 elements at the time, worst case scenario they
    // will read 32 values after the last element
    const size_t extra_pad_x = _tensor_shape.num_dimensions() < 1 ? 0 : 32;
    const size_t pad_x       = _tensor_shape.num_dimensions() < 1 ? 0 : 4;
    const size_t pad_y       = _tensor_shape.num_dimensions() < 2 ? 0 : 4;

    return extend_padding(PaddingSize(pad_y, pad_x + extra_pad_x, pad_y, pad_x));
}

std::tuple<Strides, size_t, size_t> TensorInfo::calculate_padding_requirements(const PaddingSize &padding)
{
    // Calculate resulting stride for the X, Y and Z dimension
    const size_t stride_x = element_size();
    const size_t stride_y = (padding.left + _tensor_shape[0] + padding.right) * stride_x;
    const size_t stride_z = (padding.top + _tensor_shape[1] + padding.bottom) * stride_y;

    Strides      required_strides;
    size_t       required_total_size           = 0;
    const size_t required_offset_first_element = padding.left * stride_x + padding.top * stride_y;

    switch(_tensor_shape.num_dimensions())
    {
        case 0:
        {
            if(_tensor_shape.total_size() > 0)
            {
                required_strides    = Strides(stride_x, stride_x);
                required_total_size = stride_z;
            }
            break;
        }
        case 1:
            required_strides    = compute_strides(*this, stride_x, stride_y);
            required_total_size = stride_z;
            break;
        case 2:
            required_strides    = compute_strides(*this, stride_x, stride_y);
            required_total_size = stride_z;
            break;
        default:
        {
            required_strides = compute_strides(*this, stride_x, stride_y, stride_z);

            const unsigned int idx_last_dimension = _tensor_shape.num_dimensions() - 1;

            required_total_size = _tensor_shape[idx_last_dimension] * required_strides[idx_last_dimension];
            break;
        }
    }

    return std::make_tuple(required_strides, required_offset_first_element, required_total_size);
}

bool TensorInfo::extend_padding(const PaddingSize &padding)
{
    ARM_COMPUTE_ERROR_ON(!_is_resizable);

    bool updated = false;

    if(padding.top > _padding.top)
    {
        _padding.top = padding.top;
        updated      = true;
    }

    if(padding.right > _padding.right)
    {
        _padding.right = padding.right;
        updated        = true;
    }

    if(padding.bottom > _padding.bottom)
    {
        _padding.bottom = padding.bottom;
        updated         = true;
    }

    if(padding.left > _padding.left)
    {
        _padding.left = padding.left;
        updated       = true;
    }

    std::tie(_strides_in_bytes, _offset_first_element_in_bytes, _total_size) = calculate_padding_requirements(_padding);

    return updated;
}

std::unique_ptr<ITensorInfo> TensorInfo::clone() const
{
    return support::cpp14::make_unique<TensorInfo>(*this);
}

ITensorInfo &TensorInfo::set_data_type(DataType data_type)
{
    _data_type = data_type;
    _format    = Format::UNKNOWN;
    return set_tensor_shape(tensor_shape()); // Force total size and strides to update
}

ITensorInfo &TensorInfo::set_num_channels(int num_channels)
{
    _num_channels = num_channels;
    _format       = Format::UNKNOWN;
    return *this;
}

ITensorInfo &TensorInfo::set_format(Format format)
{
    _format = format;

    if(_data_type == DataType::UNKNOWN)
    {
        _num_channels = num_channels_from_format(format);
        _data_type    = data_type_from_format(format);
    }
    else
    {
        ARM_COMPUTE_ERROR_ON(num_channels_from_format(format) != _num_channels);
        ARM_COMPUTE_ERROR_ON(data_type_from_format(format) != _data_type);
    }
    return *this;
}

ITensorInfo &TensorInfo::set_tensor_shape(const TensorShape &shape)
{
    _tensor_shape                  = shape;
    _offset_first_element_in_bytes = 0;
    _strides_in_bytes              = compute_strides(*this);

    if(_tensor_shape.num_dimensions() == 0)
    {
        _total_size = _strides_in_bytes[0];
    }
    else
    {
        const unsigned int idx_last_dimension = _tensor_shape.num_dimensions() - 1;
        _total_size                           = _tensor_shape[idx_last_dimension] * _strides_in_bytes[idx_last_dimension];
    }

    std::tie(_strides_in_bytes, _offset_first_element_in_bytes, _total_size) = calculate_padding_requirements(_padding);

    _valid_region = ValidRegion{ Coordinates(), _tensor_shape };
    return *this;
}

ITensorInfo &TensorInfo::set_quantization_info(const QuantizationInfo &quantization_info)
{
    _quantization_info = quantization_info;
    return *this;
}

ITensorInfo &TensorInfo::set_data_layout(const DataLayout &data_layout)
{
    _data_layout = data_layout;
    return *this;
}

ITensorInfo &TensorInfo::reset_padding()
{
    _padding = PaddingSize();
    if(((_format != Format::UNKNOWN) || (_data_type != DataType::UNKNOWN)) && _total_size != 0)
    {
        std::tie(_strides_in_bytes, _offset_first_element_in_bytes, _total_size) = calculate_padding_requirements(_padding);
    }
    return *this;
}

int32_t TensorInfo::offset_element_in_bytes(const Coordinates &pos) const
{
    ARM_COMPUTE_ERROR_ON_COORDINATES_DIMENSIONS_GTE(pos, _tensor_shape.num_dimensions());

    int32_t offset = _offset_first_element_in_bytes;

    for(size_t i = 0; i < _tensor_shape.num_dimensions(); ++i)
    {
        offset += pos[i] * _strides_in_bytes[i];
    }

    return offset;
}
