###########################################################################################
#                                                                                         #
# Evaluator class: Implements the most popular metrics for object detection               #
#                                                                                         #
# Developed by: Rafael Padilla (rafael.padilla@smt.ufrj.br)                               #
#        SMT - Signal Multimedia and Telecommunications Lab                               #
#        COPPE - Universidade Federal do Rio de Janeiro                                   #
#        Last modification: Oct 9th 2018                                                 #
###########################################################################################

import csv
import enum
import imp
import shutil
import json
from pprint import pprint as pp
from .utils import *
from .BoundingBoxes import *
from .BoundingBox import *
import numpy as np
import os
import sys
from collections import Counter
import matplotlib.pyplot as plt
plt.switch_backend('agg')


# os.environ['debug_mode'] = 'true'


def debug_print(*args):
    if 'debug_mode' in os.environ:
        print(*args)


class Evaluator:
    def analyze(
            self,
            boundingboxes,
            IOUThreshold=0.5,
            scoreThreshold=0.5,
            savePath='/results'):
        """Get the metrics used by the VOC Pascal 2012 challenge.
        Get
        Args:
            boundingboxes: Object of the class BoundingBoxes representing ground truth and detected
            bounding boxes;
            IOUThreshold: IOU threshold indicating which detections will be considered TP or FP
            (default value = 0.5);
            method (default = EveryPointInterpolation): It can be calculated as the implementation
            in the official PASCAL VOC toolkit (EveryPointInterpolation), or applying the 11-point
            interpolatio as described in the paper "The PASCAL Visual Object Classes(VOC) Challenge"
            or EveryPointInterpolation"  (ElevenPointInterpolation);
        Returns:
            A list of dictionaries. Each dictionary contains information and metrics of each class.
            The keys of each dictionary are:
            dict['class']: class representing the current dictionary;
            dict['precision']: array with the precision values;
            dict['recall']: array with the recall values;
            dict['AP']: average precision;
            dict['interpolated precision']: interpolated precision values;
            dict['interpolated recall']: interpolated recall values;
            dict['total positives']: total number of ground truth positives;
            dict['total TP']: total number of True Positive detections;
            dict['total FP']: total number of False Positive detections;
        """
        if not savePath:
            savePath = '/results'
        os.makedirs(savePath, exist_ok=True)

        img_dict = {}

        # Loop through all bounding boxes and separate them into GTs and detections
        for bb in boundingboxes.getBoundingBoxes():
            # [imageName, class, confidence, (bb coordinates XYX2Y2)]
            img_name = bb.getImageName()
            if img_name not in img_dict:
                img_dict[img_name] = {}
                img_dict[img_name]['gt'] = []
                img_dict[img_name]['det'] = []
            if bb.getBBType() == BBType.GroundTruth:
                img_dict[img_name]['gt'].append(
                    [bb.getClassId(), 1, bb.getAbsoluteBoundingBox(BBFormat.XYX2Y2), bb.getIndex()])
            else:
                img_dict[img_name]['det'].append(
                    [bb.getClassId(), bb.getConfidence(), bb.getAbsoluteBoundingBox(BBFormat.XYX2Y2), bb.getIndex()])

        det_path = os.path.join(savePath, 'det.csv')
        gt_path = os.path.join(savePath, 'gt.csv')
        with open(det_path, 'w', encoding='utf-8', newline='') as det_file:
            det_csv = csv.writer(det_file)
            det_csv.writerow(
                ['检测框id', '类型', '框大小', '框面积', '置信度', '与同类别目标框的最大IOU'])
        with open(gt_path, 'w', encoding='utf-8', newline='') as gt_file:
            gt_csv = csv.writer(gt_file)
            gt_csv.writerow(['目标框id', '类型', '框大小', '框面积',
                             '与同类别检测框的最大IOU', '该检测框的置信度'])
        for img_path, info in img_dict.items():
            img_name = os.path.basename(img_path)
            dets = info['det']
            gts = info['gt']

            def max_iou(box_info, t_boxes_info):
                iouMax = 0
                conf = 0
                target_box = []
                for i in range(len(t_boxes_info)):
                    iou = Evaluator.iou(box_info[2], t_boxes_info[i][2])
                    if iou > iouMax:
                        iouMax = iou
                        target_box = t_boxes_info[i][2]
                        conf = t_boxes_info[i][1]
                return iouMax, conf, target_box

            # 打印dets信息
            with open(det_path, 'a+', encoding='utf-8', newline='') as det_file:
                det_csv = csv.writer(det_file)
                for idx, det in enumerate(dets):
                    id = '{}_{}'.format(img_name, idx)
                    typ = det[0]
                    conf = det[1]
                    x1, y1, x2, y2 = det[2]
                    h = y2-y1
                    w = x2-x1
                    w_h = '{}*{}'.format(int(w), int(h))
                    area = w*h
                    iou_max, _, _ = max_iou(
                        det, [gt for gt in gts if gt[0] == det[0]])
                    line = [id, typ, w_h, area, round(
                        conf, 3), round(iou_max, 3)]
                    det_csv.writerow(line)

            # 打印gts信息
            with open(gt_path, 'a+', encoding='utf-8', newline='') as gt_file:
                gt_csv = csv.writer(gt_file)
                for idx, gt in enumerate(gts):
                    id = '{}_{}'.format(img_name, idx)
                    typ = gt[0]
                    x1, y1, x2, y2 = gt[2]
                    h = y2-y1
                    w = x2-x1
                    w_h = '{}*{}'.format(int(w), int(h))
                    area = w*h
                    iou_max, conf, _ = max_iou(
                        gt, [det for det in dets if gt[0] == det[0]])
                    line = [id, typ, w_h, area, round(
                        iou_max, 3), round(conf, 3)]
                    gt_csv.writerow(line)
        print('det_csv under {}'.format(det_path))
        print('gt_csv under {}'.format(gt_path))
        return

    # For each detections, calculate IOU with reference
    @staticmethod
    def _getAllIOUs(reference, detections):
        ret = []
        bbReference = reference.getAbsoluteBoundingBox(BBFormat.XYX2Y2)
        # img = np.zeros((200,200,3), np.uint8)
        for d in detections:
            bb = d.getAbsoluteBoundingBox(BBFormat.XYX2Y2)
            iou = Evaluator.iou(bbReference, bb)
            # Show blank image with the bounding boxes
            # img = add_bb_into_image(img, d, color=(255,0,0), thickness=2, label=None)
            # img = add_bb_into_image(img, reference, color=(0,255,0), thickness=2, label=None)
            ret.append((iou, reference, d))  # iou, reference, detection
        # cv2.imshow("comparing",img)
        # cv2.waitKey(0)
        # cv2.destroyWindow("comparing")
        # sort by iou (from highest to lowest)
        return sorted(ret, key=lambda i: i[0], reverse=True)

    @staticmethod
    def iou(boxA, boxB):
        # if boxes dont intersect
        if Evaluator._boxesIntersect(boxA, boxB) is False:
            return 0
        interArea = Evaluator._getIntersectionArea(boxA, boxB)
        union = Evaluator._getUnionAreas(boxA, boxB, interArea=interArea)
        # intersection over union
        iou = interArea / union
        assert iou >= 0
        return iou

    # boxA = (Ax1,Ay1,Ax2,Ay2)
    # boxB = (Bx1,By1,Bx2,By2)
    @staticmethod
    def _boxesIntersect(boxA, boxB):
        if boxA[0] > boxB[2]:
            return False  # boxA is right of boxB
        if boxB[0] > boxA[2]:
            return False  # boxA is left of boxB
        if boxA[3] < boxB[1]:
            return False  # boxA is above boxB
        if boxA[1] > boxB[3]:
            return False  # boxA is below boxB
        return True

    @staticmethod
    def _getIntersectionArea(boxA, boxB):
        xA = max(boxA[0], boxB[0])
        yA = max(boxA[1], boxB[1])
        xB = min(boxA[2], boxB[2])
        yB = min(boxA[3], boxB[3])
        # intersection area
        return (xB - xA + 1) * (yB - yA + 1)

    @staticmethod
    def _getUnionAreas(boxA, boxB, interArea=None):
        area_A = Evaluator._getArea(boxA)
        area_B = Evaluator._getArea(boxB)
        if interArea is None:
            interArea = Evaluator._getIntersectionArea(boxA, boxB)
        return float(area_A + area_B - interArea)

    @staticmethod
    def _getArea(box):
        return (box[2] - box[0] + 1) * (box[3] - box[1] + 1)
