import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter
import math
import tensorflow as tf
from datetime import datetime

from tensorflow.keras.layers import Dense, LSTM, Concatenate, Input, Embedding, Reshape, StackedRNNCells, LSTMCell, Dropout, Lambda
from tensorflow.keras.models import Model, Sequential, load_model
from tensorflow.keras.optimizers import RMSprop, Adam
from tensorflow.keras.losses import CategoricalCrossentropy, SparseCategoricalCrossentropy
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

from tensorflow.keras.utils import plot_model
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping
from tensorflow.keras.metrics import CategoricalCrossentropy
from tensorflow.keras.activations import relu
from tensorflow.keras.layers.experimental.preprocessing import  Normalization

from sklearn.preprocessing import OneHotEncoder

dados=pd.read_csv("train_set.csv")
#dados=pd.read_csv("dataset-ines-30-03.csv")
dados=dados.rename(str.lower, axis='columns')
dados['is_onenet'].fillna(0.0,inplace=True)
dados['is_onenet']=dados['is_onenet'].astype(int)
dados.pop('sessionid')
dados.pop('unnamed: 0')
dados=dados[(dados["symptom_l0"]!="FORWARD") | (dados["symptom_l1"]!="FORWARD") | (dados["symptom_l2"]!="FORWARD") | (dados["symptom_l3"]!="FORWARD") | (dados["symptom_l4"]!="FORWARD") | (dados["symptom_l5"]!="FORWARD") | (dados["symptom_l6"]!="FORWARD")]

labels=dados.pop('label')

