import argparse
import json
import numpy as np
import pandas as pd
import os
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report,f1_score
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras import backend as K
from keras.utils.vis_utils import plot_model
from sklearn.externals import joblib
import time


def f1(y_true, y_pred):
    def recall(y_true, y_pred):
        """Recall metric.

        Only computes a batch-wise average of recall.

        Computes the recall, a metric for multi-label classification of
        how many relevant items are selected.
        """
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
        possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
        recall = true_positives / (possible_positives + K.epsilon())
        return recall

    def precision(y_true, y_pred):
        """Precision metric.

        Only computes a batch-wise average of precision.

        Computes the precision, a metric for multi-label classification of
        how many selected items are relevant.
        """
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
        predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
        precision = true_positives / (predicted_positives + K.epsilon())
        return precision
    precision = precision(y_true, y_pred)
    recall = recall(y_true, y_pred)
    return 2*((precision*recall)/(precision+recall+K.epsilon()))

def get_embeddings(sentences_list,layer_json):
    '''

    :param sentences_list: the path o the sentences.txt
    :param layer_json: the path of the json file that contains the embeddings of the sentences
    :return: Dictionary with key each sentence of the sentences_list and as value the embedding
    '''
    sentences = dict()#dict with key the index of each line of the sentences_list.txt and as value the sentence
    embeddings = dict()##dict with key the index of each sentence and as value the its embedding
    sentence_emb = dict()#key:sentence,value:its embedding

    with open(sentences_list,'r') as file:
        for index,line in enumerate(file):
            sentences[index] = line.strip()

    with open(layer_json, 'r',encoding='utf-8') as f:
        for line in f:
            embeddings[json.loads(line)['linex_index']] = np.asarray(json.loads(line)['features'])



    for key,value in sentences.items():
        sentence_emb[value] = embeddings[key]


    return sentence_emb

def train_classifier(sentences_list,layer_json,dataset_csv,filename):
    '''

    :param sentences_list: the path o the sentences.txt
    :param layer_json: the path of the json file that contains the embeddings of the sentences
    :param dataset_csv: the path of the dataset
    :param filename: The path of the pickle file that the model will be stored
    :return:
    '''

    dataset = pd.read_csv(dataset_csv)
    bert_dict = get_embeddings(sentences_list,layer_json)


    length = list()
    sentence_emb = list()
    previous_emb = list()
    next_list = list()
    section_list = list()
    label = list()
    errors = 0
    for row in dataset.iterrows():
        sentence = row[1][0].strip()
        previous = row[1][1].strip()
        nexts = row[1][2].strip()
        section = row[1][3].strip()

        if sentence in bert_dict:
            sentence_emb.append(bert_dict[sentence])
        else:
            sentence_emb.append(np.zeros(768))
            print(sentence)
            errors += 1

        if previous in bert_dict:
            previous_emb.append(bert_dict[previous])
        else:
            previous_emb.append(np.zeros(768))

        if nexts in bert_dict:
            next_list.append(bert_dict[nexts])
        else:
            next_list.append(np.zeros(768))

        if section in bert_dict:
            section_list.append(bert_dict[section])
        else:
            section_list.append(np.zeros(768))

        length.append(row[1][4])
        label.append(row[1][5])

    sentence_emb = np.asarray(sentence_emb)
    print(sentence_emb.shape)
    next_emb = np.asarray(next_list)
    print(next_emb.shape)
    previous_emb = np.asarray(previous_emb)
    print(previous_emb.shape)
    section_emb = np.asarray(section_list)
    print(sentence_emb.shape)
    length = np.asarray(length)
    print(length.shape)
    label = np.asarray(label)
    print(errors)
    features = np.concatenate([sentence_emb, previous_emb, next_emb,section_emb], axis=1)
    features = np.column_stack([features, length])  # np.append(features,length,axis=1)
    print(features.shape)

    X_train, X_val, y_train, y_val = train_test_split(features, label, test_size=0.33, random_state=42)

    log = LogisticRegression(random_state=0, solver='newton-cg', max_iter=1000, C=0.1)
    log.fit(X_train, y_train)

    #save the model
    _ = joblib.dump(log, filename, compress=9)

    predictions = log.predict(X_val)
    print("###########################################")
    print("Results using embeddings from the",layer_json,"file")
    print(classification_report(y_val, predictions))
    print("F1 score using Logistic Regression:",f1_score(y_val, predictions))
    print("###########################################")


    #train a DNN
    f1_results = list()
    for i in range(3):
        model = Sequential()
        model.add(Dense(64, activation='relu', trainable=True))
        model.add(Dense(128, activation='relu', trainable=True))
        model.add(Dropout(0.30))
        model.add(Dense(64, activation='relu', trainable=True))
        model.add(Dropout(0.25))
        model.add(Dense(64, activation='relu', trainable=True))
        model.add(Dropout(0.35))
        model.add(Dense(1, activation='sigmoid'))
        # compile network
        model.compile(loss='binary_crossentropy', optimizer='sgd', metrics=[f1])
        # fit network
        model.fit(X_train, y_train, epochs=100, batch_size=64)



        loss, f_1 = model.evaluate(X_val, y_val, verbose=1)
        print('\nTest F1: %f' % (f_1 * 100))
        f1_results.append(f_1)
        model = None

    print("###########################################")
    print("Results using embeddings from the", layer_json, "file")
    # evaluate
    print(np.mean(f1_results))
    print("###########################################")


