# -*- coding: utf-8 -*-
"""
Created on Tue Nov  7 13:27:04 2017

@author: hhofmann
"""

from __future__ import print_function

import os
import sys
import pickle
import numpy as np

import tensorflow as tf

this_folder =  os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, this_folder+ '/../dataPreprocessing/OnILSVRCdata/')

def batchnorm(input_tensor):
    with tf.name_scope("batch_norm"):
        input_depth = input_tensor.get_shape().as_list()[-1]#takes the last element which is in this case 64
    #make new mean and new Variance
        with tf.name_scope("beta"):
            beta = tf.Variable(tf.constant(0.0,shape=[input_depth],dtype=tf.float32), name="beta",trainable=True)
        with tf.name_scope("gamma"):
            gamma = tf.Variable(tf.constant(1.0,shape=[input_depth],dtype=tf.float32),name="gamma",trainable=True)
        batch_mean, batch_variance = tf.nn.moments(x=input_tensor,axes=[0,1,2])
        return tf.nn.batch_normalization(x=input_tensor,
                                             mean=batch_mean,
                                             variance=batch_variance,
                                             offset=beta,
                                             scale=gamma,
                                             variance_epsilon=1e-4,
                                             name=None)

def convLayer(tensor,layerNr,batchSize, filterwidth, inputdepth, outputdepth, strides, batchnorm_=True, dropout_=True,training=False):
    '''
    tensor:     "Tensor" Input-Tensor 4 dimensional [batches,width,height,depth] (Width and height could also be changed with each other)
    layerNr:    "Scalar" The number of the Layer in the whole context
    filterwidth:"Scalar" the width and the height of the convolutional filtermask
    inputdepth: "Scalar" The depth of the input
    outputdepth:"Scalar" The depth of the output (also the depth of the convolutional Filtermask)
    strides:    "Scalar" The number of Elements overhopped by learning in height and with
    
    returns:    "Tensor" 4 Dimensional Tensor with shapes []
    '''
    with tf.name_scope(str(layerNr)+"_conv_Layer") as scope:
        with tf.name_scope("W"):
            W = tf.Variable(tf.truncated_normal(shape=[filterwidth,filterwidth,inputdepth,outputdepth], stddev=0.01, dtype=tf.float32))
        preactivate = tf.nn.conv2d(input=tensor,filter=W,strides=[1,strides,strides,1],padding='SAME')
        if batchnorm_ == True:
            preactivate = batchnorm(input_tensor=preactivate)
        if dropout_ == True:
            #dropout only over all the feature-maps and batches.
            preactivate = tf.cond(training,
                                  lambda:tf.nn.dropout(x=preactivate, keep_prob=0.9,noise_shape=[batchSize,1,1,outputdepth]),
                                  lambda:preactivate)
        with tf.name_scope("relu"):
            tensor = tf.maximum(0*preactivate,preactivate)
        with tf.name_scope("summary"):
            variable_summaries(variable=W,name="W"+str(layerNr))
            #variable_summaries(variable=b,name="b")
            variable_summaries(variable=preactivate, name="preactivate"+str(layerNr))
        return tensor
        
def variable_summaries(variable, name=" "):
    '''
    variable:   "Tensor" Tensor, which shall be displayed in the tensorboard
    name:       "String" is the name, with which the Graphics in the Board shall be named
    Reference:
    Jonas Schmid hand_detector.py
    '''
    variable = tf.cast(variable, tf.float32)
    #check for nan's
    variable = tf.reshape(variable, [-1])
    nr_of_infinite_elements = tf.reduce_sum(tf.boolean_mask(tf.ones(tf.shape(variable)),tf.logical_not(tf.is_finite(variable))))
    tf.summary.scalar(name+"nrOfInfElem", nr_of_infinite_elements)
    variable = tf.boolean_mask(variable,tf.is_finite(variable) )
    

    mean = tf.reduce_mean(variable)
    tf.summary.scalar('mean_'+name, mean)
    with tf.name_scope('stddev'):
        stddev = tf.sqrt(tf.reduce_mean(tf.square(variable - mean)))
    tf.summary.scalar('stddev_'+name, stddev)
    tf.summary.scalar('max_'+name, tf.reduce_max(variable))
    tf.summary.scalar('min_'+name, tf.reduce_min(variable))
    tf.summary.histogram('histogram_'+name, variable)

def normalize_pictures(tensor):
    mean,var = tf.nn.moments(x=tensor, axes=[1,2],keep_dims=True)
    tensor = tf.subtract(tensor , mean)
    var = var + 1e-4
    tensor = tf.div(tensor,var)
    return tensor
    
