import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import Model
from tensorflow import multiply
from sklearn.model_selection import GridSearchCV
from sklearn import model_selection
from sklearn.linear_model import LinearRegression, LogisticRegression
from tensorflow.keras.constraints import NonNeg
from sklearn.model_selection import KFold
import itertools
from sklearn.metrics import mean_squared_error,accuracy_score
import numpy as np
import os
import pandas as pd


def minimax_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index):   
    #grid_search
    rates = [0.1,0.01,0.001]
    batch_sizes = [32, 64, 128, 256,512,1024]
    epochs =  [50,100,400,800,1000, 1500, 2000]
    best_evaluations = 1000
    best_parameters = [0,0,0,0]
    
    kf = KFold(n_splits = 5, shuffle = True, random_state = 2)
    for r,b,e in itertools.product(rates, batch_sizes, epochs):
        evaluations = []
        index = 0
        for train_index, test_index in kf.split(train_dataset):
            index+=1
            if index<=3:
                train_X = train_dataset.iloc[train_index]
                train_Y = train_labels.iloc[train_index]
                test_X =  train_dataset.iloc[test_index]
                test_Y =  train_labels.iloc[test_index]
                model = build_minimax_baseline_model(train_X,r)
                model.fit(train_X, train_Y, epochs=e, batch_size=b, validation_split = 0,verbose=0)  
                test_score = evaluate(model, test_X,test_Y)
                evaluations.append(test_score)
        result = np.mean(evaluations)
        if result<best_evaluations:
            best_evaluations = result
            best_parameters = [r,b,e]
            print(result)
            print(best_parameters)
    [r,b,e] = best_parameters 
    print(best_parameters)
    model = build_minimax_baseline_model(train_dataset,r)
    model.fit(train_dataset, train_labels, epochs=e, batch_size=b, validation_split = 0.2,verbose=0)
    rmse_train = evaluate(model, train_dataset,train_labels)
    rmse_test = evaluate(model, test_dataset,test_labels)
    return rmse_train, rmse_test


def minimax_classification_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index):
    #grid_search
    rates = [0.1,0.01,0.001]
    batch_sizes = [32, 64, 128, 256,512,1024]
    epochs =  [50,100,400,800,1000, 1500, 2000]
    best_evaluations = 0
    best_parameters = [0,0,0,0]
    index = 0
    kf = KFold(n_splits = 5, shuffle = True, random_state = 2)
    for r,b,e in itertools.product(rates, batch_sizes, epochs):
        evaluations = []
        index = 0
        for train_index, test_index in kf.split(train_dataset):
            index+=1
            if index<=5:
                train_X = train_dataset.iloc[train_index]
                train_Y = train_labels.iloc[train_index]
                test_X =  train_dataset.iloc[test_index]
                test_Y =  train_labels.iloc[test_index]
                model = build_minimax_baseline_classification_model(train_X,r)
                model.fit(train_X, train_Y, epochs=e, batch_size=b, validation_split = 0,verbose=0)  
                test_score = evaluate(model, test_X,test_Y)
                evaluations.append(test_score)
        result = np.mean(evaluations)
        if best_evaluations<result:
            best_evaluations = result
            best_parameters = [r,b,e]
            print(result)
            print(best_parameters)
    [r,b,e] = best_parameters 
    print(best_parameters)
    model = build_minimax_baseline_model(train_dataset,r)
    model.fit(train_dataset, train_labels, epochs=e, batch_size=b, validation_split = 0.2,verbose=0)
    model = build_minimax_baseline_classification_model(train_dataset,r)
    acc_train = evaluate_classification(model, train_dataset,train_labels)
    acc_test = evaluate_classification(model, test_dataset,test_labels)
    return acc_train, acc_test


def evaluate(model, test_dataset,test_labels):
    scores = model.evaluate(test_dataset, test_labels, verbose=0)
    #print(model.metrics_names[1])
    return scores[1]