def build_model(vocab_size):

  #LSTM para tratar de sintomas
  l0_inputs = Input(shape=(None,),name='l0')
  l0_length = Input(shape=(1,),name='l0_length')
  l1_inputs = Input(shape=(None,),name='l1')
  l1_length = Input(shape=(1,),name='l1_length')
  l2_inputs = Input(shape=(None,),name='l2')
  l2_length = Input(shape=(1,),name='l2_length')
  l3_inputs = Input(shape=(None,),name='l3')
  l3_length = Input(shape=(1,),name='l3_length')
  l4_inputs = Input(shape=(None,),name='l4')
  l4_length = Input(shape=(1,),name='l4_length')
  l5_inputs = Input(shape=(None,),name='l5')
  l5_length = Input(shape=(1,),name='l5_length')
  l6_inputs = Input(shape=(None,),name='l6')
  l6_length = Input(shape=(1,),name='l6_length')
  l7_inputs = Input(shape=(None,),name='l7')
  l7_length = Input(shape=(1,),name='l7_length')
  l8_inputs = Input(shape=(None,),name='l8')
  l8_length = Input(shape=(1,),name='l8_length')
  l9_inputs = Input(shape=(None,),name='l9')
  l9_length = Input(shape=(1,),name='l9_length')

  emb=Embedding(1700,64)

  l0_emb = emb(l0_inputs)
  l1_emb = emb(l1_inputs)
  l2_emb = emb(l2_inputs)
  l3_emb = emb(l3_inputs)
  l4_emb = emb(l4_inputs)
  l5_emb = emb(l5_inputs)
  l6_emb = emb(l6_inputs)
  l7_emb = emb(l7_inputs)
  l8_emb = emb(l8_inputs)
  l9_emb = emb(l9_inputs)

  lstm1=LSTM(128, return_state=True, return_sequences=True)
  lstm2=LSTM(128, return_state=True, return_sequences=True)
  lstm3=LSTM(128, return_state=True)
  #Lstm4 ser stacked e aumentar 
  lstm4=LSTM(64)


  def mygather2D(tensor,idx, lstm_units):
    bsize = tf.shape(tensor)[0]
    rowSize = tf.shape(tensor)[1]*lstm_units
    ftensor = tf.reshape(tensor,[-1,lstm_units])
    aux = tf.shape(tensor)[1]*tf.range(bsize)
    nidx = idx + aux
    return tf.gather(ftensor,nidx)


  def stackedLstm(emb,length,initial_state0=None,initial_state1=None,initial_state2=None):
    print(emb)
    lstm_out, l1_state_h, l1_state_c = lstm1(emb, initial_state=initial_state0)
    lstm_out, l2_state_h, l2_state_c = lstm2(lstm_out, initial_state=initial_state1)
    lstm_out, l3_state_h, l3_state_c = lstm3(lstm_out, initial_state=initial_state2)
    concat = Concatenate(axis=1)([length,lstm_out])
    lstm_out= Lambda(lambda x: mygather2D(x[:,1:],
                                          tf.cast(x[:,0],tf.int32),
                                          128))(concat)
    return lstm_out,[l1_state_h, l1_state_c],[l2_state_h,l2_state_c],[l3_state_h,l3_state_c]

  l0_lstm_out, l01, l02, l03=stackedLstm(l0_emb,l0_length)
  l1_lstm_out, l11, l12, l13=stackedLstm(l1_emb,l1_length,l01,l02,l03)
  l2_lstm_out, l21, l22, l23=stackedLstm(l2_emb,l2_length,l11,l12,l13)
  l3_lstm_out, l31, l32, l33=stackedLstm(l3_emb,l3_length,l21,l22,l23)
  l4_lstm_out, l41, l42, l43=stackedLstm(l4_emb,l4_length,l31,l32,l33)
  l5_lstm_out, l51, l52, l53=stackedLstm(l5_emb,l5_length,l41,l42,l43)
  l6_lstm_out, l61, l62, l63=stackedLstm(l6_emb,l6_length,l51,l52,l53)
  l7_lstm_out, l71, l72, l73=stackedLstm(l7_emb,l7_length,l61,l62,l63)
  l8_lstm_out, l81, l82, l83=stackedLstm(l8_emb,l8_length,l71,l72,l73)
  l9_lstm_out, l91, l92, l93=stackedLstm(l9_emb,l9_length,l81,l82,l83)
  
  outputs_symp1 = Concatenate(name='Concat')([l0_lstm_out,l1_lstm_out,l2_lstm_out,l3_lstm_out,l4_lstm_out,l5_lstm_out,l6_lstm_out,l7_lstm_out,l8_lstm_out,l9_lstm_out])
  outputs_symp = Reshape((10,128))(outputs_symp1)
  outputs_symp = lstm4(outputs_symp)
  outputs_symp = Dense(256,activation="softmax")(outputs_symp)

  #NN para tratar das restantes features
  input_outher=Input(293, name='outher')
  output = Concatenate() ([input_outher,outputs_symp])
  output=Dense(256, relu)(output)
  output=Dense(512, relu)(output)
  output=Dropout(0.2)(output)
  output=Dense(256, relu)(output)
  output = Dense(212, activation="softmax")(output)

  model=Model([input_outher,l0_inputs,l1_inputs,l2_inputs,l3_inputs,l4_inputs,l5_inputs,l6_inputs,l7_inputs,l8_inputs,l9_inputs,
              l0_length,l1_length,l2_length,l3_length,l4_length,l5_length,l6_length,l7_length,l8_length,l9_length], output)

  return model

def build_vocab(data):
  tokenizer = Tokenizer()
  data=data.fillna("-")
  tokenizer.fit_on_texts(data)

  vocab_size= len(tokenizer.word_index)+1
  max_length = np.array([len(s) for s in data])

  X_tokens=tokenizer.texts_to_sequences(data)

  return [vocab_size, max_length, X_tokens]

texts=pd.DataFrame()
inputs=np.empty((10,3), dtype=list)
texts['l0']=dados.pop('symptom_l0')
inputs[0]=build_vocab(texts['l0'])
for i in range(1,10):
    inputs[i]=build_vocab(dados.pop('symptom_l'+str(i)))
vocab_size=max(inputs[:,0])
print(vocab_size)
for x in inputs:
    x[2]=pad_sequences(x[2],vocab_size,padding='post')

print(type(inputs[0][2][0][0]))

# model=build_model(vocab_size)
# model.compile(Adam(1e-4),SparseCategoricalCrossentropy(), metrics=['accuracy'])


