from __future__ import division
import os
import time
import cv2
import numpy as np
import sys
import pickle
from keras import backend as K
from keras.layers import Input
from keras.models import Model
from keras_frcnn import roi_helpers
import keras_frcnn.vgg as nn

sys.setrecursionlimit(40000)

##########################################################################
#### Aux functions - copied as is from test_frcnn.py
##########################################################################
def format_img_size(img, C):
    """ formats the image size based on config """
    img_min_side = float(C.im_size)
    (height, width, _) = img.shape

    if width <= height:
        ratio = img_min_side / width
        new_height = int(ratio * height)
        new_width = int(img_min_side)
    else:
        ratio = img_min_side / height
        new_width = int(ratio * width)
        new_height = int(img_min_side)
    img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_CUBIC)
    return img, ratio


def format_img_channels(img, C):
    """ formats the image channels based on config """
    img = img[:, :, (2, 1, 0)]
    img = img.astype(np.float32)
    img[:, :, 0] -= C.img_channel_mean[0]
    img[:, :, 1] -= C.img_channel_mean[1]
    img[:, :, 2] -= C.img_channel_mean[2]
    img /= C.img_scaling_factor
    img = np.transpose(img, (2, 0, 1))
    img = np.expand_dims(img, axis=0)
    return img


def format_img(img, C):
    """ formats an image for model prediction based on config """
    img, ratio = format_img_size(img, C)
    img = format_img_channels(img, C)
    return img, ratio