def evaluate_classification(model, test_dataset,test_labels):
    scores = model.evaluate(test_dataset, test_labels, verbose=0)
    #print(model.metrics_names[1])
    return scores[1]

def build_minimax_baseline_model(train_dataset,lr):
    layer_size = 3
    z = train_dataset.shape
    hidden_size = z[0]/3
    inputs = layers.Input(shape=(train_dataset.shape[1],))
    
    hidden_layer_0 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_1 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_2 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_3 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_4 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_5 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_6 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)

    max_0 = layers.maximum([hidden_layer_0,hidden_layer_1])
    max_1 = layers.maximum([hidden_layer_2, hidden_layer_3, hidden_layer_4])
    max_2 = layers.maximum([hidden_layer_5,hidden_layer_6])
    
    min_val = layers.minimum([max_0, max_1, max_2])
    out = layers.Dense(1)(min_val)
    model = Model(inputs=inputs, outputs=out)

    optimizer = keras.optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
    model.compile(loss='mse',
                optimizer=optimizer,
                metrics=['mse','mae'])

    return model


def build_minimax_baseline_classification_model(train_dataset, lr):
    layer_size = 3
    z = train_dataset.shape
    hidden_size = z[0]/3
    inputs = layers.Input(shape=(train_dataset.shape[1],))
    
    hidden_layer_0 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_1 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_2 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_3 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_4 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_5 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)
    hidden_layer_6 = layers.Dense(hidden_size, activation=tf.nn.relu, kernel_constraint= keras.constraints.NonNeg())(inputs)

    max_0 = layers.maximum([hidden_layer_0,hidden_layer_1])
    max_1 = layers.maximum([hidden_layer_2, hidden_layer_3, hidden_layer_4])
    max_2 = layers.maximum([hidden_layer_5,hidden_layer_6])
    
    min_val = layers.minimum([max_0, max_1, max_2])
    out = layers.Dense(1)(min_val)
    model = Model(inputs=inputs, outputs=out)

    optimizer = keras.optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
    model.compile(loss='binary_crossentropy',
                optimizer=optimizer,
                metrics=['accuracy'])

    return model

def train_minimax_baseline(train_dataset, train_labels):
    #build the model
    model, activation_types, layer_size = build(train_dataset)
    model.fit(train_dataset, train_labels, epochs=500, batch_size=32, validation_split = 0.2, verbose=0)
    return model


def run_folds_minmax_auto_mpg():
    train_evaluations = []
    test_evaluations = []
    folds = 3
    for fold in range(folds):
        train_data_path = '/auto-mpg/'+str(fold)+'/train_data.csv'
        test_data_path  = '/auto-mpg/'+str(fold)+'/test_data.csv'
        train_dataset = pd.read_csv(train_data_path,index_col=0)
        test_dataset = pd.read_csv(test_data_path,index_col=0)
        train_labels = train_dataset.pop('MPG')
        test_labels = test_dataset.pop('MPG')
        monotonic_index = 1 
        train_dataset['Displacement'] = train_dataset['Displacement']*-1
        test_dataset['Displacement'] = test_dataset['Displacement']*-1
        train_dataset['Weight'] = train_dataset['Weight']*-1
        test_dataset['Weight'] = test_dataset['Weight']*-1
        train_dataset['Horsepower'] = train_dataset['Horsepower']*-1
        test_dataset['Horsepower'] = test_dataset['Horsepower']*-1
        train_mse, test_mse = minimax_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index)
        train_evaluations.append(train_mse)
        test_evaluations.append(test_mse)
    mean_train = np.mean(train_evaluations)
    print('mean_train: '+str(mean_train))
    std_train = np.std(train_evaluations)
    print('std_train: '+str(std_train))
    mean_test = np.mean(test_evaluations)
    print('mean_test: '+str(mean_test))
    std_test = np.std(test_evaluations)
    print('std_test: '+str(std_test))


