# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.
# pylint: disable=invalid-name, too-many-nested-blocks
"Roi align in python"

import math

import numpy as np


def _bilinear(a_np, n, c, y, x, height, width, layout):
    if y < -1 or y > height or x < -1 or x > width:
        return 0

    y = min(max(y, 0), height - 1)
    x = min(max(x, 0), width - 1)

    y_low = math.floor(y)
    x_low = math.floor(x)
    y_high = y_low + 1
    x_high = x_low + 1

    wy_h = y - y_low
    wx_h = x - x_low
    wy_l = 1 - wy_h
    wx_l = 1 - wx_h

    val = 0
    for wx, xp in zip((wx_l, wx_h), (x_low, x_high)):
        for wy, yp in zip((wy_l, wy_h), (y_low, y_high)):
            if 0 <= yp < height and 0 <= xp < width:
                if layout == "NCHW":
                    val += wx * wy * a_np[n, c, yp, xp]
                else:
                    val += wx * wy * a_np[n, yp, xp, c]
    return val


def roi_align_common(
    a_np,
    b_np,
    rois_np,
    channel,
    pooled_size_h,
    pooled_size_w,
    spatial_scale,
    sample_ratio,
    aligned,
    avg_mode,
    max_mode,
    height,
    width,
    layout,
):
    """Common code used by roi align NCHW and NHWC"""
    num_roi = rois_np.shape[0]

    for i in range(num_roi):
        roi = rois_np[i]
        batch_index = int(roi[0])
        roi_start_w, roi_start_h, roi_end_w, roi_end_h = roi[1:] * spatial_scale
        roi_h = roi_end_h - roi_start_h if aligned else max(roi_end_h - roi_start_h, 1.0)
        roi_w = roi_end_w - roi_start_w if aligned else max(roi_end_w - roi_start_w, 1.0)

        bin_h = roi_h / pooled_size_h
        bin_w = roi_w / pooled_size_w

        if sample_ratio > 0:
            roi_bin_grid_h = roi_bin_grid_w = int(sample_ratio)
        else:
            roi_bin_grid_h = math.ceil(roi_h / pooled_size_h)
            roi_bin_grid_w = math.ceil(roi_w / pooled_size_w)

        count = roi_bin_grid_h * roi_bin_grid_w

        for c in range(channel):
            for ph in range(pooled_size_h):
                for pw in range(pooled_size_w):
                    if avg_mode:
                        total = 0.0
                    if max_mode:
                        total = float("-inf")
                    for iy in range(roi_bin_grid_h):
                        for ix in range(roi_bin_grid_w):
                            y = roi_start_h + ph * bin_h + (iy + 0.5) * bin_h / roi_bin_grid_h
                            x = roi_start_w + pw * bin_w + (ix + 0.5) * bin_w / roi_bin_grid_w
                            if avg_mode:
                                total += (
                                    _bilinear(a_np, batch_index, c, y, x, height, width, layout)
                                    / count
                                )
                            if max_mode:
                                total = max(
                                    total,
                                    _bilinear(a_np, batch_index, c, y, x, height, width, layout),
                                )

                    if layout == "NCHW":
                        b_np[i, c, ph, pw] = total
                    else:
                        b_np[i, ph, pw, c] = total
    return b_np


def roi_align_nchw_python(
    a_np, rois_np, pooled_size, spatial_scale, sample_ratio, mode=b"avg", aligned=False
):
    """Roi align NCHW in python"""
    avg_mode = mode in (b"avg", "avg", 0)
    max_mode = mode in (b"max", "max", 1)
    assert avg_mode or max_mode, "Mode must be average or max. Please pass a valid mode."
    _, channel, height, width = a_np.shape
    if isinstance(pooled_size, int):
        pooled_size_h = pooled_size_w = pooled_size
    else:
        pooled_size_h, pooled_size_w = pooled_size

    b_np = np.zeros((rois_np.shape[0], channel, pooled_size_h, pooled_size_w), dtype=a_np.dtype)

    return roi_align_common(
        a_np,
        b_np,
        rois_np,
        channel,
        pooled_size_h,
        pooled_size_w,
        spatial_scale,
        sample_ratio,
        aligned,
        avg_mode,
        max_mode,
        height,
        width,
        "NCHW",
    )


def roi_align_nhwc_python(
    a_np, rois_np, pooled_size, spatial_scale, sample_ratio, mode=b"avg", aligned=False
):
    """Roi align NHWC in python"""
    avg_mode = mode in (b"avg", "avg", 0)
    max_mode = mode in (b"max", "max", 1)
    assert avg_mode or max_mode, "Mode must be average or max. Please pass a valid mode."
    _, height, width, channel = a_np.shape
    num_roi = rois_np.shape[0]

    if isinstance(pooled_size, int):
        pooled_size_h = pooled_size_w = pooled_size
    else:
        pooled_size_h, pooled_size_w = pooled_size

    b_np = np.zeros((num_roi, pooled_size_h, pooled_size_w, channel), dtype=a_np.dtype)

    return roi_align_common(
        a_np,
        b_np,
        rois_np,
        channel,
        pooled_size_h,
        pooled_size_w,
        spatial_scale,
        sample_ratio,
        aligned,
        avg_mode,
        max_mode,
        height,
        width,
        "NHWC",
    )