# Method to transform the coordinates of the bounding box to its original size
def get_real_coordinates(ratio, x1, y1, x2, y2):
    real_x1 = int(round(x1 // ratio))
    real_y1 = int(round(y1 // ratio))
    real_x2 = int(round(x2 // ratio))
    real_y2 = int(round(y2 // ratio))

    return (real_x1, real_y1, real_x2, real_y2)
##########################################################################
##########################################################################
##########################################################################

##########################################################################
## TAMIR : Start of my functions
##########################################################################


num_features = None
class_mapping = None
class_to_color = None
# Number of ROIs per iteration. Higher means more memory use
num_rois = 512


def load_model(config_path, model_path):
    """
    config_path : config pickle file
    weigths_path : model.h5 file
    """
    for f in [config_path, model_path]:
        if not os.path.isfile(f):
            print("cant find {}".format(f))
            assert 0

    # Load model config file
    with open(config_path, 'rb') as f_in:
        C = pickle.load(f_in)
    # Turn of augmentation during test time
    C.use_horizontal_flips = False
    C.use_vertical_flips = False
    C.rot_90 = False

    if C.network == 'resnet50':
        assert 0,"Does not support resnet50 network"
    elif C.network == 'vgg':
        print("Using VGG16 as faster-rcnn base network")
        global num_features
        num_features = 512

    # Define model path & lodel base model (without weights)
    C.model_path = model_path
    print('model path is {}'.format(C.model_path))

    global class_mapping
    global class_to_color
    class_mapping = C.class_mapping
    if 'bg' not in class_mapping:
        class_mapping['bg'] = len(class_mapping)
    class_mapping = {v: k for k, v in class_mapping.items()}
    print(class_mapping)
    class_to_color = {class_mapping[v]: np.random.randint(0, 255, 3) for v in class_mapping}
    global num_rois
    C.num_rois = int(num_rois)

    if K.image_dim_ordering() == 'th':
        input_shape_img = (3, None, None)
        input_shape_features = (num_features, None, None)
    else:
        input_shape_img = (None, None, 3)
        input_shape_features = (None, None, num_features)

    img_input = Input(shape=input_shape_img)
    roi_input = Input(shape=(C.num_rois, 4))
    feature_map_input = Input(shape=input_shape_features)

    # define the base network (resnet here, can be VGG, Inception, etc)
    shared_layers = nn.nn_base(img_input, trainable=True)

    # define the RPN, built on the base layers
    num_anchors = len(C.anchor_box_scales) * len(C.anchor_box_ratios)
    rpn_layers = nn.rpn(shared_layers, num_anchors)
    model_rpn = Model(img_input, rpn_layers)

    classifier = nn.classifier(feature_map_input, roi_input, C.num_rois, nb_classes=len(class_mapping), trainable=True)
    model_classifier = Model([feature_map_input, roi_input], classifier)

    print('Loading model weights from {}'.format(C.model_path))
    model_rpn.load_weights(C.model_path, by_name=True)
    model_classifier.load_weights(C.model_path, by_name=True)
    model_rpn.compile(optimizer='sgd', loss='mse')
    model_classifier.compile(optimizer='sgd', loss='mse')
    print("Done loading model")

    model_dict= {
        'C': C,
        'model_rpn': model_rpn,
        'model_classifier': model_classifier,
        'class_to_color' : class_to_color,
    }

    return model_dict
# End of load_model


def predit_images(img_names, img_base_path, model_config, model_path, bbox_threshold=0.8, visualise=False):
    """
    img_path_list : list of full paths of images to perform predict on
    bbox_threshold : a region with P(class) below this value will be discarded
    """

    # Sanity check - check all images exists
    for img in img_names:
        if not os.path.isfile(img_base_path + '/' + img):
            print("cant locate {}".format(img))
            assert 0

    # load model
    model_dict = load_model(model_config, model_path)
    C = model_dict['C']
    model_rpn = model_dict['model_rpn']
    model_classifier = model_dict['model_classifier']

    # result dict
    results = {}

    # Iterate over each image - do predictions
    for idx, img_name in enumerate(sorted(img_names)):
        if not img_name.lower().endswith(('.bmp', '.jpeg', '.jpg', '.png', '.tif', '.tiff')):
            continue
        print(img_name)
        st = time.time()
        filepath = os.path.join(img_base_path, img_name)
        img = cv2.imread(filepath)
        orig_img = img.copy()

        if visualise:
            img_for_visualize = img.copy()

        X, ratio = format_img(img, C)

        if K.image_dim_ordering() == 'tf':
            X = np.transpose(X, (0, 2, 3, 1))

        # get the feature maps and output from the RPN
        [Y1, Y2, F] = model_rpn.predict(X)

        R = roi_helpers.rpn_to_roi(Y1, Y2, C, K.image_dim_ordering(), overlap_thresh=0.7)

        # convert from (x1,y1,x2,y2) to (x,y,w,h)
        R[:, 2] -= R[:, 0]
        R[:, 3] -= R[:, 1]

        # apply the spatial pyramid pooling to the proposed regions
        bboxes = {}
        probs = {}

        for jk in range(R.shape[0] // C.num_rois + 1):
            ROIs = np.expand_dims(R[C.num_rois * jk:C.num_rois * (jk + 1), :], axis=0)
            if ROIs.shape[1] == 0:
                break

            if jk == R.shape[0] // C.num_rois:
                # pad R
                curr_shape = ROIs.shape
                target_shape = (curr_shape[0], C.num_rois, curr_shape[2])
                ROIs_padded = np.zeros(target_shape).astype(ROIs.dtype)
                ROIs_padded[:, :curr_shape[1], :] = ROIs
                ROIs_padded[0, curr_shape[1]:, :] = ROIs[0, 0, :]
                ROIs = ROIs_padded

            [P_cls, P_regr] = model_classifier.predict([F, ROIs])

            for ii in range(P_cls.shape[1]):

                if np.max(P_cls[0, ii, :]) < bbox_threshold or np.argmax(P_cls[0, ii, :]) == (P_cls.shape[2] - 1):
                    continue

                cls_name = class_mapping[np.argmax(P_cls[0, ii, :])]

                if cls_name not in bboxes:
                    bboxes[cls_name] = []
                    probs[cls_name] = []

                (x, y, w, h) = ROIs[0, ii, :]

                cls_num = np.argmax(P_cls[0, ii, :])
                try:
                    (tx, ty, tw, th) = P_regr[0, ii, 4 * cls_num:4 * (cls_num + 1)]
                    tx /= C.classifier_regr_std[0]
                    ty /= C.classifier_regr_std[1]
                    tw /= C.classifier_regr_std[2]
                    th /= C.classifier_regr_std[3]
                    x, y, w, h = roi_helpers.apply_regr(x, y, w, h, tx, ty, tw, th)
                except:
                    pass
                bboxes[cls_name].append(
                    [C.rpn_stride * x, C.rpn_stride * y, C.rpn_stride * (x + w), C.rpn_stride * (y + h)])
                probs[cls_name].append(np.max(P_cls[0, ii, :]))

        all_dets = []

        results[img_name] = {
            'bboxes': [],
            'image': None,
        }

        for key in bboxes:
            bbox = np.array(bboxes[key])

            new_boxes, new_probs = roi_helpers.non_max_suppression_fast(bbox, np.array(probs[key]), overlap_thresh=0.5)
            for jk in range(new_boxes.shape[0]):
                (x1, y1, x2, y2) = new_boxes[jk, :]

                (real_x1, real_y1, real_x2, real_y2) = get_real_coordinates(ratio, x1, y1, x2, y2)

                if results[img_name]['image'] is None:
                    results[img_name]['image'] = orig_img

                results[img_name]['bboxes'].append(
                    {
                        'x_min': real_x1,
                        'y_min': real_y1,
                        'x_max': real_x2,
                        'y_max': real_y2,
                        'prob': new_probs[jk],
                    }
                )

                if visualise:
                    print("visualize : {}".format(str((real_x1, real_y1, real_x2, real_y2))))

                    cv2.rectangle(img_for_visualize, (real_x1, real_y1), (real_x2, real_y2),
                                  (int(class_to_color[key][0]), int(class_to_color[key][1]), int(class_to_color[key][2])),
                                  thickness=10)

                    textLabel = '{}: {}'.format(key, int(100 * new_probs[jk]))
                    all_dets.append((key, 100 * new_probs[jk]))

                    (retval, baseLine) = cv2.getTextSize(textLabel, cv2.FONT_HERSHEY_COMPLEX, 1, 1)
                    textOrg = (real_x1, real_y1 - 0)

                    cv2.rectangle(img_for_visualize, (textOrg[0] - 5, textOrg[1] + baseLine - 5),
                                  (textOrg[0] + retval[0] + 5, textOrg[1] - retval[1] - 5), (0, 0, 0), 2)
                    cv2.rectangle(img_for_visualize, (textOrg[0] - 5, textOrg[1] + baseLine - 5),
                                  (textOrg[0] + retval[0] + 5, textOrg[1] - retval[1] - 5), (255, 255, 255), -1)
                    cv2.putText(img_for_visualize, textLabel, textOrg, cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0, 0), 1)

        if visualise:
            from matplotlib import pyplot as plt
            print('Elapsed time = {}'.format(time.time() - st))
            print(all_dets)
            plt.figure()  # figure starts from 1 ...
            plt.imshow(cv2.cvtColor(img_for_visualize, cv2.COLOR_BGR2RGB))
            plt.show()

    print("faster_rcnn DONE")
    return results


def main():

    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-m", "--models", dest="models", help="Path to model.")
    parser.add_option("-p", "--test", dest="test_path", help="test path.")
    (options, args) = parser.parse_args()

    img_base_path = options.test_path
    img_names = []
    for img in os.listdir(img_base_path):
        img_names.append(img)
    print("Images to predict {}".format(str(img_names)))

    model_config = os.path.join(options.models, 'config.pickle')
    model_path = os.path.join(options.models, 'model_frcnn.hdf5')

    predit_images(img_names, img_base_path, model_config, model_path, visualise=True)


if __name__ == "__main__":
    main()