def run_folds_minmax_boston():
    train_evaluations = []
    test_evaluations = []
    folds = 3
    for fold in range(folds):
        train_data_path = 'boston/'+str(fold)+'/train_data.csv'
        test_data_path  = 'boston/'+str(fold)+'/test_data.csv'
        train_dataset = pd.read_csv(train_data_path,index_col=0)
        test_dataset = pd.read_csv(test_data_path,index_col=0)
        train_labels = train_dataset.pop('CMEDV')
        test_labels = test_dataset.pop('CMEDV')
        monotonic_index = 1 
        train_dataset['CRIM'] = train_dataset['CRIM']*-1
        test_dataset['CRIM'] = test_dataset['CRIM']*-1
        train_dataset['LSTAT'] = train_dataset['LSTAT']*-1
        test_dataset['LSTAT'] = test_dataset['LSTAT']*-1
        train_mse, test_mse = minimax_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index)
        train_evaluations.append(train_mse)
        test_evaluations.append(test_mse)
    mean_train = np.mean(train_evaluations)
    print('mean_train: '+str(mean_train))
    std_train = np.std(train_evaluations)
    print('std_train: '+str(std_train))
    mean_test = np.mean(test_evaluations)
    print('mean_test: '+str(mean_test))
    std_test = np.std(test_evaluations)
    print('std_test: '+str(std_test))


def run_folds_minmax_heart():
    train_evaluations = []
    test_evaluations = []
    folds = 3
    for fold in range(folds):
        train_data_path = 'heart/'+str(fold)+'/train_data.csv'
        test_data_path  = 'heart/'+str(fold)+'/test_data.csv'
        train_dataset = pd.read_csv(train_data_path,index_col=0)
        test_dataset = pd.read_csv(test_data_path,index_col=0)
        train_labels = train_dataset.pop('target')
        test_labels = test_dataset.pop('target')
        monotonic_index = 1 
        train_dataset['trestbps'] = train_dataset['trestbps']*1
        test_dataset['trestbps'] = test_dataset['trestbps']*1
        train_dataset['chol'] = train_dataset['chol']*1
        test_dataset['chol'] = test_dataset['chol']*1
        train_acc, test_acc = minimax_classification_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index)
        train_evaluations.append(train_acc)
        test_evaluations.append(test_acc)
    mean_train = np.mean(train_evaluations)
    print('acc_train: '+str(mean_train))
    std_train = np.std(train_evaluations)
    print('std_train: '+str(std_train))
    mean_test = np.mean(test_evaluations)
    print('acc_test: '+str(mean_test))
    std_test = np.std(test_evaluations)
    print('std_test: '+str(std_test))


def run_folds_minmax_adult():
    train_evaluations = []
    test_evaluations = []
    folds = 1
    for fold in range(folds):
        train_data_path = 'adult/'+str(fold)+'/train_data.csv'
        test_data_path  = 'adult/'+str(fold)+'/test_data.csv'
        train_dataset = pd.read_csv(train_data_path,index_col=0)
        test_dataset = pd.read_csv(test_data_path,index_col=0)
        train_labels = train_dataset.pop('IncomeRange')
        test_labels = test_dataset.pop('IncomeRange')
        monotonic_index = 1 
        train_dataset['HoursPerWeek'] = train_dataset['HoursPerWeek']*1
        test_dataset['HoursPerWeek'] = test_dataset['HoursPerWeek']*1
        train_dataset['CapitalGain'] = train_dataset['CapitalGain']*1
        test_dataset['CapitalGain'] = test_dataset['CapitalGain']*1
        train_acc, test_acc = minimax_classification_baseline(train_dataset, train_labels, test_dataset, test_labels, monotonic_index)
        train_evaluations.append(train_acc)
        test_evaluations.append(test_acc)
    mean_train = np.mean(train_evaluations)
    print('acc_train: '+str(mean_train))
    std_train = np.std(train_evaluations)
    print('std_train: '+str(std_train))
    mean_test = np.mean(test_evaluations)
    print('acc_test: '+str(mean_test))
    std_test = np.std(test_evaluations)
    print('std_test: '+str(std_test))