def parameter_tuning_LR(sentences_list,layer_json,dataset_csv):
    '''

    :param sentences_list: the path o the sentences.txt
    :param layer_json: the path of the json file that contains the embeddings of the sentences
    :param dataset_csv: the path of the dataset
    :return:
    '''

    dataset = pd.read_csv(dataset_csv)
    bert_dict = get_embeddings(sentences_list,layer_json)


    length = list()
    sentence_emb = list()
    previous_emb = list()
    next_list = list()
    section_list = list()
    label = list()
    errors = 0
    for row in dataset.iterrows():
        sentence = row[1][0].strip()
        previous = row[1][1].strip()
        nexts = row[1][2].strip()
        section = row[1][3].strip()

        if sentence in bert_dict:
            sentence_emb.append(bert_dict[sentence])
        else:
            sentence_emb.append(np.zeros(768))
            print(sentence)
            errors += 1

        if previous in bert_dict:
            previous_emb.append(bert_dict[previous])
        else:
            previous_emb.append(np.zeros(768))

        if nexts in bert_dict:
            next_list.append(bert_dict[nexts])
        else:
            next_list.append(np.zeros(768))

        if section in bert_dict:
            section_list.append(bert_dict[section])
        else:
            section_list.append(np.zeros(768))

        length.append(row[1][4])
        label.append(row[1][5])

    sentence_emb = np.asarray(sentence_emb)
    print(sentence_emb.shape)
    next_emb = np.asarray(next_list)
    print(next_emb.shape)
    previous_emb = np.asarray(previous_emb)
    print(previous_emb.shape)
    section_emb = np.asarray(section_list)
    print(sentence_emb.shape)
    length = np.asarray(length)
    print(length.shape)
    label = np.asarray(label)
    print(errors)
    features = np.concatenate([sentence_emb, previous_emb, next_emb,section_emb], axis=1)
    features = np.column_stack([features, length])
    print(features.shape)

    X_train, X_val, y_train, y_val = train_test_split(features, label, test_size=0.33, random_state=42)

    C = [0.1,1,2,5,10]
    solver = ['newton-cg','saga','sag']
    best_params = dict()
    best_score = 0.0
    for c in C:
        for s in solver:
            start = time.time()
            log = LogisticRegression(random_state=0, solver=s, max_iter=1000, C=c)
            log.fit(X_train, y_train)

            predictions = log.predict(X_val)
            print("###########################################")
            print("LR with C =",c,'and solver = ',s)
            print("Results using embeddings from the", layer_json, "file")
            print(classification_report(y_val, predictions))
            f1 = f1_score(y_val, predictions)
            if f1 > best_score:
                best_score = f1
                best_params['c'] = c
                best_params['solver'] = s
            print("F1 score using Logistic Regression:",f1)
            print("###########################################")
            end = time.time()
            running_time = end - start
            print("Running time:"+str(running_time))



def visualize_DNN(file_to_save):
    '''
    Save the DNN architecture to a png file. Better use the Visulize_DNN.ipynd
    :param file_to_save: the png file that the architecture of the DNN will be saved.
    :return: None
    '''

    model = Sequential()
    model.add(Dense(64, activation='relu', trainable=True))
    model.add(Dense(128, activation='relu', trainable=True))
    model.add(Dropout(0.30))
    model.add(Dense(64, activation='relu', trainable=True))
    model.add(Dropout(0.25))
    model.add(Dense(64, activation='relu', trainable=True))
    model.add(Dropout(0.35))
    model.add(Dense(1, activation='sigmoid'))

    plot_model(model, to_file=file_to_save, show_shapes=True)



