
corpus = """
Hello! What's up? Hi, How are you today? I’m doing well, thank you for asking. How about you? It’s such a nice day, isn’t it? The weather is perfect today, not too hot, not too cold. Have you been enjoying the sunshine? It’s been such a refreshing change from all that rain last week. By the way, have you tried that new café down the street? It’s cozy and they make the best coffee.

What’s been keeping you busy lately? I’ve been swamped with work, but it’s all good. How about you? Anything interesting going on? Sometimes it feels like everything’s moving so fast, and it’s hard to keep up with everything. But I guess that’s just part of life, right? Anyway, are you planning anything for the weekend? I was thinking of catching up on some reading and maybe watching a movie. There’s that new sci-fi film out. Have you seen it yet? I’ve heard good things about it.

Speaking of movies, do you like thrillers or mysteries? There’s a book I read recently that was a real page-turner. It’s about a psychiatrist and a patient with a dark secret. It kept me hooked from start to finish. You might enjoy it. What kind of books do you usually read? I’m always looking for recommendations.

The weekend seems like a good time to relax. Are you going anywhere, or just staying in? It’s always nice to take a break and recharge. Maybe a walk in the park would be refreshing, especially with this kind of weather. There’s nothing like the feeling of the sun on your face and a cool breeze in the air. It makes everything feel a little bit brighter.

It’s funny how a simple change in the weather can lift your mood, isn’t it? I think it’s one of those little things that we often take for granted. Anyway, how’s work been going for you? Have you been working on any interesting projects? I’ve been handling a pretty big one lately, and it’s been quite the challenge, but it’s rewarding too. It feels good to see everything coming together after all the hard work.

If you ever need a break, maybe we could grab some coffee sometime, chat about life, and just unwind. It’s always nice to take a step back from everything and enjoy the moment. Sometimes it’s the little moments like these that make the day feel special, don’t you think?

What are your plans for the future? Anything exciting coming up? It’s always fun to dream a little and think about what lies ahead. For now, though, I think I’ll just enjoy the present. Time really does fly, and it’s easy to forget to slow down and appreciate the small things. Maybe that’s something we should all try to do a bit more.

Oh, before I forget, I was wondering if you’ve heard of that upcoming event next month? It’s a reunion of sorts, and it would be great to catch up with everyone. I’m definitely planning to go, should be fun. Maybe you’ll be able to make it too? It could be a nice way to reconnect after all this time. There’s something about seeing old friends that brings back a lot of memories.

Well, I won’t keep you for too long. It’s been nice chatting with you. Goddbye! Let’s catch up again soon. Until then, take care and have a great rest of your day. If you ever need anything, don’t hesitate to reach out, bye. I hope everything goes well with your work and plans. Farewell, looking forward to talking again soon. See you! See ya!

"""

import math
import re
import random


ModelName = 'AgGPT-mn' # haha
output_length = 15 # Set the maximum number of words to generate
creativity = 0.5  # Set the creativity level from 0 (rigid) to 1 (creative)
UserName = 'User' # put your name here


def mat_mul(A, B):
    result = []
    for i in range(len(A)):
        result.append([])
        for j in range(len(B[0])):
            result[i].append(sum(A[i][k] * B[k][j] for k in range(len(B))))
    return result

def softmax(x):
    exp_x = [math.exp(v - max(x)) for v in x]
    sum_exp_x = sum(exp_x)
    return [e / sum_exp_x for e in exp_x]

def self_attention(Q, K, V):
    scores = []
    for i in range(len(Q)):
        row = []
        for j in range(len(K)):
            score = sum(Q[i][idx] * K[j][idx] for idx in range(len(Q[i])))
            row.append(score)
        scores.append(row)

    attention_weights = [softmax(row) for row in scores]

    output = []
    for i in range(len(V)):
        weighted_sum = [sum(attention_weights[i][k] * V[k][j] for k in range(len(V)))
                        for j in range(len(V[0]))]
        output.append(weighted_sum)

    return output

def multi_head_attention(Q, K, V, num_heads):
    d_model = len(Q[0])
    head_size = d_model // num_heads
    outputs = []

    for head in range(num_heads):
        q_head = [row[head * head_size:(head + 1) * head_size] for row in Q]
        k_head = [row[head * head_size:(head + 1) * head_size] for row in K]
        v_head = [row[head * head_size:(head + 1) * head_size] for row in V]

        attention_output = self_attention(q_head, k_head, v_head)
        outputs.extend(attention_output)

    return outputs

def positional_encoding(seq_len, d_model):
    encoding = []
    for pos in range(seq_len):
        row = []
        for i in range(d_model):
            if i % 2 == 0:
                row.append(math.sin(pos / (10000 ** (i / d_model))))
            else:
                row.append(math.cos(pos / (10000 ** (i / d_model))))
        encoding.append(row)
    return encoding

def add_positional_encoding(embeddings, positional_encodings):
    return [[val + positional_encodings[i][j] for j, val in enumerate(row)]
            for i, row in enumerate(embeddings)]

def feed_forward_network(x):
    input_dim = len(x[0])
    hidden_dim = 4
    output_dim = 2
    W1 = [[1 if i == j else 0 for j in range(hidden_dim)] for i in range(input_dim)]
    b1 = [0] * hidden_dim
    W2 = [[1 for _ in range(output_dim)] for _ in range(hidden_dim)]
    b2 = [0] * output_dim
    hidden = [[max(0, sum(x[i][k] * W1[k][j] for k in range(len(W1))) + b1[j])
               for j in range(hidden_dim)] for i in range(len(x))]
    output = [[sum(hidden[i][k] * W2[k][j] for k in range(len(W2))) + b2[j]
               for j in range(output_dim)] for i in range(len(hidden))]
    return output