# logdir="logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S")
# tensorboard_callback = TensorBoard(log_dir=logdir)
# checkpoint_dir='logs/checkpoint/'+ datetime.now().strftime("%Y-%m-%d-%H-%M-%S")+'-{epoch:02d}/'+'load_model.hdf5'
# checkpoint_callback=ModelCheckpoint(
#     filepath=checkpoint_dir,
#     save_weights_only=False,
#     monitor='val_accuracy',
#     mode='max',
#     save_best_only=True)

# model.fit({'l0':inputs[0][2],'l1':inputs[1][2],'l2':inputs[2][2],'l3':inputs[3][2],'l4':inputs[4][2],'l5':inputs[5][2],'l6':inputs[6][2],'l7':inputs[7][2],'l8':inputs[8][2],'l9':inputs[9][2],
#            'l0_length':inputs[0][1],'l1_length':inputs[1][1],'l2_length':inputs[2][1],'l3_length':inputs[3][1],'l4_length':inputs[4][1],'l5_length':inputs[5][1],'l6_length':inputs[6][1],'l7_length':inputs[7][1],'l8_length':inputs[8][1],'l9_length':inputs[9][1],
#            'outher':dados},
#           labels.to_numpy().astype(np.float32),
#           callbacks=[tensorboard_callback, checkpoint_callback],
#           epochs=2000)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter
import math
import tensorflow as tf
from datetime import datetime

from tensorflow.keras.layers import Dense, LSTM, Concatenate, Input, Embedding, Reshape, StackedRNNCells, LSTMCell, Dropout, Lambda
from tensorflow.keras.models import Model, Sequential, load_model
from tensorflow.keras.optimizers import RMSprop, Adam
from tensorflow.keras.losses import CategoricalCrossentropy, SparseCategoricalCrossentropy
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

from tensorflow.keras.utils import plot_model
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping
from tensorflow.keras.metrics import CategoricalCrossentropy
from tensorflow.keras.activations import relu
from tensorflow.keras.layers.experimental.preprocessing import  Normalization

from sklearn.preprocessing import OneHotEncoder

dados=pd.read_csv("train_set.csv")
#dados=pd.read_csv("dataset-ines-30-03.csv")
dados=dados.rename(str.lower, axis='columns')
dados['is_onenet'].fillna(0.0,inplace=True)
dados['is_onenet']=dados['is_onenet'].astype(int)
dados.pop('sessionid')
dados.pop('unnamed: 0')
dados=dados[(dados["symptom_l0"]!="FORWARD") | (dados["symptom_l1"]!="FORWARD") | (dados["symptom_l2"]!="FORWARD") | (dados["symptom_l3"]!="FORWARD") | (dados["symptom_l4"]!="FORWARD") | (dados["symptom_l5"]!="FORWARD") | (dados["symptom_l6"]!="FORWARD")]

labels=dados.pop('label')