def save_model(sentences_list,layer_json,dataset_csv,pkl):
    dataset = pd.read_csv(dataset_csv)
    bert_dict = get_embeddings(sentences_list, layer_json)

    length = list()
    sentence_emb = list()
    previous_emb = list()
    next_list = list()
    section_list = list()
    label = list()
    errors = 0
    for row in dataset.iterrows():
        sentence = row[1][0].strip()
        previous = row[1][1].strip()
        nexts = row[1][2].strip()
        section = row[1][3].strip()

        if sentence in bert_dict:
            sentence_emb.append(bert_dict[sentence])
        else:
            sentence_emb.append(np.zeros(768))
            print(sentence)
            errors += 1

        if previous in bert_dict:
            previous_emb.append(bert_dict[previous])
        else:
            previous_emb.append(np.zeros(768))

        if nexts in bert_dict:
            next_list.append(bert_dict[nexts])
        else:
            next_list.append(np.zeros(768))

        if section in bert_dict:
            section_list.append(bert_dict[section])
        else:
            section_list.append(np.zeros(768))

        length.append(row[1][4])
        label.append(row[1][5])

    sentence_emb = np.asarray(sentence_emb)
    print(sentence_emb.shape)
    next_emb = np.asarray(next_list)
    print(next_emb.shape)
    previous_emb = np.asarray(previous_emb)
    print(previous_emb.shape)
    section_emb = np.asarray(section_list)
    print(sentence_emb.shape)
    length = np.asarray(length)
    print(length.shape)
    label = np.asarray(label)
    print(errors)
    features = np.concatenate([sentence_emb, previous_emb, next_emb, section_emb], axis=1)
    features = np.column_stack([features, length])
    print(features.shape)

    log = LogisticRegression(random_state=0, solver='saga', max_iter=1000, C=1)
    log.fit(features, label)

    _ = joblib.dump(log, pkl, compress=9)



if __name__ == '__main__':

    #save_model('sentences_list.txt','Fudan_output_layer_-1.json','train_sentences1.csv','summarizer1.pkl')
    ap = argparse.ArgumentParser()
    ap.add_argument("-s", "--sentences", required=True, help="sentences list")
    ap.add_argument("-o", "--output", required=True, help="output")
    ap.add_argument("-ts", "--train set", required=True, help="path to train set")
    ap.add_argument("-sp", "--summarizer path", required=True, help="path to save summarizer")

    args = vars(ap.parse_args())
    layer = train_classifier(args['sentences'], args['output'], args['train set'],args['summarizer path'])
    #layer_1 = train_classifier('sentences_list.txt', 'new_output_layer_-1.json', 'train_sentences1.csv','fine_tune_BERT_sentence_classification1.pkl')
    #layer_2 = train_classifier('sentences_list.txt','new_output_layer_-2.json','train_sentences1.csv','fine_tune_BERT_sentence_classification2.pkl')
    #layer_3 = train_classifier('sentences_list.txt','new_output_layer_-3.json','train_sentences1.csv','fine_tune_BERT_sentence_classification3.pkl')
    #layer_4 = train_classifier('sentences_list.txt','new_output_layer_-4.json','train_sentences1.csv','fine_tune_BERT_sentence_classification4.pkl')

    #tuning = parameter_tuning_LR('sentences_list.txt','new_output_layer_-1.json','train_sentences1.csv')
    #layer_1 = train_classifier('sentences_list.txt','output_layer_-1.json','train_sentences1.csv','fine_tune_BERT_sentence_classification.pkl')
    #layer_2 = train_classifier('sentences_list.txt','output_layer_-2.json','train_sentences1.csv','fine_tune_BERT_sentence_classification.pkl')
    #layer_3 = train_classifier('sentences_list.txt','output_layer_-3.json','train_sentences1.csv','fine_tune_BERT_sentence_classification.pkl')
    #layer_4 = train_classifier('sentences_list.txt','output_layer_-4.json','train_sentences1.csv','fine_tune_BERT_sentence_classification.pkl')