def tokenize(text):
    return re.sub(r'[.,!?]', '', text.lower()).split()

def embed_tokens(tokens):
    return [[random.random() for _ in range(3)] for _ in tokens]

def build_ngram_models(corpus):
    bigram_model = {}
    trigram_model = {}
    words = tokenize(corpus)

    for i in range(len(words) - 1):
        word1, word2 = words[i], words[i + 1]
        if word1 not in bigram_model:
            bigram_model[word1] = []
        bigram_model[word1].append(word2)

    for i in range(len(words) - 2):
        word1, word2, word3 = words[i], words[i + 1], words[i + 2]
        bigram = f"{word1} {word2}"
        if bigram not in trigram_model:
            trigram_model[bigram] = []
        trigram_model[bigram].append(word3)

    return {"bigram_model": bigram_model, "trigram_model": trigram_model}

def predict_next_word(text, models):
    bigram_model, trigram_model = models["bigram_model"], models["trigram_model"]
    words = tokenize(text)

    if not words:
        return ''

    if len(words) == 1:
        last_word = words[0]
        if last_word in bigram_model:
            next_words = bigram_model[last_word]
            return random.choice(next_words)
    elif len(words) >= 2:
        last_bigram = f"{words[-2]} {words[-1]}"
        if last_bigram in trigram_model:
            next_words = trigram_model[last_bigram]
            return random.choice(next_words)
        elif words[-1] in bigram_model:
            next_words = bigram_model[words[-1]]
            return random.choice(next_words)

    return ''

def predict_next_word_with_attention(text, ngram_models):
    bigram_model, trigram_model = ngram_models["bigram_model"], ngram_models["trigram_model"]
    tokens = tokenize(text)
    d_model = 3
    embeddings = embed_tokens(tokens)
    positional_encodings = positional_encoding(len(tokens), d_model)
    encoded_embeddings = add_positional_encoding(embeddings, positional_encodings)

    num_heads = 2
    attention_output = multi_head_attention(encoded_embeddings, encoded_embeddings, encoded_embeddings, num_heads)

    ff_output = feed_forward_network(attention_output)

    ngram_prediction = predict_next_word(text, ngram_models)
    return ngram_prediction

def clean_user_input(text):
    return re.sub(r'[<>,./;\'"\[\]{}|=_+`~!@#$%^&*()?\-]', '', text).strip().lower()

def print_progress(progress, total):
    percent = (progress / total) * 100
    bar_length = 40
    filled_length = int(bar_length * progress // total)
    bar = '=' * filled_length + '-' * (bar_length - filled_length)
    print(f'\r[{bar}] {percent:.2f}% Complete', end='')

def train_model(corpus):
    print('\nTraining for ' + ModelName + ' has begun.')
    cleaned_corpus = re.sub(r'[\r\n]+', ' ', corpus.strip())
    print_progress(0, 3)
    cleaned_corpus = re.sub(r'[.,!?]', '', cleaned_corpus)
    print_progress(1, 3)
    ngram_models = build_ngram_models(cleaned_corpus)
    print_progress(2, 3)
    print_progress(3, 3)
    print('\nTraining complete.')
    return ngram_models


def correct_text(text):
    text = text.strip()
    text = text[0].upper() + text[1:]
    
    if not re.search(r'[.!?]$', text):
        if re.search(r'\b(?:how|when|what|why|where|who|is|are|can|do|does|will|shall)\b', text, re.IGNORECASE):
            text += '?'
        else:
            text += '.'
    
    text = re.sub(r'(?<=\.\s)(\w)', lambda x: x.group().upper(), text)
    text = re.sub(r'\bi\b', 'I', text)
    text = re.sub(r'\b(i\'m|i\'ve|i\'d|i\'ll)\b', lambda x: x.group().capitalize(), text)
    
    return text

def is_sentence_complete(sentence, corpus):
    sentence = sentence.strip()
    if len(sentence) == 0:
        return False
    if 'eos' in sentence.lower() or '<eos>' in sentence.lower():
        return True
    return False

def predict_sentence_with_attention(input_text, ngram_models, output_length, creativity, recent_history_length=3):
    cleaned_input = clean_user_input(input_text)
    sentence = cleaned_input
    recent_history = []

    for _ in range(output_length):
        prediction = predict_next_word_with_attention(sentence, ngram_models)
        if not prediction:
            break
        if prediction in recent_history:
            continue  
        
        recent_history.append(prediction)
        if len(recent_history) > recent_history_length:
            recent_history.pop(0) 

        if random.random() < creativity: 
            sentence += ' ' + prediction
        else:
            next_words = ngram_models["bigram_model"].get(sentence.split()[-1], [])
            if next_words:
                most_likely_word = max(set(next_words), key=next_words.count)
                sentence += ' ' + most_likely_word
        
        if is_sentence_complete(sentence, corpus):
            break
    
    sentence = correct_text(sentence)

    if cleaned_input in sentence:
        sentence = sentence.replace(cleaned_input, '', 1).strip()
    
    return sentence


ngram_models = train_model(corpus)

while True:
    input_text = input('Type a message: ').strip()
    if input_text.lower() == 'exit':
        break
    predicted_sentence = predict_sentence_with_attention(input_text, ngram_models, output_length, creativity)
    predicted_sentence = predicted_sentence.replace('{name}', f'{ModelName}').strip()
    predicted_sentence = predicted_sentence.replace('{username}', f'{UserName}').strip()
    predicted_sentence = predicted_sentence.replace('<eos>', '').strip()

    print(f'{ModelName}:', predicted_sentence)
