# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# 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.

import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from ppdet.core.workspace import register

from ppdet.modeling.bbox_utils import batch_bbox_overlaps
from ppdet.modeling.transformers import bbox_xyxy_to_cxcywh

__all__ = ['UniformAssigner']


def batch_p_dist(x, y, p=2):
    """
    calculate pairwise p_dist, the first index of x and y are batch
    return [x.shape[0], y.shape[0]]
    """
    x = x.unsqueeze(1)
    diff = x - y
    return paddle.linalg.vector_norm(diff, p=p, axis=list(range(2, diff.dim())))


@register
class UniformAssigner(nn.Layer):
    def __init__(self, pos_ignore_thr, neg_ignore_thr, match_times=4):
        super(UniformAssigner, self).__init__()
        self.pos_ignore_thr = pos_ignore_thr
        self.neg_ignore_thr = neg_ignore_thr
        self.match_times = match_times

    def forward(self, bbox_pred, anchor, gt_bboxes, gt_labels=None):
        num_bboxes = bbox_pred.shape[0]
        num_gts = gt_bboxes.shape[0]
        match_labels = paddle.full([num_bboxes], -1, dtype=paddle.int32)

        pred_ious = batch_bbox_overlaps(bbox_pred, gt_bboxes)
        pred_max_iou = pred_ious.max(axis=1)
        neg_ignore = pred_max_iou > self.neg_ignore_thr
        # exclude potential ignored neg samples first, deal with pos samples later
        #match_labels: -2(ignore), -1(neg) or >=0(pos_inds)
        match_labels = paddle.where(neg_ignore,
                                    paddle.full_like(match_labels, -2),
                                    match_labels)

        bbox_pred_c = bbox_xyxy_to_cxcywh(bbox_pred)
        anchor_c = bbox_xyxy_to_cxcywh(anchor)
        gt_bboxes_c = bbox_xyxy_to_cxcywh(gt_bboxes)
        bbox_pred_dist = batch_p_dist(bbox_pred_c, gt_bboxes_c, p=1)
        anchor_dist = batch_p_dist(anchor_c, gt_bboxes_c, p=1)

        top_pred = bbox_pred_dist.topk(
            k=self.match_times, axis=0, largest=False)[1]
        top_anchor = anchor_dist.topk(
            k=self.match_times, axis=0, largest=False)[1]

        tar_pred = paddle.arange(num_gts).expand([self.match_times, num_gts])
        tar_anchor = paddle.arange(num_gts).expand([self.match_times, num_gts])
        pos_places = paddle.concat([top_pred, top_anchor]).reshape([-1])
        pos_inds = paddle.concat([tar_pred, tar_anchor]).reshape([-1])

        pos_anchor = anchor[pos_places]
        pos_tar_bbox = gt_bboxes[pos_inds]
        pos_ious = batch_bbox_overlaps(
            pos_anchor, pos_tar_bbox, is_aligned=True)
        pos_ignore = pos_ious < self.pos_ignore_thr
        pos_inds = paddle.where(pos_ignore,
                                paddle.full_like(pos_inds, -2), pos_inds)
        match_labels[pos_places] = pos_inds
        match_labels.stop_gradient = True
        pos_keep = ~pos_ignore

        if pos_keep.sum() > 0:
            pos_places_keep = pos_places[pos_keep]
            pos_bbox_pred = bbox_pred[pos_places_keep].reshape([-1, 4])
            pos_bbox_tar = pos_tar_bbox[pos_keep].reshape([-1, 4]).detach()
        else:
            pos_bbox_pred = None
            pos_bbox_tar = None

        return match_labels, pos_bbox_pred, pos_bbox_tar
