import urllib.request
import re
import unicodedata
import numpy as np

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import LambdaCallback

def clean_text(text, lowercase=True, keep_accents=False):
    # Normalize unicode
    text = unicodedata.normalize("NFC", text)

    # Replace newlines/tabs with spaces
    text = re.sub(r"[\n\r\t]+", " ", text)

    # Remove special characters (keeps Greek letters, English letters, numbers, spaces)
    text = re.sub(r"[^0-9A-Za-zΑ-Ωα-ωΆΈΉΊΌΎΏάέήίόύώϊϋΐΰ\s]", "", text)

    # Remove accents
    if not keep_accents:
        text = ''.join(
            c for c in unicodedata.normalize('NFD', text)
            if unicodedata.category(c) != 'Mn'
        )

    # Lowercase
    if lowercase:
        text = text.lower()

    # Remove extra spaces
    text = re.sub(r"\s+", " ", text).strip()

    return text


def create_training_data(data, char_to_idx, length):
    
    ################
    #your code here#
    ################

    return X, y

def create_model(vocab_size, length, hidden_dim):
    model = Sequential()
    model.add(Input(shape=(length, vocab_size)))
    
    ################
    #your code here#
    ################

    return model

def generate_text(epoch, _):
    generated_text = ''
    sample_text = 'οταν τα ματια σου κοιταω θελω '
    generated_text += sample_text
    print('\nGenerating text with seed: "' + sample_text + '"')

    ################
    #your code here#
    ################

    print('Generated text:', generated_text)


# Load data
with urllib.request.urlopen('https://users.uop.gr/~nikolentzos/files/lyrics.txt') as f:
    data = f.read().decode("utf-8")

# Clean text
data = clean_text(data)

# Extract vocabulary (character-level)
vocab = sorted(list(set(data)))
vocab_size = len(vocab)
print('Size of vocabulary:', vocab_size)

# Number of characters to give as input to model
length = 30

# create a mapping from characters to integers
char_to_idx = { ch:i for i,ch in enumerate(vocab) }
idx_to_char = { i:ch for i,ch in enumerate(vocab) }

# Example of text converted to list of integers and reverse process
example_txt = 'μηχανική μάθηση'
example_clean_txt = clean_text(example_txt)
encoded_txt = [char_to_idx[c] for c in example_clean_txt]
decoded_txt = ''.join([idx_to_char[c] for c in encoded_txt])
print('Decoded text:', decoded_txt)

# Create training data
X, y = create_training_data(data, char_to_idx, length)
print("Size of training matrix:", X.shape)

model = create_model(vocab_size, length, hidden_dim=128)

# Use Adam optimizer and categorical cross-entropy as the loss function
optimizer = Adam(learning_rate=0.001)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

# Callback to generate text at the end of an epoch
test_callback = LambdaCallback(on_epoch_end=generate_text)

# Train model for 50 epochs and generate text at the end of each epoch
model.fit(X, y, epochs=50, batch_size=128, callbacks=[test_callback])