def build_model(vocab_size):

  #LSTM para tratar de sintomas
  l0_inputs = Input(shape=(None,),name='l0')
  l0_length = Input(shape=(1,),name='l0_length')
  l1_inputs = Input(shape=(None,),name='l1')
  l1_length = Input(shape=(1,),name='l1_length')
  l2_inputs = Input(shape=(None,),name='l2')
  l2_length = Input(shape=(1,),name='l2_length')
  l3_inputs = Input(shape=(None,),name='l3')
  l3_length = Input(shape=(1,),name='l3_length')
  l4_inputs = Input(shape=(None,),name='l4')
  l4_length = Input(shape=(1,),name='l4_length')
  l5_inputs = Input(shape=(None,),name='l5')
  l5_length = Input(shape=(1,),name='l5_length')
  l6_inputs = Input(shape=(None,),name='l6')
  l6_length = Input(shape=(1,),name='l6_length')
  l7_inputs = Input(shape=(None,),name='l7')
  l7_length = Input(shape=(1,),name='l7_length')
  l8_inputs = Input(shape=(None,),name='l8')
  l8_length = Input(shape=(1,),name='l8_length')
  l9_inputs = Input(shape=(None,),name='l9')
  l9_length = Input(shape=(1,),name='l9_length')

  emb=Embedding(1700,64)

  l0_emb = emb(l0_inputs)
  l1_emb = emb(l1_inputs)
  l2_emb = emb(l2_inputs)
  l3_emb = emb(l3_inputs)
  l4_emb = emb(l4_inputs)
  l5_emb = emb(l5_inputs)
  l6_emb = emb(l6_inputs)
  l7_emb = emb(l7_inputs)
  l8_emb = emb(l8_inputs)
  l9_emb = emb(l9_inputs)

  lstm1=LSTM(128, return_state=True, return_sequences=True)
  lstm2=LSTM(128, return_state=True, return_sequences=True)
  lstm3=LSTM(128, return_state=True)
  #Lstm4 ser stacked e aumentar 
  lstm4=LSTM(64)


  def mygather2D(tensor,idx, lstm_units):
    bsize = tf.shape(tensor)[0]
    rowSize = tf.shape(tensor)[1]*lstm_units
    ftensor = tf.reshape(tensor,[-1,lstm_units])
    aux = tf.shape(tensor)[1]*tf.range(bsize)
    nidx = idx + aux
    return tf.gather(ftensor,nidx)


  def stackedLstm(emb,length,initial_state0=None,initial_state1=None,initial_state2=None):
    print(emb)
    lstm_out, l1_state_h, l1_state_c = lstm1(emb, initial_state=initial_state0)
    lstm_out, l2_state_h, l2_state_c = lstm2(lstm_out, initial_state=initial_state1)
    lstm_out, l3_state_h, l3_state_c = lstm3(lstm_out, initial_state=initial_state2)
    concat = Concatenate(axis=1)([length,lstm_out])
    lstm_out= Lambda(lambda x: mygather2D(x[:,1:],
                                          tf.cast(x[:,0],tf.int32),
                                          128))(concat)
    return lstm_out,[l1_state_h, l1_state_c],[l2_state_h,l2_state_c],[l3_state_h,l3_state_c]

  l0_lstm_out, l01, l02, l03=stackedLstm(l0_emb,l0_length)
  l1_lstm_out, l11, l12, l13=stackedLstm(l1_emb,l1_length,l01,l02,l03)
  l2_lstm_out, l21, l22, l23=stackedLstm(l2_emb,l2_length,l11,l12,l13)
  l3_lstm_out, l31, l32, l33=stackedLstm(l3_emb,l3_length,l21,l22,l23)
  l4_lstm_out, l41, l42, l43=stackedLstm(l4_emb,l4_length,l31,l32,l33)
  l5_lstm_out, l51, l52, l53=stackedLstm(l5_emb,l5_length,l41,l42,l43)
  l6_lstm_out, l61, l62, l63=stackedLstm(l6_emb,l6_length,l51,l52,l53)
  l7_lstm_out, l71, l72, l73=stackedLstm(l7_emb,l7_length,l61,l62,l63)
  l8_lstm_out, l81, l82, l83=stackedLstm(l8_emb,l8_length,l71,l72,l73)
  l9_lstm_out, l91, l92, l93=stackedLstm(l9_emb,l9_length,l81,l82,l83)
  
  outputs_symp1 = Concatenate(name='Concat')([l0_lstm_out,l1_lstm_out,l2_lstm_out,l3_lstm_out,l4_lstm_out,l5_lstm_out,l6_lstm_out,l7_lstm_out,l8_lstm_out,l9_lstm_out])
  outputs_symp = Reshape((10,128))(outputs_symp1)
  outputs_symp = lstm4(outputs_symp)
  outputs_symp = Dense(256,activation="softmax")(outputs_symp)

  #NN para tratar das restantes features
  input_outher=Input(293, name='outher')
  output = Concatenate() ([input_outher,outputs_symp])
  output=Dense(256, relu)(output)
  output=Dense(512, relu)(output)
  output=Dropout(0.2)(output)
  output=Dense(256, relu)(output)
  output = Dense(212, activation="softmax")(output)

  model=Model([input_outher,l0_inputs,l1_inputs,l2_inputs,l3_inputs,l4_inputs,l5_inputs,l6_inputs,l7_inputs,l8_inputs,l9_inputs,
              l0_length,l1_length,l2_length,l3_length,l4_length,l5_length,l6_length,l7_length,l8_length,l9_length], output)

  return model