def batchnormPretrained(input_tensor,layerNr,origin_path):
    with tf.name_scope("batch_norm"):
    #make new mean and new Variance
        with tf.name_scope("beta"):
            pythonbeta = pickle.load( open( origin_path + "../../../weights/pythonWeights/"+str(layerNr)+"_conv_Layer_batch_norm_beta_beta.pkl", "rb" ) )
            beta = tf.Variable(pythonbeta, name="beta",trainable=True,dtype=tf.float32)
        with tf.name_scope("gamma"):
            pythongamma = pickle.load( open( origin_path + "../../../weights/pythonWeights/"+str(layerNr)+"_conv_Layer_batch_norm_gamma_gamma.pkl", "rb" ) )
            gamma = tf.Variable(pythongamma,name="gamma",trainable=True,dtype=tf.float32)
        batch_mean, batch_variance = tf.nn.moments(x=input_tensor,axes=[0,1,2])
        return tf.nn.batch_normalization(x=input_tensor,
                                             mean=batch_mean,
                                             variance=batch_variance,
                                             offset=beta,
                                             scale=gamma,
                                             variance_epsilon=1e-4,
                                             name=None)

def convLayerPretrained(tensor,layerNr,batchSize, filterwidth, inputdepth, outputdepth, strides, origin_path, batchnorm_=True, dropout_=True,training=False):
    '''
    tensor:     "Tensor" Input-Tensor 4 dimensional [batches,width,height,depth] (Width and height could also be changed with each other)
    layerNr:    "Scalar" The number of the Layer in the whole context
    filterwidth:"Scalar" the width and the height of the convolutional filtermask
    inputdepth: "Scalar" The depth of the input
    outputdepth:"Scalar" The depth of the output (also the depth of the convolutional Filtermask)
    strides:    "Scalar" The number of Elements overhopped by learning in height and with
    
    returns:    "Tensor" 4 Dimensional Tensor with shapes []
    '''
    with tf.name_scope(str(layerNr)+"_conv_Layer") as scope:
        with tf.name_scope("W"):
            pythonW = pickle.load( open( origin_path + "../../../weights/pythonWeights/"+str(layerNr)+"_conv_Layer_W_Variable.pkl", "rb" ) )
            W = tf.Variable(pythonW,dtype=tf.float32)
        preactivate = tf.nn.conv2d(input=tensor,filter=W,strides=[1,strides,strides,1],padding='SAME')        
        if batchnorm_ == True:
            preactivate = batchnormPretrained(input_tensor=preactivate,layerNr=layerNr,origin_path=origin_path)
        if dropout_ == True:
            #dropout only over all the feature-maps and batches.
            preactivate = tf.cond(training,
                                  lambda:tf.nn.dropout(x=preactivate, keep_prob=0.8,noise_shape=[batchSize,1,1,outputdepth]),
                                  lambda:preactivate)
        with tf.name_scope("relu"):
            tensor = tf.maximum(0*preactivate,preactivate)
        with tf.name_scope("summary"):
            variable_summaries(variable=W,name="W"+str(layerNr))
            #variable_summaries(variable=b,name="b")
            variable_summaries(variable=preactivate, name="preactivate"+str(layerNr))
        return tensor
        
        
def iou(x_label,y_label,h_label,w_label,y,x,h,w):
    
    label_left = tf.subtract(x_label,(w_label/2))
    label_right = tf.add(x_label,(w_label/2))
    label_top = tf.subtract(y_label,(h_label/2))
    label_bottom = tf.add(y_label,(h_label/2))
    
    left = tf.subtract(x,(w/2))
    right = tf.add(x,(w/2))
    top = tf.subtract(y,(h/2))
    bottom = tf.add(y,(h/2))
    
    overlap_left = tf.maximum(label_left,left)
    overlap_right = tf.minimum(label_right,right)
    overlap_top = tf.maximum(label_top,top)
    overlap_bottom = tf.minimum(label_bottom,bottom)
    
    overlap_width = tf.subtract(overlap_right,overlap_left)
    overlap_width = tf.maximum(overlap_width,np.float32(0))
    overlap_height = tf.subtract(overlap_bottom,overlap_top)
    overlap_height = tf.maximum(overlap_height,np.float32(0))
    
    area_of_overlap = tf.multiply(overlap_width,overlap_height)
    area = tf.multiply(h,w)
    label_area = tf.multiply(h_label,w_label)
    area_of_union = tf.subtract(tf.add(label_area,area), area_of_overlap)
    
    IoU = tf.div(area_of_overlap,area_of_union)
    
    return IoU



    