#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
import torch
from maskrcnn_benchmark.layers import nms as _box_nms

from .bounding_box import BoxList
from maskrcnn_benchmark.structures.segmentation_mask import SegmentationMask
import numpy as np
import shapely
from shapely.geometry import Polygon,MultiPoint

def boxlist_nms(boxlist, nms_thresh, max_proposals=-1, score_field="score"):
    """
    Performs non-maximum suppression on a boxlist, with scores specified
    in a boxlist field via score_field.

    Arguments:
        boxlist(BoxList)
        nms_thresh (float)
        max_proposals (int): if > 0, then only the top max_proposals are kept
            after non-maxium suppression
        score_field (str)
    """
    if nms_thresh <= 0:
        return boxlist
    mode = boxlist.mode
    boxlist = boxlist.convert("xyxy")
    boxes = boxlist.bbox
    score = boxlist.get_field(score_field)
    keep = _box_nms(boxes, score, nms_thresh)
    if max_proposals > 0:
        keep = keep[:max_proposals]
    boxlist = boxlist[keep]
    return boxlist.convert(mode)


def remove_small_boxes(boxlist, min_size):
    """
    Only keep boxes with both sides >= min_size

    Arguments:
        boxlist (Boxlist)
        min_size (int)
    """
    # TODO maybe add an API for querying the ws / hs
    xywh_boxes = boxlist.convert("xywh").bbox
    _, _, ws, hs = xywh_boxes.unbind(dim=1)
    keep = ((ws >= min_size) & (hs >= min_size)).nonzero().squeeze(1)
    return boxlist[keep]


# implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py
# with slight modifications
def boxlist_iou(boxlist1, boxlist2):
    """Compute the intersection over union of two set of boxes.
    The box order must be (xmin, ymin, xmax, ymax).

    Arguments:
      box1: (BoxList) bounding boxes, sized [N,4].
      box2: (BoxList) bounding boxes, sized [M,4].

    Returns:
      (tensor) iou, sized [N,M].

    Reference:
      https://github.com/chainer/chainercv/blob/master/chainercv/utils/bbox/bbox_iou.py
    """
    if boxlist1.size != boxlist2.size:
        raise RuntimeError(
            "boxlists should have same image size, got {}, {}".format(
                boxlist1, boxlist2
            )
        )

    # N = len(boxlist1)
    # M = len(boxlist2)

    area1 = boxlist1.area()
    area2 = boxlist2.area()

    box1, box2 = boxlist1.bbox, boxlist2.bbox

    lt = torch.max(box1[:, None, :2], box2[:, :2])  # [N,M,2]
    rb = torch.min(box1[:, None, 2:], box2[:, 2:])  # [N,M,2]

    TO_REMOVE = 1

    wh = (rb - lt + TO_REMOVE).clamp(min=0)  # [N,M,2]
    inter = wh[:, :, 0] * wh[:, :, 1]  # [N,M]

    iou = inter / (area1[:, None] + area2 - inter)
    return iou

# def boxlist_polygon_iou(target, proposal):
#     """Compute the intersection over union of two set of boxes.
#     The box order must be (xmin, ymin, xmax, ymax).

#     Arguments:
#       box1: (BoxList) bounding boxes, sized [N,4].
#       box2: (BoxList) bounding boxes, sized [M,4].

#     Returns:
#       (tensor) iou, sized [N,M].