[(84, 'pandas.read_csv', 'pd.read_csv', (['dataset_csv'], {}), True, 'import pandas as pd\n'), (126, 'numpy.asarray', 'np.asarray', (['sentence_emb'], {}), True, 'import numpy as np\n'), (128, 'numpy.asarray', 'np.asarray', (['next_list'], {}), True, 'import numpy as np\n'), (130, 'numpy.asarray', 'np.asarray', (['previous_emb'], {}), True, 'import numpy as np\n'), (132, 'numpy.asarray', 'np.asarray', (['section_list'], {}), True, 'import numpy as np\n'), (134, 'numpy.asarray', 'np.asarray', (['length'], {}), True, 'import numpy as np\n'), (136, 'numpy.asarray', 'np.asarray', (['label'], {}), True, 'import numpy as np\n'), (138, 'numpy.concatenate', 'np.concatenate', (['[sentence_emb, previous_emb, next_emb, section_emb]'], {'axis': '(1)'}), True, 'import numpy as np\n'), (139, 'numpy.column_stack', 'np.column_stack', (['[features, length]'], {}), True, 'import numpy as np\n'), (142, 'sklearn.model_selection.train_test_split', 'train_test_split', (['features', 'label'], {'test_size': '(0.33)', 'random_state': '(42)'}), False, 'from sklearn.model_selection import train_test_split\n'), (144, 'sklearn.linear_model.LogisticRegression', 'LogisticRegression', ([], {'random_state': '(0)', 'solver': '"""newton-cg"""', 'max_iter': '(1000)', 'C': '(0.1)'}), False, 'from sklearn.linear_model import LogisticRegression\n'), (148, 'sklearn.externals.joblib.dump', 'joblib.dump', (['log', 'filename'], {'compress': '(9)'}), False, 'from sklearn.externals import joblib\n'), (198, 'pandas.read_csv', 'pd.read_csv', (['dataset_csv'], {}), True, 'import pandas as pd\n'), (240, 'numpy.asarray', 'np.asarray', (['sentence_emb'], {}), True, 'import numpy as np\n'), (242, 'numpy.asarray', 'np.asarray', (['next_list'], {}), True, 'import numpy as np\n'), (244, 'numpy.asarray', 'np.asarray', (['previous_emb'], {}), True, 'import numpy as np\n'), (246, 'numpy.asarray', 'np.asarray', (['section_list'], {}), True, 'import numpy as np\n'), (248, 'numpy.asarray', 'np.asarray', (['length'], {}), True, 'import numpy as np\n'), (250, 'numpy.asarray', 'np.asarray', (['label'], {}), True, 'import numpy as np\n'), (252, 'numpy.concatenate', 'np.concatenate', (['[sentence_emb, previous_emb, next_emb, section_emb]'], {'axis': '(1)'}), True, 'import numpy as np\n'), (253, 'numpy.column_stack', 'np.column_stack', (['[features, length]'], {}), True, 'import numpy as np\n'), (256, 'sklearn.model_selection.train_test_split', 'train_test_split', (['features', 'label'], {'test_size': '(0.33)', 'random_state': '(42)'}), False, 'from sklearn.model_selection import train_test_split\n'), (293, 'keras.models.Sequential', 'Sequential', ([], {}), False, 'from keras.models import Sequential\n'), (303, 'keras.utils.vis_utils.plot_model', 'plot_model', (['model'], {'to_file': 'file_to_save', 'show_shapes': '(True)'}), False, 'from keras.utils.vis_utils import plot_model\n'), (308, 'pandas.read_csv', 'pd.read_csv', (['dataset_csv'], {}), True, 'import pandas as pd\n'), (349, 'numpy.asarray', 'np.asarray', (['sentence_emb'], {}), True, 'import numpy as np\n'), (351, 'numpy.asarray', 'np.asarray', (['next_list'], {}), True, 'import numpy as np\n'), (353, 'numpy.asarray', 'np.asarray', (['previous_emb'], {}), True, 'import numpy as np\n'), (355, 'numpy.asarray', 'np.asarray', (['section_list'], {}), True, 'import numpy as np\n'), (357, 'numpy.asarray', 'np.asarray', (['length'], {}), True, 'import numpy as np\n'), (359, 'numpy.asarray', 'np.asarray', (['label'], {}), True, 'import numpy as np\n'), (361, 'numpy.concatenate', 'np.concatenate', (['[sentence_emb, previous_emb, next_emb, section_emb]'], {'axis': '(1)'}), True, 'import numpy as np\n'), (362, 'numpy.column_stack', 'np.column_stack', (['[features, length]'], {}), True, 'import numpy as np\n'), (365, 'sklearn.linear_model.LogisticRegression', 'LogisticRegression', ([], {'random_state': '(0)', 'solver': '"""saga"""', 'max_iter': '(1000)', 'C': '(1)'}), False, 'from sklearn.linear_model import LogisticRegression\n'), (368, 'sklearn.externals.joblib.dump', 'joblib.dump', (['log', 'pkl'], {'compress': '(9)'}), False, 'from sklearn.externals import joblib\n'), (375, 'argparse.ArgumentParser', 'argparse.ArgumentParser', ([], {}), False, 'import argparse\n'), (153, 'sklearn.metrics.classification_report', 'classification_report', (['y_val', 'predictions'], {}), False, 'from sklearn.metrics import classification_report, f1_score\n'), (154, 'sklearn.metrics.f1_score', 'f1_score', (['y_val', 'predictions'], {}), False, 'from sklearn.metrics import classification_report, f1_score\n'), (161, 'keras.models.Sequential', 'Sequential', ([], {}), False, 'from keras.models import Sequential\n'), (185, 'numpy.mean', 'np.mean', (['f1_results'], {}), True, 'import numpy as np\n'), (294, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (295, 'keras.layers.Dense', 'Dense', (['(128)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (296, 'keras.layers.Dropout', 'Dropout', (['(0.3)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (297, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (298, 'keras.layers.Dropout', 'Dropout', (['(0.25)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (299, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (300, 'keras.layers.Dropout', 'Dropout', (['(0.35)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (301, 'keras.layers.Dense', 'Dense', (['(1)'], {'activation': '"""sigmoid"""'}), False, 'from keras.layers import Dense, Dropout\n'), (162, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (163, 'keras.layers.Dense', 'Dense', (['(128)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (164, 'keras.layers.Dropout', 'Dropout', (['(0.3)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (165, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (166, 'keras.layers.Dropout', 'Dropout', (['(0.25)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (167, 'keras.layers.Dense', 'Dense', (['(64)'], {'activation': '"""relu"""', 'trainable': '(True)'}), False, 'from keras.layers import Dense, Dropout\n'), (168, 'keras.layers.Dropout', 'Dropout', (['(0.35)'], {}), False, 'from keras.layers import Dense, Dropout\n'), (169, 'keras.layers.Dense', 'Dense', (['(1)'], {'activation': '"""sigmoid"""'}), False, 'from keras.layers import Dense, Dropout\n'), (264, 'time.time', 'time.time', ([], {}), False, 'import time\n'), (265, 'sklearn.linear_model.LogisticRegression', 'LogisticRegression', ([], {'random_state': '(0)', 'solver': 's', 'max_iter': '(1000)', 'C': 'c'}), False, 'from sklearn.linear_model import LogisticRegression\n'), (273, 'sklearn.metrics.f1_score', 'f1_score', (['y_val', 'predictions'], {}), False, 'from sklearn.metrics import classification_report, f1_score\n'), (280, 'time.time', 'time.time', ([], {}), False, 'import time\n'), (26, 'keras.backend.clip', 'K.clip', (['(y_true * y_pred)', '(0)', '(1)'], {}), True, 'from keras import backend as K\n'), (27, 'keras.backend.clip', 'K.clip', (['y_true', '(0)', '(1)'], {}), True, 'from keras import backend as K\n'), (28, 'keras.backend.epsilon', 'K.epsilon', ([], {}), True, 'from keras import backend as K\n'), (39, 'keras.backend.clip', 'K.clip', (['(y_true * y_pred)', '(0)', '(1)'], {}), True, 'from keras import backend as K\n'), (40, 'keras.backend.clip', 'K.clip', (['y_pred', '(0)', '(1)'], {}), True, 'from keras import backend as K\n'), (41, 'keras.backend.epsilon', 'K.epsilon', ([], {}), True, 'from keras import backend as K\n'), (45, 'keras.backend.epsilon', 'K.epsilon', ([], {}), True, 'from keras import backend as K\n'), (104, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (111, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (116, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (121, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (218, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (225, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (230, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (235, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (272, 'sklearn.metrics.classification_report', 'classification_report', (['y_val', 'predictions'], {}), False, 'from sklearn.metrics import classification_report, f1_score\n'), (327, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (334, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (339, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (344, 'numpy.zeros', 'np.zeros', (['(768)'], {}), True, 'import numpy as np\n'), (64, 'json.loads', 'json.loads', (['line'], {}), False, 'import json\n'), (64, 'json.loads', 'json.loads', (['line'], {}), False, 'import json\n')]