%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import pandas as pd
import numpy as np
import tensorflow as tf
import tflearn
from tflearn.data_utils import to_categorical
import matplotlib.pyplot as plt
from itertools import product

"""
Explanation: Static tumbling neural network
Imports
End of explanation
"""


static_tumbling = pd.read_csv('static-tumbling.csv')

"""
Explanation: Load and prepare the data
Import data from static tumbling csv file
End of explanation
"""


elements, score = static_tumbling['elements'], static_tumbling['score']

"""
Explanation: Separate the data into features and targets
End of explanation
"""


#Main vocabulary, based on the data set elements
main_vocab = set()
for line in elements:
    for element in line.split(" "):
        main_vocab.add(element)
        
main_vocab = list(main_vocab)

#Expanded vocabulary based on 49 permutations of the posible transitions

vocab = list(main_vocab)

for roll in product(main_vocab, repeat = 2 ):
     vocab.append("{} {}".format(roll[0],roll[1]))
        


"""
Explanation: Generate global vocabulary
End of explanation
"""


word2idx = {word: i for i, word in enumerate(vocab)}

word2idx

"""
Explanation: Create dictionary to map each element to an index
End of explanation
"""


def text_to_vector(text):
    word_vector = np.zeros(len(vocab), dtype=np.int_)
    text_vector = text.split(' ')
    
    #basic vocab matching
    for element in text_vector:
        idx = word2idx.get(element, None)
        if idx is None:
            continue
        else:
            word_vector[idx] += 1
    
    #Check for transition order
    for x in range(len(text_vector) -1 ):
        pair = "{} {}".format(text_vector[x],text_vector[x+1])
        idx2 = word2idx.get(pair, None)
        if idx2 is None:
            continue
        else:
            word_vector[idx2]+=1
            
    return np.array(word_vector)

text_to_vector("flick flick flick mortal")

"""
Explanation: Text to vector fucntion
It will convert the elements to a vector of words
End of explanation
"""


word_vectors = np.zeros((len(elements), len(vocab)), dtype=np.int_)
for ii, text in enumerate(elements):
    word_vectors[ii] = text_to_vector(text)

word_vectors

"""
Explanation: Convert all static tumbling passes to vectors
End of explanation
"""


Y = (score).astype(np.float_)
records = len(score)

shuffle = np.arange(records)
np.random.shuffle(shuffle)
test_fraction = 0.9

#Y values are one dimentional array of shape (1, N) in order to get the dot product we need it in the form
# (N, 1) so that's why i'm using `Y.values[train_split,None]`
train_split, test_split = shuffle[:int(records*test_fraction)], shuffle[int(records*test_fraction):]
trainX, trainY = word_vectors[train_split,:], Y.values[train_split,None]
testX, testY = word_vectors[test_split,:], Y.values[test_split,None]

trainX

"""
Explanation: Train, validation, Tests sets
Now that we have the word_vectors, we're ready to split our data into train, validation, and test sets. Remember that we train on the train data, use the validation data to set the hyperparameters, and at the very end measure the network performance on the test data.
End of explanation
"""


# Network building
def build_model():
    # This resets all parameters and variables, leave this here
    tf.reset_default_graph()
    #Input
    net = tflearn.input_data([None, 56])
    #Hidden
    net = tflearn.fully_connected(net, 350, activation='sigmoid')
    net = tflearn.fully_connected(net, 150, activation='sigmoid')
    net = tflearn.fully_connected(net, 25, activation='sigmoid')
    #output layer as a linear  activation function
    net = tflearn.fully_connected(net, 1, activation='linear')
    net = tflearn.regression(net, optimizer='sgd', loss='mean_square',metric='R2', learning_rate=0.01)
    model = tflearn.DNN(net)
    return model

"""
Explanation: Building the network
End of explanation
"""


model = build_model()

"""
Explanation: Initializing the model
End of explanation
"""


# Training
model.fit(trainX, trainY, validation_set=0.1, show_metric=True, batch_size=128, n_epoch=2000)

"""
Explanation: Training the network
End of explanation
"""


# Load model
# model.load('Checkpoints/model-with-transitions-with-3-layers.tfl')

# Manually save model
model.save("Checkpoints/model-with-transitions-with-3-layers.tfl")

"""
Explanation: I know total loss is still to high, but not that bad for the first round of hyper parameters, still room for total loss improvement 
Saving de model
End of explanation
"""


# Helper function that uses our model to get the score for the static tumbling pass
def test_score(sentence):
    score = model.predict([text_to_vector(sentence.lower())])
    print('Gym pass: {}'.format(sentence))
    print('Score: {}'.format(score))
    print()
    return score

# Helper function that uses our model to compare static tumbling passes
def test_compare(pass1, pass2):
    score1 = test_score(pass1)
    score2 = test_score(pass2)
    if score1 > score2:
        print('Gym pass 1: {}'.format(pass1))
    elif score2 > score1:
        print('Gym pass 2: {}'.format(pass2))
    else:
        print('same difficulty')
    

"""
Explanation: Testing
End of explanation
"""


element1 = "flick mortal"
element2 = "mortal flick"

test_compare(element1,element2)

"""
Explanation: Now we check the accuracy of the mode, this test checks which static tumbling line is more difficult, the second one is not even in the data we trianed the neural network.
First we compare to static tumblin pass that has the same elements but different transition cost or effort, 
acording to flick mortal and mortal flick it's harder to execute mortal flick
End of explanation
"""


test_element1 = "flick flick flick flick flick mortal giro giro giro2"
test_element2 = "mortal flick giro flick giro mortal giro2 giro2 giro2"

test_compare(test_element1,test_element2)

"""
Explanation: Now test the model with data that wasn't in the data set
in this complex example the second element is a lot harder to execute
End of explanation
"""


fig, ax = plt.subplots(figsize=(15,6))
predictions = model.predict(testX)
ax.plot(predictions,label='Prediction')
ax.plot(testY, label='Data')
ax.set_xlim(right=len(predictions))
ax.legend()

"""
Explanation: Test data validation
Now the test values we separeted from the begining are going to be compared with the actual values to check model accuracy
End of explanation
"""