#     Reference:
#       https://github.com/chainer/chainercv/blob/master/chainercv/utils/bbox/bbox_iou.py
#     """
#     if target.size != proposal.size:
#         raise RuntimeError(
#             "boxlists should have same image size, got {}, {}".format(
#                 target, proposal
#             )
#         )
#     target_polygon = target.get_field("masks").to_np_polygon()
#     proposal_polygon = proposal.get_field("masks").to_np_polygon()
#     print(target_polygon)
#     print(proposal_polygon)
#     polygon_points1 = target_polygon[0].reshape(-1, 2)
#     poly1 = Polygon(polygon_points1).convex_hull
#     polygon_points2 = proposal_polygon[0].reshape(-1, 2)
#     poly2 = Polygon(polygon_points2).convex_hull
#     union_poly = np.concatenate((polygon_points1, polygon_points2))
#     if not poly1.intersects(poly2): # this test is fast and can accelerate calculation
#         iou = 0
#     else:
#         try:
#             inter_area = poly1.intersection(poly2).area
#             #union_area = poly1.area + poly2.area - inter_area
#             union_area = MultiPoint(union_poly).convex_hull.area
#             if union_area == 0:
#                 return 0
#             iou = float(inter_area) / union_area
#         except shapely.geos.TopologicalError:
#             print('shapely.geos.TopologicalError occured, iou set to 0')
#             iou = 0
#     return iou


# TODO redundant, remove
def _cat(tensors, dim=0):
    """
    Efficient version of torch.cat
    avoids a copy if there is only a single element in a list
    """
    assert isinstance(tensors, (list, tuple))
    if len(tensors) == 1:
        return tensors[0]
    return torch.cat(tensors, dim)

def _cat_mask(masks):
    polygons_cat = []
    size = masks[0].size
    for mask in masks:
        polygons = mask.get_polygons()
        polygons_cat.extend(polygons)
    masks_cat = SegmentationMask(polygons_cat, size)
    return masks_cat


def cat_boxlist(bboxes):
    """
    Concatenates a list of BoxList (having the same image size) into a
    single BoxList

    Arguments:
        bboxes (list[BoxList])
    """
    # if bboxes is None:
    #     return None
    # if bboxes[0] is None:
    #     bboxes = [bboxes[1]
    assert isinstance(bboxes, (list, tuple))
    assert all(isinstance(bbox, BoxList) for bbox in bboxes)

    size = bboxes[0].size
    assert all(bbox.size == size for bbox in bboxes)

    mode = bboxes[0].mode
    assert all(bbox.mode == mode for bbox in bboxes)

    fields = set(bboxes[0].fields())
    assert all(set(bbox.fields()) == fields for bbox in bboxes)

    cat_boxes = BoxList(_cat([bbox.bbox for bbox in bboxes], dim=0), size, mode)

    for field in fields:
        if field == 'masks':
            data = _cat_mask([bbox.get_field(field) for bbox in bboxes])
        else:
            data = _cat([bbox.get_field(field) for bbox in bboxes], dim=0)
        cat_boxes.add_field(field, data)

    return cat_boxes


def cat_boxlist_gt(bboxes):
    """
    Concatenates a list of BoxList (having the same image size) into a
    single BoxList

    Arguments:
        bboxes (list[BoxList])
    """
    assert isinstance(bboxes, (list, tuple))
    assert all(isinstance(bbox, BoxList) for bbox in bboxes)

    size = bboxes[0].size
    # bboxes[1].set_size(size)
    assert all(bbox.size == size for bbox in bboxes)

    mode = bboxes[0].mode
    assert all(bbox.mode == mode for bbox in bboxes)

    fields = set(bboxes[0].fields())
    assert all(set(bbox.fields()) == fields for bbox in bboxes)
    if bboxes[0].bbox.sum().item() == 0:
        cat_boxes = BoxList(bboxes[1].bbox, size, mode)
    else:
        cat_boxes = BoxList(_cat([bbox.bbox for bbox in bboxes], dim=0), size, mode)

    for field in fields:
        if bboxes[0].bbox.sum().item() == 0:
            if field == 'masks':
                data = _cat_mask([bbox.get_field(field) for bbox in bboxes[1:]])
            else:
                data = _cat([bbox.get_field(field) for bbox in bboxes[1:]], dim=0)
        else:
            if field == 'masks':
                data = _cat_mask([bbox.get_field(field) for bbox in bboxes])
            else:
                data = _cat([bbox.get_field(field) for bbox in bboxes], dim=0)
        cat_boxes.add_field(field, data)

    return cat_boxes