def build_vocab(data):
  tokenizer = Tokenizer()
  data=data.fillna("-")
  tokenizer.fit_on_texts(data)

  vocab_size= len(tokenizer.word_index)+1
  max_length = np.array([len(s) for s in data])

  X_tokens=tokenizer.texts_to_sequences(data)

  return [vocab_size, max_length, X_tokens]

texts=pd.DataFrame()
inputs=np.empty((10,3), dtype=list)
texts['l0']=dados.pop('symptom_l0')
inputs[0]=build_vocab(texts['l0'])
for i in range(1,10):
    inputs[i]=build_vocab(dados.pop('symptom_l'+str(i)))
vocab_size=max(inputs[:,0])
print(vocab_size)
for x in inputs:
    x[2]=pad_sequences(x[2],vocab_size,padding='post')

print(type(inputs[0][2][0][0]))

# model=build_model(vocab_size)
# model.compile(Adam(1e-4),SparseCategoricalCrossentropy(), metrics=['accuracy'])


# logdir="logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S")
# tensorboard_callback = TensorBoard(log_dir=logdir)
# checkpoint_dir='logs/checkpoint/'+ datetime.now().strftime("%Y-%m-%d-%H-%M-%S")+'-{epoch:02d}/'+'load_model.hdf5'
# checkpoint_callback=ModelCheckpoint(
#     filepath=checkpoint_dir,
#     save_weights_only=False,
#     monitor='val_accuracy',
#     mode='max',
#     save_best_only=True)

# model.fit({'l0':inputs[0][2],'l1':inputs[1][2],'l2':inputs[2][2],'l3':inputs[3][2],'l4':inputs[4][2],'l5':inputs[5][2],'l6':inputs[6][2],'l7':inputs[7][2],'l8':inputs[8][2],'l9':inputs[9][2],
#            'l0_length':inputs[0][1],'l1_length':inputs[1][1],'l2_length':inputs[2][1],'l3_length':inputs[3][1],'l4_length':inputs[4][1],'l5_length':inputs[5][1],'l6_length':inputs[6][1],'l7_length':inputs[7][1],'l8_length':inputs[8][1],'l9_length':inputs[9][1],
#            'outher':dados},
#           labels.to_numpy().astype(np.float32),
#           callbacks=[tensorboard_callback, checkpoint_callback],
#           epochs=2000)
#dados=pd.read_csv("dataset-ines-30-03.csv") #LSTM para tratar de sintomas #Lstm4 ser stacked e aumentar #NN para tratar das restantes features # model=build_model(vocab_size) # model.compile(Adam(1e-4),SparseCategoricalCrossentropy(), metrics=['accuracy']) # logdir="logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S") # tensorboard_callback = TensorBoard(log_dir=logdir) # checkpoint_dir='logs/checkpoint/'+ datetime.now().strftime("%Y-%m-%d-%H-%M-%S")+'-{epoch:02d}/'+'load_model.hdf5' # checkpoint_callback=ModelCheckpoint( #     filepath=checkpoint_dir, #     save_weights_only=False, #     monitor='val_accuracy', #     mode='max', #     save_best_only=True) # model.fit({'l0':inputs[0][2],'l1':inputs[1][2],'l2':inputs[2][2],'l3':inputs[3][2],'l4':inputs[4][2],'l5':inputs[5][2],'l6':inputs[6][2],'l7':inputs[7][2],'l8':inputs[8][2],'l9':inputs[9][2], #            'l0_length':inputs[0][1],'l1_length':inputs[1][1],'l2_length':inputs[2][1],'l3_length':inputs[3][1],'l4_length':inputs[4][1],'l5_length':inputs[5][1],'l6_length':inputs[6][1],'l7_length':inputs[7][1],'l8_length':inputs[8][1],'l9_length':inputs[9][1], #            'outher':dados}, #           labels.to_numpy().astype(np.float32), #           callbacks=[tensorboard_callback, checkpoint_callback], #           epochs=2000)