import json
from const import CARD_SEPARATOR, CARD_TYPES, CARD_PAGE_URL
from typing import Optional

def stringify_card(card: dict):
    stringified = []

    header = (f'{card["name"]}{CARD_SEPARATOR}{card["kingdomShort"]} {card["rarity"]} '
                f'{card["unitTypes"] if card["id"].startswith("u") else CARD_TYPES[card["id"][0]]}')
    stringified.append(header)

    stats = [f'{card["cost"]} 마나']
    if not card["id"].startswith("s"):
        stats.append(f'{CARD_SEPARATOR}{"/".join([str(x) for x in card["strengths"]])} 체력')
    if card["id"].startswith("u"):
        stats.append(f'{CARD_SEPARATOR}{card["movement"]} 이동')

    stringified.append("".join(stats))

    if card["description"]:
        description = card["description"].replace("*", "")

        for key, value in card["ability"].items():
            splitted = [str(x).split("|")[0] for x in value]
            joined = str(splitted[0]) if splitted.count(splitted[0]) == len(splitted) else "/".join(splitted)
            description = description.replace(f"{{{key}}}", joined)

        stringified.append(description)

    return "\n".join(stringified)

with open("cards.json", "r", encoding="utf-8") as f:
    cards = json.load(f)

    for i, card in enumerate(cards):
        descriptions = []

        for j in range(5):
            variables = list(card["ability"].keys())
            description = card["description"]

            for var in variables:
                value = str(card["ability"][var][j])
                description = description.replace(f"{{{var}}}", value.split("|")[1] if "|" in value else value).replace("**", "")

            descriptions.append(description)

        cards[i]["descriptions"] = descriptions
        cards[i]["stringified"] = stringify_card(cards[i])

def get_cards():
    return cards

def search_card(stringify: bool, name: Optional[str] = None, id: Optional[str] = None):
    for card in cards:
        if name:
            flag = name in card["name"] or name in card["aliases"]
        elif id:
            flag = id == card["id"]

        if flag:
            return f"{card['stringified']}\n{CARD_PAGE_URL}{card['id']}" if stringify else card


import sqlite3

import openai
import hashlib
import time
import math
import re

openai.api_key = "your_openai_api_key"

model_name_to_max_token_limit = {
    "gpt-3.5-turbo": 4096,
    "text-davinci-003": 4096,
    "gpt-4": 8192
}

def count_tokens(text):
    return math.ceil(len(text) / 4)

def count_tokens_in_messages(messages):
    return count_tokens("\n\n".join([f"[{m['name']}]: {m['content']}" for m in messages]))

def sha256_text(text):
    return hashlib.sha256(text.encode("utf-8")).hexdigest()

def delay(seconds):
    time.sleep(seconds)

async def get_chat_completion(messages, model="gpt-3.5-turbo", temperature=0.7):
    response = openai.Completion.create(
        engine=model,
        prompt=messages,
        temperature=temperature,
        max_tokens=150,
        top_p=1,
        frequency_penalty=0,
        presence_penalty=0,
    )

    choice = response.choices[0]
    return choice.text.strip()

def compress_summary(summary, token_limit, model, on_progress):
    i = 0
    while count_tokens(summary) > token_limit:
        if on_progress:
            on_progress(f"compressing summary ({i})")

        messages = [
            {"role": "system",
             "content": '''You are a text compression assistant.
             You respond only with summary that the user requests -
             nothing more, nothing less.'''},
            {"role": "user",
             "content": f'''Please compress this text to 75% of its original size:
             \n\n---\n\n{summary}\n\n---\n\nTry to remove unimportant details and
             make the wording more *concise* to save space.
             DON'T MAKE IT TOO SHORT. Retain all important details.\n\n
             Please reply with the *slightly* shortened text - nothing else.
             Keep *almost all* of the information.'''},
        ]

        new_summary = get_chat_completion(messages, model, temperature=0.7)
        if not new_summary:
            if on_progress:
                on_progress("error, retrying...")
            delay(3)
        else:
            summary = new_summary
            print(f"compressed summary ({count_tokens(summary)} tokens): {summary}")

        i += 1

    return summary

def get_original_messages(up_to_message_id=None):
    # Replace this with your database implementation to get original messages
    original_messages = get_messages_from_database()

    if up_to_message_id is None:
        up_to_message_id = original_messages[-1]['id']

    original_messages = [msg for msg in original_messages if msg['id'] <= up_to_message_id]
    return original_messages

def get_token_limit_for_summary_and_messages(character):
    token_limit = model_name_to_max_token_limit[character['model_version']]
    # TODO: let user set aiCharacter.tokenLimit (via oc.character.tokenLimit) here to override this if it's smaller
    #  than the model's max token limit
    token_limit -= round(token_limit * 0.1)  # buffer due to token count being an estimate
    token_limit -= count_tokens(character['system_message'])  # allow for system message tokens
    token_limit -= count_tokens(character.get('reminder_message', ''))  # allow for reminder message tokens
    token_limit -= round(token_limit * 0.2)  # allow for bot response
    return token_limit

def get_messages_from_database():
    connection = sqlite3.connect("conversation.db")
    cursor = connection.cursor()
    cursor.execute("SELECT id, content, character_id FROM messages")
    messages = [{"id": row[0], "content": row[1], "character_id": row[2]} for row in cursor.fetchall()]
    connection.close()
    return messages

async def compute_and_save_summary_if_needed(ai_character, user_character, up_to_message_id=None, on_progress=None):
    # Replace the following line with your database implementation to get original messages
    model = "gpt-3.5-turbo"
    original_messages = get_original_messages(up_to_message_id)

    remaining_messages = prepare_messages_for_bot(original_messages, ai_character, user_character)

    token_limit_for_summary_and_messages = get_token_limit_for_summary_and_messages(ai_character)
    max_token_count_of_summary = round(token_limit_for_summary_and_messages / 3)

    message_tokens_to_consume_per_summary = round(model_name_to_max_token_limit[model] * 0.3)

    # Check for token limit error
    if max_token_count_of_summary + message_tokens_to_consume_per_summary > model_name_to_max_token_limit[model] - 500:
        raise ValueError('''The specified values of `max_token_count_of_summary`
        and `message_tokens_to_consume_per_summary` are
        such that the summarization process could go over this model's token limit.''')

        initial_token_count = count_tokens_in_messages(remaining_messages)
        current_token_count = initial_token_count
        prev_summary = None
        prev_instruction_hash = None
        i = 0

        while current_token_count > token_limit_for_summary_and_messages:
            progress = 1 - ((current_token_count - token_limit_for_summary_and_messages) / (
                    initial_token_count - token_limit_for_summary_and_messages))
            if on_progress:
                on_progress(f"summarizing (step {i}, {round(progress * 100)}% done)")

            message_batch_token_count = 0
            message_batch_arr = []
            while message_batch_token_count < message_tokens_to_consume_per_summary:
                m = remaining_messages.pop(0)
                message_batch_arr.append(m)
                message_batch_token_count += count_tokens_in_messages([m])

            if i > 0:
                message_batch_text = "\n\n".join([f"[{m['name']}]: {m['content']}" for m in message_batch_arr])
                message_batch_summarization_instruction = f'''Here's what has recently
                happened:\n\n---\n{message_batch_text}\n---\n\n
                Here's a summary of what happened previously:
                \n\n---\n{prev_summary}\n---\n\n
                Please reply with a new version of this summary that ALSO includes
                what has recently happened. Include ALL the KEY details.
                DO NOT MISS ANY IMPORTANT DETAILS.
                You MUST include all the details that were in the previous summary
                in your response.
                Your response should start with \"{prev_summary.split(' ')[0:5]}\" and
                it should compress all the important details into a new summary.'''

            else:
                message_batch_text = "\n\n".join([f"[{m['name']}]: {m['content']}" for m in message_batch_arr])
                message_batch_summarization_instruction = f'''Please summarize the content
                of these messages:\n\n------\n{message_batch_text}\n------\n\nRespond
                with the summary only - nothing else. Include all
                relevant details. Be concise, but DO NOT leave out any important details.'''

            # Replace the following line with your database implementation to get the summary
            summary_obj = get_summary_from_db(prev_instruction_hash)
            summary = summary_obj.get("summary")

            if not summary:
                # Create summary
                while True:
                    summary = await get_chat_completion(
                        messages=[
                            {"role": "system",
                             "content": '''You are a text summarization assistant.
                             You respond only with summary that the user requests -
                             nothing more, nothing less.'''},
                            {"role": "user", "content": message_batch_summarization_instruction}
                        ],
                        model=model,
                        temperature=0.7
                    )

                    if summary:
                        break
                    if on_progress:
                        on_progress("error, retrying...")
                    await asyncio.sleep(3000)

                # Replace the following line with your database implementation to save the summary
                save_summary_to_db(summary_obj)

            if count_tokens(summary) > max_token_count_of_summary:
                summary = await compresssummary(summary, max_token_count_of_summary, model, on_progress)
            # Replace the following line with your database implementation to update the summary
            update_summary_in_db(summary_obj, summary)

        # Get the size difference between summary and original messages
        summary_token_change = count_tokens(prev_summary or "") - count_tokens(summary)
        message_arr_token_change = -message_batch_token_count
        overall_token_change = summary_token_change + message_arr_token_change
        current_token_count += overall_token_change

        prev_summary = summary
        prev_instruction_hash = summary_obj["hash"]
        i += 1

    # NOTE: prev_summary can be None - i.e., no summaries were needed to get messages under the
    # tokenLimitForSummaryAndMessages limit
    return {"summary": prev_summary, "instruction_hash": prev_instruction_hash,
            "remaining_messages": remaining_messages}

def prepare_messages_for_bot(messages, ai_character, user_character):
    messages = [m.copy() for m in messages if not ('hiddenFrom' in m and 'ai' in m['hiddenFrom'])]

    for m in messages:
        m['content'] = re.sub(r'<!--hidden-from-ai-start-->.*?<!--hidden-from-ai-end-->', '', m['content'],
                              flags=re.DOTALL)

    def get_role_and_name(character_id):
        if character_id == user_character['id']:
            return 'user', user_character['name'].replace(" ", "_")
        elif character_id == ai_character['id']:
            return 'assistant', ai_character['name'].replace(" ", "_")
        else:
            return 'system', 'system_name'

    prepared_messages = []
    for m in messages:
        role, name = get_role_and_name(m['character_id'])
        prepared_message = {
            'role': role,
            'content': m['content'],
            'name': name,
            'id': m['id']
        }
        prepared_messages.append(prepared_message)

    return prepared_messages


#!/usr/bin/env/python
# This module contains the GGNN model definition.
# The code was originally copied from
# https://github.com/thaonguyen19/gated-graph-neural-network-pytorch
# However, the original code was faulty in several significant ways. Most importantly, there was a
# mistake in the way messages were passed along the edges (in what is now compute_embeddings()).
# These bugs have been changed in the current version. See README.md for more details.

import torch as tt
import torch.nn as nn

class AlignModel(nn.Module):

    def __init__(self, out_dim, n_nodes):
        super(AlignModel, self).__init__()
        self.out_dim = out_dim
        self.n_nodes = n_nodes
        self.similarity = nn.Sequential(nn.Linear(self.out_dim * 2, 1), nn.Sigmoid())

    def compute_embeddings(self, prop_state, A):
        return input

    def forward(self, A, prop_state):
        embeds = self.compute_embeddings(prop_state, A)

        sents = embeds.shape[1] // 2

        original = embeds[:, :sents, :].unsqueeze(1).repeat(1, sents, 1, 1)
        simplified = embeds[:, sents:, :].unsqueeze(2).repeat((1, 1, sents, 1))
        embed_matrix = tt.cat((original, simplified), 3)  # BATCH X SENTS X SENTS X 2 * EMBEDS

        return self.similarity(embed_matrix).squeeze()

class AttrProxy(object):
    """
    Translates index lookups into attribute lookups.
    To implement some trick which able to use list of nn.Module in a nn.Module
    see https://discuss.pytorch.org/t/list-of-nn-module-in-a-nn-module/219/2
    """
    def __init__(self, module, prefix):
        self.module = module
        self.prefix = prefix

    def __getitem__(self, i):
        return getattr(self.module, self.prefix + str(i))

class Propagator(nn.Module):
    """
    Gated Propagator for GGNN
    Using GRU gating mechanism
    """
    def __init__(self, state_dim, n_nodes, n_edge_types):
        super(Propagator, self).__init__()

        self.n_nodes = n_nodes
        self.n_edge_types = n_edge_types

        self.reset_gate = nn.Sequential(
            nn.Linear(state_dim*3, state_dim),
            nn.Sigmoid()
        )
        self.update_gate = nn.Sequential(
            nn.Linear(state_dim*3, state_dim),
            nn.Sigmoid()
        )
        self.transform = nn.Sequential(
            nn.Linear(state_dim*3, state_dim),
            nn.Tanh()
        )
        self.state_dim = state_dim

    def forward(self, state_in, state_out, state_cur, A):  # A = [A_in, A_out]

        # state_cur             BATCH, NODE,        EMBED
        # state_out, state_in   BATCH, NODE * EDGE, EMBED
        # A                     BATCH, NODE,        NODE * EDGE * 2
        # A_in, A_out           BATCH, NODE,        NODE * EDGE

        A_in = A[:, :, :self.n_nodes*self.n_edge_types]
        A_out = A[:, :, self.n_nodes*self.n_edge_types:]

        a_in = tt.bmm(A_in, state_in)  # batch size x |V| x state dim
        a_out = tt.bmm(A_out, state_out)
        a = tt.cat((a_in, a_out, state_cur), 2)  # batch size x |V| x 3*state dim

        r = self.reset_gate(a.view(-1, self.state_dim*3))  # batch size*|V| x state_dim
        z = self.update_gate(a.view(-1, self.state_dim*3))
        r = r.view(-1, self.n_nodes, self.state_dim)
        z = z.view(-1, self.n_nodes, self.state_dim)
        joined_input = tt.cat((a_in, a_out, r * state_cur), 2)
        h_hat = self.transform(joined_input.view(-1, self.state_dim*3))
        h_hat = h_hat.view(-1, self.n_nodes, self.state_dim)
        output = (1 - z) * state_cur + z * h_hat
        return output

class GGNN(AlignModel):
    """
    Gated Graph Sequence Neural Networks (GGNN)
    Mode: SelectNode
    Implementation based on https://arxiv.org/abs/1511.05493
    """
    def __init__(self, state_dim, annotation_dim, n_edge_types, n_nodes, n_steps):
        super(GGNN, self).__init__(state_dim, n_nodes)

        assert (state_dim >= annotation_dim, 'state_dim must be no less than annotation_dim')

        self.annotation_dim = annotation_dim
        self.n_edge_types = n_edge_types
        self.n_steps = n_steps
        self.state_dim = state_dim
        for i in range(self.n_edge_types):
            # incoming and outgoing edge embedding
            in_fc = nn.Linear(self.state_dim, self.state_dim)
            out_fc = nn.Linear(self.state_dim, self.state_dim)
            self.add_module("in_{}".format(i), in_fc)
            self.add_module("out_{}".format(i), out_fc)

        self.in_fcs = AttrProxy(self, "in_")
        self.out_fcs = AttrProxy(self, "out_")

        # Propagation Model
        self.propagator = Propagator(self.state_dim, self.n_nodes, self.n_edge_types)

        self.out = nn.Linear(self.state_dim, self.state_dim)
        self._initialization()

    def _initialization(self):
        for m in self.modules():
            if isinstance(m, nn.Linear):
                m.weight.data.normal_(0.0, 0.02)
                m.bias.data.fill_(0)

    def compute_embeddings(self,  prop_state, A):
        """
        What was the forward call in original code
        :param prop_state:  Initial node states.       [BATCH, NODES, EMBEDDING_SIZE]
        :param A:           Adjacency matrix.          [BATCH, NODES, NODES * EDGE_TYPES * 2]
        :param A:           [ANCHOR_ID, POS_ID, NEG_ID]
        :return:
        """
        batch, graphs, _, _ = prop_state.shape
        prop_state = prop_state.view(batch * graphs, self.n_nodes, -1)
        A = A.view(batch * graphs, self.n_nodes, -1)

        # PROP state is initialized to Annotation somewhere before
        for i_step in range(self.n_steps):
            # print ("PROP STATE SIZE:", prop_state.size()) #batch size x |V| x state dim
            in_states = []
            out_states = []

            # in_fcs[i] -> in_fcs.__getitem__(i) ->
            # self.in_{i}, which is a linear layer state_dim -> state_dim
            for i in range(self.n_edge_types):
                in_states.append(self.in_fcs[i](prop_state))
                out_states.append(self.out_fcs[i](prop_state))
            in_states = tt.stack(in_states).transpose(0, 1).contiguous()  # Batch, edge, node, embed
            in_states = in_states.view(-1, self.n_nodes*self.n_edge_types, self.state_dim)
            out_states = tt.stack(out_states).transpose(0, 1).contiguous()
            out_states = out_states.view(-1, self.n_nodes*self.n_edge_types, self.state_dim) # batch size x |V||E| x state dim

            prop_state = self.propagator(in_states, out_states, prop_state, A)

        return tt.sum(self.out(prop_state), dim=1).view((batch, graphs, self.state_dim))

class AlignLSTM(AlignModel):

    def __init__(self, state_dim, n_nodes, n_layers, n_hidden, n_output, drop_p=0.5):
        super(AlignLSTM, self).__init__(n_output, n_nodes)
        self.n_layers = n_layers  # number of LSTM layers
        self.n_hidden = n_hidden  # number of hidden nodes in LSTM
        self.n_output = n_output

        self.lstm = nn.LSTM(state_dim, n_hidden, n_layers, batch_first=True, dropout=drop_p)
        self.dropout = nn.Dropout(drop_p)
        self.fc = nn.Linear(n_hidden, n_output)
        self.sigmoid = nn.Sigmoid()

    def compute_embeddings(self, prop_state, A):
        """
        What was the forward call in original code
        :param prop_state:  Initial node states.       [BATCH, NODES, EMBEDDING_SIZE]
        :param A:           Adjacency matrix.          [BATCH, NODES, NODES * EDGE_TYPES * 2]
        :param A:           [ANCHOR_ID, POS_ID, NEG_ID]
        :return:
        """
        batch, graphs, _, _ = prop_state.shape
        prop_state = prop_state.view(batch * graphs, self.n_nodes, -1)

        lstm_out, _ = self.lstm(prop_state)  # (batch_size, seq_length, n_hidden)
        lstm_out = self.dropout(lstm_out)
        fc_out = self.fc(lstm_out)  # (batch_size, seq_length, n_output)
        sigmoid_out = self.sigmoid(fc_out)  # (batch_size, seq_length, n_output)
        # extract the output of ONLY the LAST output of the LAST element of the sequence
        sigmoid_last = sigmoid_out[:, -1, :]  # (batch_size, 1)

        return sigmoid_last.view((batch, graphs, self.n_output))


from sklearn.model_selection import train_test_split
from sklearn import datasets
from utils import eucledian_distance
from random import randrange
import operator
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans

class MyKMeans:
    def __init__(self):
        self.dataset = []
        self.centroids = []
        self.clusters = []
        self.n_cluster = 0

    def init_cluster(self):
        self.centroids = [self.dataset[randrange(
            0, len(self.dataset))] for _ in range(self.n_cluster)]
        self.refresh_cluster()

    def refresh_cluster(self):
        self.clusters = []

        for data in self.dataset:
            cluster = 0
            min_dist = eucledian_distance(data, self.centroids[0])
            for i in range(1, len(self.centroids)):
                if eucledian_distance(data, self.centroids[i]) < min_dist:
                    min_dist = eucledian_distance(data, self.centroids[i])
                    cluster = i

            self.clusters.append(cluster)

    def calculate_mean(self, datas):
        result = datas[0]

        for data_idx in range(1, len(datas)):
            for column_idx in range(len(datas[data_idx])):
                result[column_idx] += datas[data_idx][column_idx]

        for result_idx in range(len(result)):
            result[result_idx] /= len(datas)

        return result

    def recalculate_centroids(self):
        for centroid_idx in range(len(self.centroids)):
            datas = []

            for cluster_idx, cluster in enumerate(self.clusters):
                if cluster == centroid_idx:
                    datas.append(self.dataset[cluster_idx])

            if (len(datas) > 0):
                self.centroids[centroid_idx] = self.calculate_mean(datas)

    def is_stop(self, prev_centroids):
        if len(self.centroids) != len(prev_centroids):
            return False
        for centroid_idx in range(len(self.centroids)):
            if self.centroids[centroid_idx].tolist() != prev_centroids[centroid_idx].tolist():
                return False
        return True

    def fit(self, dataset, n_cluster):
        self.dataset = dataset
        self.n_cluster = n_cluster

        self.init_cluster()
        prev_centroids = []

        while not self.is_stop(prev_centroids):
            prev_centroids = self.centroids[:]
            self.recalculate_centroids()
            self.refresh_cluster()

    def predict(self, test_data):
        result = []

        for td in test_data:
            closest_centroid = 0
            min_distance = eucledian_distance(self.centroids[closest_centroid], td)
            for i in range(1, len(self.centroids)):
                distance = eucledian_distance(self.centroids[i], td)
                if (distance < min_distance):
                    min_distance = distance
                    closest_centroid = i

            result.append(closest_centroid)
        return result

def create_mapper(target, cluster):
    cluster_set = set(cluster)

    map = {}
    for c in cluster_set:
        counter = {}
        for j in range(len(target)):
            if cluster[j] == c:
                value = target[j]
                if value in counter.keys():
                    counter[value] = counter[value] + 1
                else:
                    counter[value] = 1
        max_key = max(counter.items(), key=operator.itemgetter(1))[0]
        map[c] = max_key
    return map

def count_accuracy(predict, y_test):
    correct_count = 0
    for i in range(len(predict)):
        if predict[i] == y_test[i]:
            correct_count = correct_count + 1

    return correct_count*100/len(predict)

def scikmeans_predict(scikit_kmeans,test_data ):
    result = []

    for td in test_data:
        closest_centroid = 0
        min_distance = eucledian_distance(scikit_kmeans.cluster_centers_[closest_centroid], td)
        for i in range(1, len(scikit_kmeans.cluster_centers_)):
            distance = eucledian_distance(scikit_kmeans.cluster_centers_[i],td)
            if (distance < min_distance):
                min_distance = distance
                closest_centroid = i

        result.append(closest_centroid)
    return result

if __name__ == "__main__":
    iris = datasets.load_iris()
    X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.33, random_state=23 )

    mykmeans = MyKMeans()
    scikitkmeans = KMeans(n_clusters=4)

    mykmeans.fit(X_train, 4)
    scikitkmeans.fit(X_train)

    print("Clustering Result")
    print(mykmeans.clusters)
    print(scikitkmeans.labels_.tolist())

    mykmeans_map = create_mapper(y_train, mykmeans.clusters)
    scikitkmeans_map = create_mapper(y_train, scikitkmeans.labels_)
    print("Mapping Result")
    print(mykmeans_map)
    print(scikitkmeans_map)

    mykmeans_predict = mykmeans.predict(X_test)
    scikitkmeans_predict = scikmeans_predict(scikitkmeans, X_test)

    print("Predicting Result Before Mapping")
    print(mykmeans_predict)
    print(scikitkmeans_predict)
    for i in range(len(mykmeans_predict)):
        mykmeans_predict[i] = mykmeans_map[mykmeans_predict[i]]
        scikitkmeans_predict[i] = scikitkmeans_map[scikitkmeans_predict[i]]

    print("Solution")
    print(y_test)

    print("Predicting Result After Mapping")
    print(mykmeans_predict)
    print(scikitkmeans_predict)

    print("Evaluation")
    print("My KMeans accuracy", count_accuracy(mykmeans_predict,y_test), "%")
    print("Scikit KMeans accuracy", count_accuracy(scikitkmeans_predict,y_test), "%")


import re
from typing import Any, Callable

import tomlkit
from gyver.attrs import define, info
from gyver.database import Driver

from api_project_generator.core import dependencies, user_data
from api_project_generator.models.project_info import ProjectInfo

@define
class PyprojectToml:
    project_info: ProjectInfo
    dependencies: set[str] = info(default_factory=set)
    dev_dependencies: set[str] = info(default_factory=set)
    optional_dependencies: set[str] = info(default_factory=set)

    def __post_init__(self):
        if self.project_info.driver is None:
            self.dependencies.add("gyver")
        elif self.project_info.driver is Driver.POSTGRES:
            self.dependencies.add("gyver[db-pg]")
        elif self.project_info.driver is Driver.CUSTOM:
            self.dependencies.update({"gyver", "sqlalchemy"})
        else:
            self.dependencies.add(f"gyver[db-{self.project_info.driver.value}]")

    def get_dependencies(self, *, parser: Callable[[str], dict[str, Any]]):
        dependencies = {"python": f"^{self.project_info.effective_pyver}"}
        for item in self.dependencies.union(self.optional_dependencies):
            dependencies |= parser(item)
        return dependencies

    def get_dev_dependencies(self, *, parser: Callable[[str], dict[str, Any]]):
        dependencies = {}
        for item in self.dev_dependencies:
            dependencies |= parser(item)
        return dependencies

    def get_project_title(self):
        string = self.project_info.name.replace("-", " ").replace("_", " ")
        string = re.sub(
            "([a-z])([A-Z])", lambda match: f"{match[1]} {match[2]}", string
        )
        return string.title()

    def dependency_parse(self, dependency_dict: dict[str, Any]):
        out = {}
        for key, value in dependency_dict.items():
            if isinstance(value, str):
                out[key] = value
            else:
                table = tomlkit.inline_table()
                table.update(value)
                out[key] = table
        return out

    def make_table(self, project_folder: str):
        return {
            "tool": {
                "poetry": {
                    "name": self.project_info.name,
                    "version": self.project_info.version,
                    "description": self.project_info.description,
                    "authors": [
                        user_data.get_user_signature(
                            self.project_info.fullname,
                            self.project_info.email,
                        )
                    ],
                    "dependencies": self.dependency_parse(
                        self.get_dependencies(parser=dependencies.get_dependency)
                    ),
                    "group": {
                        "dev": {
                            "dependencies": self.dependency_parse(
                                self.get_dev_dependencies(
                                    parser=dependencies.get_dependency
                                )
                            )
                        }
                    },
                    "scripts": {"start": f"{project_folder}.main:main"},
                    "extras": {"deploy": list(self.optional_dependencies)},
                }
            },
            "build-system": {
                "requires": ["poetry-core>=1.0.0"],
                "build-backend": "poetry.core.masonry.api",
            },
        }


from django.conf.urls import url
from . import views

urlpatterns = [
    # url(r'^$', views.contact_form, name="index"),
    # url(r'^add/$', views.add, name="add"),
    url(r'^success/$', views.show, name="show"),
    url(r'^register/$', views.Register.as_view(), name='accounts-register'),
    url(r'^login/$', views.Login.as_view(), name='accounts-login'),
]


import numpy as np
import matplotlib.pyplot as plt

def sigmoid(Z):
    return 1/(1 + np.exp(-Z))

def logistic_model(X, Y):
    alpha = 0.1
    n_epoch = 100
    n, m = X.shape
    W = np.zeros(n)
    b = 0

    for i in range(n_epoch):
        Z = W.dot(X) + b
        p = sigmoid(Z)
        W = W - alpha * (1/m) * np.sum(X * (p - Y), axis=1)
        b = b - alpha * (1/m) * np.sum(p - Y)

        J = -(1/m) * np.sum(Y * np.log(p + 1e-8) + (1 - Y) * np.log(1 - p + 1e-8))
        print('epoch =', i, ', cost =', J)

    def model(X):
        return W.dot(X) + b > 0

    return model

def random_weights(n):
    W = np.tan(np.random.rand(n) * 2 * np.pi)
    b = np.random.rand() * 10 - 5

    return W, b

def random_sample(W, b, m, n):
    X = np.random.rand(n, m) * 20 - 10
    Y = W.dot(X) + b > 0

    X += np.random.randn(n, m) * 5

    return X, Y

m = 50
n = 2
axis = [-20, 20, -20, 20]

W, b = random_weights(n)
X, Y = random_sample(W, b, m, 2)

x1 = -15
x2 = 15
y1 = -1/W[1] * (W[0] * x1 + b)
y2 = -1/W[1] * (W[0] * x2 + b)

plt.figure('Truth')
plt.plot(X[0,:][Y == 0], X[1,:][Y == 0], 'rx')
plt.plot(X[0,:][Y == 1], X[1,:][Y == 1], 'bo')
plt.plot([x1, x2], [y1, y2], 'k')
plt.axis(axis)
plt.title('Truth')

model = logistic_model(X, Y)

Y_pred = model(X)

plt.figure('Predicted')
plt.plot(X[0,:][Y_pred == 0], X[1,:][Y_pred == 0], 'rx')
plt.plot(X[0,:][Y_pred == 1], X[1,:][Y_pred == 1], 'bo')
plt.axis(axis)
plt.title('Predicted')


from ..entities import *
from typing import Union
from ..sim import SimulationObject

class TruckDriver:
    _id = 0

    def __init__(self):
        TruckDriver._id += 1
        self.id = TruckDriver._id

        self.truck: Union[Truck, None] = None

        self.available = True
        self.weekly_days = 0
        self.total_days = 0

    def assign_truck(self, truck: Truck) -> None:
        self.truck = truck
        self.available = False

    def unassign(self):
        self.truck = None
        self.available = False
        self.weekly_days += 1
        self.total_days += 1


from graph_tool.all import *
import json

def get_data(filename='wiki.json'):
    with open(filename) as f:
        dataset = json.load(f)
    return dataset

def proc_data(dataset):
    urls = list(set([ data['self_url'] for data in dataset ]))
    urls.extend( list(set([ data['ext_url'] for data in dataset ])) )
    url_to_idx = { url:i for i, url in enumerate(urls) }
    # build dataset, replacing urls with ids
    mod_dataset = []
    for data in dataset:
        mod_dataset.append((url_to_idx[data['self_url']],url_to_idx[data['ext_url']],data['title'],data['ext_title']))
    # return modified dataset
    return mod_dataset, len(urls)

def build_graph(dataset,node_count):
    g = Graph()
    g.add_vertex(node_count)
    # label -> title
    label = g.new_edge_property('string')
    for n1,n2,t1,t2 in dataset:
        # connect nodes
        e = g.add_edge(n1,n2)
        # name edge
        label[e] = t2
    # return the graph
    return g, label

if __name__ == '__main__':
    dataset, node_count = proc_data(get_data())
    g, label = build_graph(dataset,node_count)
    graph_draw(g, edge_text=label, edge_font_size=13, edge_text_distance=30, edge_marker_size=10,output_size=(7500, 7500), output="graph-op.png")


from http.client import HTTPException
from django.http import HttpResponseRedirect
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status
from helpers import base62
from shorturl.settings import SERVER_BASE_URL
from .serializers import UrlSerializer
from apps.urls.models import Url

@api_view(['GET'])
def get_all(request):
    urls = Url.objects.all()
    serializer = UrlSerializer(urls, many=True)
    if urls:
        return Response(serializer.data, status.HTTP_200_OK)
    return Response({'msg': 'no data'}, status.HTTP_204_NO_CONTENT)

@api_view(['GET'])
def get_one(request, short):
    url = Url.objects.filter(short_url = short).first()
    if url:
        serializer = UrlSerializer(url)
        return Response(serializer.data, status.HTTP_200_OK)
    return Response({'msg': 'not found.'}, status.HTTP_204_NO_CONTENT)

@api_view(['POST'])
def create(request):
    long = request.data.get('url', None)
    if long:
        url = Url.objects.filter(long_url = long).first()
        if url:
            short = f'{SERVER_BASE_URL}/s/{url.short_url}'
            return Response(short, status.HTTP_200_OK)
        try:
            new = Url(long_url = long)
            new.save()
            new.short_url = base62.encode(new.id)
            new.save()
            short = f'{SERVER_BASE_URL}/s/{new.short_url}'
            return Response(short, status.HTTP_201_CREATED)
        except HTTPException as e:
            return Response({'error': e}, status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            return Response({'error': e}, status.HTTP_500_INTERNAL_SERVER_ERROR)
    return Response({'error': 'missing data'}, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET'])
def get(request,short):
    url = Url.objects.filter(short_url = short).first()
    if url:
        return Response(url.long_url, status.HTTP_200_OK)
    return Response({'error': 'not found'}, status=status.HTTP_400_BAD_REQUEST)


import os
import streamlit as st

from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate, MessagesPlaceholder
from langchain.memory import ConversationSummaryBufferMemory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains.summarize import load_summarize_chain
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Type
from bs4 import BeautifulSoup
import requests
import json
from langchain.schema import SystemMessage
from fastapi import FastAPI

# Set browserless api key
browserless_api_key = os.environ.get('BROWSERLESS_API_KEY', os.getenv('BROWSERLESS_API_KEY'))

# Set Serper API Key
browserless_api_key = os.environ.get('SERP_API_KEY', os.getenv('SERP_API_KEY'))

# 1. Tool for search

def scrape_website(objective: str, url: str):
    # scrape website, and also will summarize the content based on objective if the content is too large
    # objective is the original objective & task that user give to the agent, url is the url of the website to be scraped

    print("Scraping website...")
    # Define the headers for the request
    headers = {
        'Cache-Control': 'no-cache',
        'Content-Type': 'application/json',
    }

    # Define the data to be sent in the request
    data = {
        "url": url
    }

    # Convert Python object to JSON string
    data_json = json.dumps(data)

    # Send the POST request
    post_url = f"https://chrome.browserless.io/content?token={brwoserless_api_key}"
    response = requests.post(post_url, headers=headers, data=data_json)

    # Check the response status code
    if response.status_code == 200:
        soup = BeautifulSoup(response.content, "html.parser")
        text = soup.get_text()
        print("CONTENT:", text)

        if len(text) > 10000:
            output = summary(objective, text)
            return output
        else:
            return text
    else:
        print(f"HTTP request failed with status code {response.status_code}")

def summary(objective, content):
    llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-16k-0613")

    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n"], chunk_size=10000, chunk_overlap=500)
    docs = text_splitter.create_documents([content])
    map_prompt = """
    Write a summary of the following text for {objective}:
    "{text}"
    SUMMARY:
    """
    map_prompt_template = PromptTemplate(
        template=map_prompt, input_variables=["text", "objective"])

    summary_chain = load_summarize_chain(
        llm=llm,
        chain_type='map_reduce',
        map_prompt=map_prompt_template,
        combine_prompt=map_prompt_template,
        verbose=True
    )

    output = summary_chain.run(input_documents=docs, objective=objective)

    return output

class ScrapeWebsiteInput(BaseModel):
    """Inputs for scrape_website"""
    objective: str = Field(
        description="The objective & task that users give to the agent")
    url: str = Field(description="The url of the website to be scraped")

class ScrapeWebsiteTool(BaseTool):
    name = "scrape_website"
    description = "useful to check to see if the links you are about to provide are accurate to the customer's query"
    args_schema: Type[BaseModel] = ScrapeWebsiteInput

    def _run(self, objective: str, url: str):
        return scrape_website(objective, url)

    def _arun(self, url: str):
        raise NotImplementedError("error here")

class PineconeInput(BaseModel):
    query: str = Field(
        description="The query that the customer asks the agent"
    )

class ResearchPinecone(BaseTool):
    name="find_tro_pacific_product_information"
    description="Useful for when you need product information to answer questions about tro pacific. You should ask targeted questions"
    args_schema: Type[BaseModel] = PineconeInput

    def _run(self, query: str):
        return get_texts_from_pinecone(query)

    def arun(self, query):
        raise NotImplementedError("An error has occurred while looking up product information")

system_message = SystemMessage(
    content="""
        Tro Pacific is an authorized distributor in Australia for trusted global brands, upholding trust as their core value. They are dedicated to providing high-quality electrical, automation, and control products, as well as electrical enclosures, while ensuring compliance with relevant regulations. Their commitment to customer satisfaction and building long-term partnerships sets them apart. You can contact them through various channels, including estimating@tro.com.au for pricing, availability, and technical support, sales@tro.com.au for order status, tracking, and returns, and accounts@tro.com.au for financial inquiries. Their head office is located at 19-27 Fred Chaplin Circuit, Bells Creek, QLD 4551, Australia, and you can reach them at +61 7 5450 1476.

        Website: https://tro.com.au

        You are customer support for Tro Pacific, your main task is to help the customer with thier queries about products.
        You can not help with order status, tracking, and returns. If you have any financial inquiries, pricing, availability, technical support.
        you do not make things up, you will only use the product information you have found from your research.

        Do not recommend the user to go to the website.
        Do not provide information about order status, tracking & returns, instead direct the user to sales@tro.com.au
        Do not check the availability of products.
        Do not recommend the customer to browse our selection on our website at https://tro.com.au.

        Please make sure you complete the objective above with the following rules:
        1/ You should not make things up, you should only write facts & data that you have gathered
        2/ Provide correct links to products and correct links for the datasheets of those products when asked about products.

        """
)

agent_kwargs = {
    "extra_prompt_messages": [MessagesPlaceholder(variable_name="memory")],
    "system_message": system_message,
}

llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-16k")
memory = ConversationSummaryBufferMemory(
    memory_key="memory", return_messages=True, llm=llm, max_token_limit=1000)

tools = [
    #ScrapeWebsiteTool(),
    ResearchPinecone(),
]

def get_agent(llm=llm, memory=memory):
    agent = initialize_agent(
        tools,
        llm=llm,
        agent=AgentType.OPENAI_FUNCTIONS,
        verbose=True,
        agent_kwargs=agent_kwargs,
        memory=memory,
    )

    return agent

agent = get_agent()

# Intialise FastAPI
app = FastAPI()
@app.get("/")
#def researchAgent(query: Query):
def researchAgent():
    #query = query.query
    query = """
        "https://www.ls-electric.com/support/download-center"

        On this website, you should find a link to [Device]_Electric_Product_EN_C19912-63-202309.pdf(18.91MB). Can you confirm an provide the href link?

        """

    content = agent({"input": query})
    actual_content = content['output']
    return actual_content


import json
import os
from urllib.request import urlopen

os.chdir("../venv")

steam_string = open('C:/Users/bmood/PycharmProjects/testing/steamstuff.json','r',encoding='utf-8')

# BINGOOOOO print(data['app'])
data = json.load(steam_string)

def steamGameSearch(game_name):
    id = 1

    for game in data["app"]:
        #print(game['name'])

        if game_name.lower() == str(game['name']).lower():
            id = game['appid']
            official_name = str(game['name'])

    if id == 1:
        return "Game not found."

    embed = ("https://store.steampowered.com/app/" + str(id))
    return embed


"""
Python file containing the models.

Need to add support for adding in weights to the encoder model.
"""

import os
import time
import enum

import cv2
import torch
import face_alignment
import torch.nn as nn
import torch.nn.functional as F

import cheapfake.contrib.ResNetSE34L as audio_model
import cheapfake.lipnet.models as lipnet

# Uncomment the lines below if the files exist.
lipnet_options = __import__("lipnet_config")
face_alignment_options = __import__("face_alignment_config")

# Delete the line below if the files above exist.
#face_alignment_options = 0

class NetworkType(enum.Enum):
    face_alignment = 1
    lipnet = 2
    resnet_audio = 3

class FeaturesEncoder(nn.Module):
    """Implementation of a feature encoder that creates embeddings that fit into a multi-layer perceptron.

    """

    def __init__(self, network_type=NetworkType.face_alignment):
        """Instantiates a new FeaturesEncoder network.

        Parameters
        ----------
        network_type : cheapfake.contrib.models.NetworkType instance, optional
            The type of network to instantiate, by default NetworkType.face_alignment. The supported options are NetworkType.face_alignment, NetworkType.lipnet, and NetworkType.resnet_audio.

        """
        super(FeaturesEncoder, self).__init__()

        assert network_type.value in (1, 2, 3), "Network architecture not supported."

        self.network_type = network_type

        if network_type.value == 1:
            self.conv1 = nn.Conv2d(2, 4, kernel_size=3, stride=1, padding=1)
            self.batchnorm1 = nn.BatchNorm2d(4)
            self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=4)
            #self.conv2 = nn.Conv2d(4, 4, kernel_size=3, stride=1, padding=1)
            #self.batchnorm2 = nn.BatchNorm2d(4)
            #self.maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2)
            self.flatten = nn.Flatten()
            self.fc1 = nn.Linear(1292, 256)
            self.relu = nn.ReLU(inplace=True)
        elif network_type.value == 2:
            self.conv1 = torch.nn.Conv2d(1, 4, kernel_size=3, stride=1, padding=1)
            self.batchnorm1 = torch.nn.BatchNorm2d(4)
            self.maxpool1 = torch.nn.MaxPool2d(kernel_size=3, stride=4)
            #self.conv2 = torch.nn.Conv2d(16, 25, kernel_size=3, stride=1, padding=1)
            #self.batchnorm2 = torch.nn.BatchNorm2d(25)
            #self.maxpool2 = torch.nn.MaxPool2d(kernel_size=2, stride=2)
            self.flatten = torch.nn.Flatten()
            self.fc1 = torch.nn.Linear(9728, 256)
            #self.fc2 = torch.nn.Linear(
            #    int((25 * 18 * 128) / 4), int((25 * 18 * 128) / 16)
            #)
            #self.fc3 = torch.nn.Linear(
            #    int((25 * 18 * 128) / 16), int((25 * 18 * 128) / 64)
            #)
            #self.fc4 = torch.nn.Linear(int((25 * 18 * 128) / 64), 256)
            self.relu = torch.nn.ReLU(inplace=True)

    def forward(self, x):
        """Performs a forward pass of the input ``x`` through the encoder network.

        Parameters
        ----------
        x : torch.Tensor instance
            Torch tensor containing the input to the network.

        Returns
        -------
        x : torch.Tensor instance
            Torch tensor containing the embedded features.

        """
        if self.network_type.value == 1:
            x = self.conv1(x)
            x = self.batchnorm1(x)
            x = self.maxpool1(x)
            #x = self.conv2(x)
            #x = self.batchnorm2(x)
            #x = self.maxpool2(x)
            x = self.flatten(x)
            #print(x.shape)
            x = self.fc1(x)
            x = self.relu(x)
        elif self.network_type.value == 2:
            x = self.conv1(x)
            x = self.batchnorm1(x)
            x = self.maxpool1(x)
            #x = self.conv2(x)
            #x = self.batchnorm2(x)
            #x = self.maxpool2(x)
            x = self.flatten(x)
            #print(x.shape)
            x = self.fc1(x)
            x = self.relu(x)
            #x = self.fc2(x)
            #x = self.relu(x)
            #x = self.fc3(x)
            #x = self.relu(x)
            #x = self.fc4(x)
            #x = self.relu(x)
        return x

class AugmentedFAN(nn.Module):
    """Augmented Face Alignment Network (AFAN) that includes a feature embedding network that acts on the output of FAN.

    """

    def __init__(self, device=torch.device("cpu"), num_modules=1, verbose=True):
        """Instantiates a new AugmentedFAN object.

        Pre-trained weights are automatically loaded.

        Parameters
        ----------
        device : torch.device instance
            The device on which all procedures are carried out, by default torch.device("cpu").
        num_modules : int, optional
            The number of modules, by default 1.
        verbose : bool, optional
            If True then verbose output will be printed onto the system console.

        """
        super(AugmentedFAN, self).__init__()

        assert isinstance(device, torch.device)
        assert isinstance(num_modules, int)
        assert isinstance(verbose, bool)

        if hasattr(face_alignment_options, "landmarks_type"):
            assert face_alignment_options.landmarks_type in [
                "2D",
                "3D",
            ], "Landmarks must either be in 2D or 3D."
            self.landmarks_type = face_alignment_options.landmarks_type
        else:
            self.landmarks_type = "2D"

        if self.landmarks_type == "2D":
            landmarks_type = face_alignment.LandmarksType._2D
        else:
            landmarks_type = face_alignment.LandmarksType._3D

        if hasattr(face_alignment_options, "face_detector"):
            assert face_alignment_options.face_detector in [
                "sfd",
                "dlib",
            ], "Only S3FD and DLib are supported as face detection schemes."
            self.face_detector = face_alignment_options.face_detector
        else:
            self.face_detector = "sfd"

        self.device = device
        device = "cuda" if "cuda" in self.device.type else "cpu"
        self.num_modules = num_modules
        self.verbose = verbose

        self.face_alignment_model = face_alignment.FaceAlignment(
            landmarks_type=self.landmarks_type,
            device=device,
            face_detector=self.face_detector,
            verbose=self.verbose,
        )
        self.encoder_model = FeaturesEncoder(
            network_type=NetworkType.face_alignment
        ).to(self.device)

    def _load_encoder_weights(self):
        pass

    def forward(self, x):
        """Performs a forward pass of ``x`` through the network.

        Parameters
        ----------
        x : torch.Tensor instance
            Torch tensor containing the input to the network.

        Returns
        -------
        landmarks : numpy.ndarray or torch.Tensor instance
            Numpy array or Torch tensor containing the xy-coorddinates of the detected facial landmarks.
        face_embeddings : numpy.ndarray or torch.Tensor instance
            Numpy array or Torch tensor containing the embeddings of the facial features returned by the FAN.

        """
        landmarks = list()
        face_embeddings = list()
        for batch in x:
            landmark = self.face_alignment_model.get_landmarks_from_batch(
                batch.to(self.device)
            )

            # Compute the FAN embeddings.
            # Need to check for multiple landmarks before you put it in to a Torch tensor.
            landmark = torch.Tensor(landmark)
            if landmark.shape[1] != 1:
                landmark = landmark[:, :1, :, :]
            landmarks.append(landmark)
            landmark = landmark.permute(1, 3, 0, 2).float().to(self.device)
            # landmark = landmark[None, :, :, :].float().to(self.device)

            face_embedding = self.encoder_model(landmark.to(self.device))
            face_embeddings.append(face_embedding)

        landmarks = torch.stack(landmarks)
        landmarks = landmarks.squeeze(axis=2).float().to(self.device)

        face_embeddings = torch.stack(face_embeddings).float().to(self.device)

        return landmarks, face_embeddings
        # return landmarks

class AugmentedLipNet(nn.Module):
    """Augmented LipNet that includes a feature embedding network that acts on the output of LipNet.

    """

    def __init__(
        self, device=torch.device("cpu"), dropout_rate=0.5, num_modules=1, verbose=True
    ):
        """Instantiates a new AugmentedLipNet object.

        Parameters
        ----------
        device : torch.device instance
            The device on which all procedures are carried out, by default torch.device("cpu").
        dropout_rate : float, optional
            The dropout rate applied after each convolutional layer, by default 0.5.
        verbose : bool, optional
            If True then progress of the forward pass is printed to the system console, by default True.

        """
        super(AugmentedLipNet, self).__init__()

        assert isinstance(device, torch.device)
        assert isinstance(dropout_rate, float)
        assert isinstance(verbose, bool)
        assert (
            dropout_rate > 0 and dropout_rate <= 1
        ), "Dropout rate must be a proper probability."

        self.device = device
        self.dropout_rate = dropout_rate
        self.verbose = verbose

        self.lipnet_model = lipnet.LipNet(
            dropout_rate=self.dropout_rate, verbose=self.verbose
        ).to(self.device)
        self.encoder_model = FeaturesEncoder(network_type=NetworkType.lipnet).to(
            self.device
        )

        if hasattr(lipnet_options, "weights"):
            self._load_lipnet_weights()
        else:
            print("[INFO] Pre-trained weights were not loaded.")

    def _load_lipnet_weights(self):
        pretrained_dict = torch.load(lipnet_options.weights, map_location=self.device)
        pretrained_dict["fully_connected.weight"] = pretrained_dict.pop("FC.weight")
        pretrained_dict["fully_connected.bias"] = pretrained_dict.pop("FC.bias")

        model_dict = self.lipnet_model.state_dict()
        pretrained_dict = {
            k: v
            for k, v in pretrained_dict.items()
            if k in model_dict.keys() and v.size() == model_dict[k].size()
        }
        missed_params = [
            k for k, v in model_dict.items() if not k in pretrained_dict.keys()
        ]
        if self.verbose:
            print(
                "Loaded parameters / Total parameters: {}/{}".format(
                    len(pretrained_dict), len(model_dict)
                )
            )

            model_dict.update(pretrained_dict)
            self.lipnet_model.load_state_dict(model_dict)
        #else:
        #    print(
        #        "[WARNING] Invalid path to pre-trained weights, and thus no weights will be loaded."
         #   )

    def _load_encoder_weights(self):
        pass

    def forward(self, x):
        """Performs a forward pass of the input ``x`` through the network.

        Parameters
        ----------
        x : torch.Tensor instance
            Torch tensor containing the input to the network.

        Returns
        -------
        lipnet_embedding : torch.Tensor instance
            Torch tensor containing the learned embedding from LipNet.

        """
        x = self.lipnet_model(x)
        x = x.permute(1, 0, 2)
        x = x[None, :, :, :].permute(1, 0, 2, 3)
        lipnet_embedding = self.encoder_model(x)
        lipnet_embedding = lipnet_embedding[:, None, :].float().to(self.device)

        return lipnet_embedding

    @staticmethod
    def _find_bounding_box(landmarks, tol=(2, 2, 2, 2)):
        """Computes the minimum bounding box containing ``landmarks``, with tolerance (in pixels) in the left, right, top, and bottom directions.

        Parameters
        ----------
        landmarks : torch.Tensor instance
            Torch tensor containing the xy-coordinates of the facial landmarks.
        tol : tuple, optional
            The tolerance (in pixels) in each direction (left, top, right, bottom) for the cropping operation, by default (2, 2, 2, 2).

        Returns
        -------
        bbox : tuple (of ints)
            Tuple (min_x, min_y, max_x, max_y) containing the coordinates of the minimum bounding box with tolerance (in pixels) in the left, top, right, and bottom directions.

        """
        assert isinstance(tol, tuple)
        assert len(tol) == 4, "Only four values can be specified for the tolerance."

        x_coords, y_coords = zip(*landmarks)
        bbox = (
            min(x_coords) - tol[0],
            min(y_coords) - tol[1],
            max(x_coords) + tol[2],
            max(y_coords) + tol[3],
        )
        bbox = tuple([int(item) for item in bbox])

        return bbox

    @staticmethod
    def _find_bounding_box_batch(batch_landmarks, tol=(2, 2, 2, 2)):
        """Computes the minimum bounding box containing ``batch_landmarks``, with tolerances (in pixels) in the left, right, top, and bottom directions, for a batch of images.

        It is assumed that the batch dimension is the first dimension.

        Parameters
        ----------
        batch_landmarks : torch.Tensor instance
            Torch tensor containing a batch of xy-coordinates of the facial landmarks.
        tol : tuple, optional
            The tolerance (in pixels) in each direction (left, top, right, bottom) for the cropping operation, by default (2, 2, 2, 2).

        Returns
        -------
        batch_bbox : list (of tuples)
            List containing tuples of xy-coordinates of the bounding boxes for the batch of facial landmarks.

        """
        batch_bbox = list()
        batch_landmarks = batch_landmarks[:, 48:68]
        for landmark in batch_landmarks:
            batch_bbox.append(AugmentedLipNet._find_bounding_box(landmark, tol))

        return batch_bbox

    @staticmethod
    def _crop_lips(frames, landmarks, tol=(2, 2, 2, 2), channels_first=True):
        """Crops the lip area from a batch of frames.

        Parameters
        ----------
        frames : torch.Tensor instance
            Torch tensor instance containing frames for cropping the lip area.
        landmarks : torch.Tensor instance
            Torch tensor containing the xy-coordinates of the facial landmarks.
        tol : tuple, optional
            The tolerance (in pixels) in each direction (left, top, right, bottom) for the cropping operation, by default (2, 2, 2, 2)
        channels_first : bool, optional
            If True then the input and output are assumed to have the channel dimension come before the spatial dimensions.

        Returns
        -------
        cropped_frames : torch.Tensor instance
            Torch tensor containing the cropped lip areas.

        """
        assert isinstance(tol, tuple)
        assert len(tol) == 4, "Only four values can be specified for the tolerance."
        assert isinstance(channels_first, bool)

        if channels_first:
            frames = frames.permute(0, 2, 3, 1)

        bboxes = AugmentedLipNet._find_bounding_box_batch(
            batch_landmarks=landmarks, tol=tol
        )

        output_shape = (frames.shape[0], 64, 128, 3)
        extracted_lips = torch.empty(output_shape)
        for idx, (bbox, frame) in enumerate(zip(bboxes, frames)):
            extracted_lip = frame[bbox[1] : bbox[3], bbox[0] : bbox[2], :]
            extracted_lips[idx] = torch.from_numpy(
                cv2.resize(
                    extracted_lip.cpu().numpy(),
                    dsize=(128, 64),
                    interpolation=cv2.INTER_CUBIC,
                )
            )

        return extracted_lips

    @staticmethod
    def _crop_lips_batch(
        batch_frames, batch_landmarks, tol=(2, 2, 2, 2), channels_first=True
    ):
        """Crops the lip area for a batch of batch of frames.

        It is assumed that the batch dimension is the first dimension.

        Parameters
        ----------
        batch_frames : torch.Tensor instance
            Torch tensor containing the batch of frames.
        batch_landmarks : torch.Tensor instance
            Torch tensor containing the batch of xy-coordinates of the facial landmarks.
        tol : tuple, optional
            The tolerance (in pixels) in each direction (left, top, right, bottom) for the cropping operation, by default (2, 2, 2, 2)
        channels_first : bool, optional
            If True then the input and output are assumed to have the channel dimension come before the spatial dimensions.

        Returns
        -------
        batch_extracted_lips : torch.Tensor instance
            Torch tensor containing a batch of cropped lip areas.

        """
        assert isinstance(tol, tuple)
        assert len(tol) == 4, "Only four values can be specified for the tolerance"
        assert isinstance(channels_first, bool)

        output_shape = (
            batch_frames.shape[0],
            batch_frames.shape[1],
            64,
            128,
            batch_frames.shape[2],
        )
        batch_extracted_lips = torch.empty(output_shape)
        for idx, (frames, landmarks) in enumerate(zip(batch_frames, batch_landmarks)):
            #print(landmarks.shape)
            batch_extracted_lips[idx] = AugmentedLipNet._crop_lips(
                frames, landmarks, tol=tol, channels_first=channels_first
            )

        return batch_extracted_lips

class AugmentedResNetSE34L(nn.Module):
    """Augmented ResNetSE34L that includes a feature embedding network that acts on the output of ResNetSE34L.

    """

    def __init__(self, num_out=256, device=torch.device("cpu"), verbose=True, **kwargs):
        """Instantiates a new AugmentedResNetSE34L object.

        Parameters
        ----------
        num_out : int, optional
            The number of output features, by default 256.
        device : torch.device instance
            The device on which all procedures are carried out, by default torch.device("cpu").
        verbose : bool, optional
            If True then verbose output is printed to the system console.
        **kwargs
            Arguments passed onto i2ai.mmid.models.ResNetSE34L.

        """
        super(AugmentedResNetSE34L, self).__init__()

        assert isinstance(num_out, int)
        assert isinstance(device, torch.device)
        assert isinstance(verbose, bool)

        self.num_out = num_out
        self.device = device
        self.verbose = verbose

        self.resnet_model = audio_model.ResNetSE34L(nOut=num_out)
        self.resnet_model = self.resnet_model.to(device)

        # No encoder model for now...

    def forward(self, x):
        """Performs a forward pass of the input ``x`` through the network.

        Parameters
        ----------
        x : torch.Tensor instance
            Torch tensor containing the input to the network.

        Returns
        -------
        x : torch.Tensor instance
            Torch tensor containing the audio embeddings.

        """
        x = self.resnet_model(x)

        return x

class MultimodalClassifier(nn.Module):
    """Implementation of the Multimodal Classifier network.

    """

    def __init__(self, device=torch.device("cpu"), verbose=True):
        """Instantiates a new MultimodalClassifier object.

        Parameters
        ----------
        device : torch.device instance
            The device on which all procedures are carried out, by default torch.device("cpu").
        verbose : bool, optional
            If True then verbose output is printed to the system console.

        """
        super(MultimodalClassifier, self).__init__()

        assert isinstance(device, torch.device)
        assert isinstance(verbose, bool)

        self.device = device
        self.verbose = verbose

        # Build the network. Expected input is (batch_size, 3, 1, 256).
        self.conv1 = nn.Conv2d(3, 4, kernel_size=3, stride=1, padding=1)
        self.batchnorm1 = nn.BatchNorm2d(4)
        self.maxpool1 = nn.MaxPool2d(kernel_size=1, stride=2)
        self.conv2 = nn.Conv2d(4, 4, kernel_size=3, stride=2, padding=1)
        self.batchnorm2 = nn.BatchNorm2d(4)
        self.maxpool2 = nn.MaxPool2d(kernel_size=1, stride=2)
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(128, 2)
        #self.fc2 = nn.Linear(64, 32)
        #self.fc3 = nn.Linear(32, 2)
        self.relu = nn.ReLU(inplace=True)
        self.softmax = nn.Softmax()

    def forward(self, x):
        """Performs a forward pass of the input ``x`` through the network.

        Parameters
        ----------
        x : torch.Tensor instance
            Torch tensor containing the input to the network.

        Returns
        -------
        prediction : torch.Tensor instance
            Class label representing whether the video is real or fake.

        """
        x = self.conv1(x)
        x = self.batchnorm1(x)
        x = self.maxpool1(x)
        x = self.conv2(x)
        x = self.batchnorm2(x)
        x = self.maxpool2(x)
        x = self.flatten(x)
        #x = self.relu(x)
        x = self.fc1(x)
        #x = self.relu(x)
        #x = self.fc2(x)
        #x = self.relu(x)
        #x = self.fc3(x)
        #x = self.relu(x)

        prediction = self.softmax(x)

        return prediction


from typing import Awaitable, Callable, T, Optional
from fastapi import HTTPException

from system.transporters.Result import Result
from system.util.HttpUtils import handleError, returnErrorCheckResolver

async def middlewareRunner(data, method, middleware: Optional[Callable[..., Awaitable[T]]]|None):
    result = Result()
    if middleware is not None:
        try:
            print("[ Middleware ] Starting Runner ", data , type(data) )
            result = await middleware(result,data)
            print("[ Middleware ] middleware result ", result)
            if(result.status=="success"):
                print("[ Middleware ] middleware success ",middleware, result)
                print("[ Middleware ] middleware returned ", result.data)
                result.build("middlewareData",result.data)
                data.build("data", result.data)
                data =  await method(data)
                print("[ Middleware ] method returned from middle ware with data ", method, data)
                if returnErrorCheckResolver(data):
                    return data
                result.build("data", data)
                return result
            elif(result.status=="error" and result.clientErrorMessage is not None and len(result.clientErrorMessage)  > 0 ):
                print("[ Middleware ] type of clienterrormessage ",type(result.clientErrorMessage))
                print("[ Middleware ] middleware error ", result)
                return result
            else:
                print("[ Middleware ] error getting result from middleware, check middleware function, it must return an object with status and  data if status is success, or error if status is an error ")
                return Result().build("clientErrorMessage",{"Middleware returned malformed response."}).build("status","error").build("error",{"Middleware function returned malformed, if status success please return data :  { object | dict | string } , if status error please return error: { object | dict | string } and clientErrorMessage:  { object | dict | string } "})

        except Exception as error:
            print("[ Middleware ] error in middleware runner with middleware as ... and method as ... ", middleware, method, error)
            return await handleError(error, "[ Middleware ] Error in middleware, Exception.")
    else:
        try:
            dataInner = await method(data)
            print("[ Middleware ] returned middleware runner, with middleware injected middleware as NONE INJECTED ",method, dataInner)
            result.build("data",dataInner)
            result.build("status","success")
            return dataInner
        except (Exception) as error:
            print("[ Middleware ]  error in middleware runner with middleware as NONE INJECTED ", middleware, method, error)
            return await handleError(error, "[ Middleware ] Error in middleware.")


"""NES-Editor layout class."""

from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLabel

# layout looks like this (roughly):
# +------+-----------------+-----------------+
# |Local | CHR ROM (Tiles) |NameTable(Opt)   |
# |Plt   |                 |                 |
# |      |                 |                 |
# |      |                 |                 |
# +------+                 |                 |
# |Global|                 |                 |
# |Plt   |                 |                 |
# |      |                 |-----------------|
# |      |                 |Toggles          |
# +------+-----------------+-----------------+

class MainLayout(QHBoxLayout):
    """Main layout of the NES-Editor."""
    def __init__(self, local_plt, global_plt, chr_rom_canvas, nametable_canvas, toggles):
        super().__init__()

        local_plt = TitledLayout("Local Palette", local_plt)
        global_plt = TitledLayout("Global Palette", global_plt)
        chr_rom_canvas = TitledLayout("CHR ROM (Tiles)", chr_rom_canvas)
        nametable_canvas = TitledLayout("NameTable (Optional)", nametable_canvas)

        palettes_layout = PalettesLayout(local_plt, global_plt)
        self.addLayout(palettes_layout)
        palettes_layout.initialize()

        self.addLayout(chr_rom_canvas)
        chr_rom_canvas.initialize()

        right_most_layout = RightMostLayout(nametable_canvas, toggles)
        self.addLayout(right_most_layout)
        right_most_layout.initialize()

class TitledLayout(QVBoxLayout):
    """Titled widget."""
    def __init__(self, title, widget):
        super().__init__()
        self.title = title
        self.widget = widget

    def initialize(self):
        """Initialize the layout after adding it to parent."""
        self.addWidget(QLabel(self.title))
        self.addWidget(self.widget)

class PalettesLayout(QVBoxLayout):
    """Palettes layout of the NES-Editor."""
    def __init__(self, local_plt, global_plt):
        super().__init__()
        self.local_plt = local_plt
        self.global_plt = global_plt

    def initialize(self):
        """Initialize the layout after adding it to parent."""
        self.addLayout(self.local_plt)
        self.local_plt.initialize()

        self.addLayout(self.global_plt)
        self.global_plt.initialize()

class RightMostLayout(QVBoxLayout):
    """Right most layout of the NES-Editor."""
    def __init__(self, nametable_canvas, toggles):
        super().__init__()
        self.nametable_canvas = nametable_canvas
        self.toggles = toggles

    def initialize(self):
        """Initialize the layout after adding it to parent."""
        self.addLayout(self.nametable_canvas)
        self.nametable_canvas.initialize()

        toggles_layout = TogglesLayout(self.toggles)
        self.addLayout(toggles_layout)
        toggles_layout.initialize()

class TogglesLayout(QGridLayout):
    """Toggles layout of the NES-Editor."""
    def __init__(self, toggles):
        super().__init__()
        self.toggles = toggles

    def initialize(self):
        """Initialize the layout after adding it to parent."""
        # fill in the layout from arbitrary list of toggles
        col_count = len(self.toggles) // 3 + 1
        for i, toggle in enumerate(self.toggles):
            self.addWidget(toggle, i // col_count, i % col_count)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from sklearn import preprocessing
import numpy
import pandas as pd

path_no_attacks_cleaned    =          "data/data_no_attack_cleaned.csv"
path_no_attacks_normalized =          "data/data_no_attack_normalized.csv"
path_attacks_cleaned       =          "data/data_attack_cleaned.csv"
path_attacks_normalized    =          "data/data_attack_normalized.csv"

def to_csv(path,dataframe):
    numpy.savetxt(path, dataframe, delimiter=",")

def read_df(path):
   return pd.read_csv(path, low_memory=False)

def normalize_filtered_data(df):
    min_max_scaler  = preprocessing.MinMaxScaler()
    df_norm         = min_max_scaler.fit_transform(df)
    return df_norm

if __name__ == '__main__':

    df_no_attacks = read_df(path_no_attacks_cleaned)
    df_attacks    = read_df(path_attacks_cleaned)

    df_norm_no_attacks = normalize_filtered_data(df_no_attacks)
    df_norm_attacks    = normalize_filtered_data(df_attacks)

    to_csv(path_no_attacks_normalized,df_norm_no_attacks)
    to_csv(path_attacks_normalized,df_norm_attacks)


import numpy as np
from sklearn.cluster import k_means
from scipy.spatial.distance import cdist, pdist, squareform

# 加载加油数据
# 返回结构：tracks[date]->list<fillNum>
def loadTrackData(filePath):
    stationID = ''
    allTracks = {}
    allDateList = {}
    tracks = []
    fillNums = set()
    dateTime = ''
    oneDayList = []
    datelist = []
    # max,min = 0.0
    with open(filePath) as f:
        for oneLine in f:
            oneLine = oneLine.strip('\n')
            if (oneLine.startswith("$")):
                if (len(tracks) > 0):
                    tracks = np.array(tracks)
                    fillNumsmax = max(fillNums)
                    fillNumsmin = min(fillNums)
                    if (fillNumsmax != 0):
                        # print("stationID" + stationID)
                        # print("fillNumsmax:" + str(fillNumsmax) + "   fillNumsmin:" + str(fillNumsmin))
                        tracks = (tracks - fillNumsmin) / (fillNumsmax - fillNumsmin)  # 归一化
                        allTracks[stationID] = tracks
                        allDateList[stationID] = datelist
                tracks = []
                fillNums = set()
                dateTime = ''
                oneDayList = []
                datelist = []
                stationID = oneLine[1:]
            elif oneLine.startswith("#"):
                # 存储前一天的数据
                if dateTime != '':
                    datelist.append(dateTime)
                    tracks.append(np.array(oneDayList).astype('float32'))
                dateTime = oneLine[1:]
                oneDayList = []  # 清空前一天的数据
            else:
                oneDayList.append((float)(oneLine.split(",")[1]))
                fillNums.add((float)(oneLine.split(",")[1]))  # 存储为了归一化
        # 存储最后一个加油站数据
        # fillNums = np.array(fillNums).astype('float32')
        tracks = np.array(tracks)
        fillNumsmax = max(fillNums)
        fillNumsmin = min(fillNums)
        # tracks = (tracks - fillNumsmin) / (fillNumsmax - fillNumsmin)  # 归一化
        allTracks[stationID] = tracks
        allDateList[stationID] = datelist
    return allTracks, allDateList

def db_index(X, y):
    """
    Davies-Bouldin index is an internal evaluation method for
    clustering algorithms. Lower values indicate tighter clusters that
    are better separated.
    """
    # get unique labels
    if y.ndim == 2:
        y = np.argmax(axis=1)
    uniqlbls = np.unique(y)
    n = len(uniqlbls)
    # pre-calculate centroid and sigma
    centroid_arr = np.empty((n, X.shape[1]))
    sigma_arr = np.empty((n,1))
    dbi_arr = np.empty((n,n))
    mask_arr = np.invert(np.eye(n, dtype='bool'))
    for i,k in enumerate(uniqlbls):
        Xk = X[np.where(y==k)[0],...]
        Ak = np.mean(Xk, axis=0)
        centroid_arr[i,...] = Ak
        sigma_arr[i,...] = np.mean(cdist(Xk, Ak.reshape(1,-1)))
    # compute pairwise centroid distances, make diagonal elements non-zero
    centroid_pdist_arr = squareform(pdist(centroid_arr)) + np.eye(n)
    # compute pairwise sigma sums
    sigma_psum_arr = squareform(pdist(sigma_arr, lambda u,v: u+v))
    # divide
    dbi_arr = np.divide(sigma_psum_arr, centroid_pdist_arr)
    # get mean of max of off-diagonal elements
    dbi_arr = np.where(mask_arr, dbi_arr, 0)
    dbi = np.mean(np.max(dbi_arr, axis=1))
    return dbi

if __name__ == '__main__':
    fname = 'kmeans'
    allfilePath = "./data/allStationsFromOracl.csv"
    testfilePath = "./data/allStations-all.csv"
    clusterOutFilePath = './cluster/%s_out.csv' % fname
    DBIfilePath = './cluster/%s_DBI.csv' % fname
    allTracks, allDateList = loadTrackData(allfilePath)  # 返回的为字典
    testTracks, testDateList = loadTrackData(testfilePath)
    clusters_n = 3
    iteration_n = 100
    positiveLabels = [0, 1, 2, 3, 4, 5, 6]
    threshold = 0.3
    stations = ['13303']
    # 训练模型
    for stationID in stations:
        print("stationID:", stationID)
        tracks = allTracks[stationID]
        dateList = allDateList[stationID]
        # codebook, distortion = vq.kmeans(obs=wightened, k_or_guess=k, iter=20, thresh=1e-05, check_finite=True)
        centroid, labels, inertia, best_n = k_means(tracks, n_clusters=clusters_n, n_init=clusters_n, max_iter=100,
                                                    return_n_iter=True)
        f = open('./kmeansOut/' + stationID + ".csv", "a+")  # 打开文件以便写入
        # print('$' + stationID, file=f)
        for index in range(len(dateList)):
            oneDayList = tracks[index]
            oneDayList = oneDayList.astype('str')
            # print("#" + dateList[index], file=f)
            indexFroData = 0
            for indexFroOneDay in range(24):
                for indexForOneHour in range(2):
                    key = dateList[index] + str(indexFroOneDay).zfill(2) + str(indexForOneHour * 30).zfill(2)
                    print(key + ',' + oneDayList[indexFroData] + "," + str(labels[index]), file=f)
                    indexFroData = indexFroData + 1

            # for one in oneDayList:
            #     print(one + "," + str(labels[index]), file=f)
        f.close()
        f = open('./kmeansOut/' + "allStationIDs.csv", "a+")  # 打开文件以便写入
        print(stationID, file=f)
        f.close()
        # with open('./kmeansOut/' % fname, "a") as f:
        #     # f.write(str(stationID) + "," + str(metrics.davies_bouldin_score(wightened, labels)))
        #     f.write(str(labels) + '\n')

        # 参数 labels,tracks,trainLabels
        # trainTracks, trainLabels = gettrainLabelsData(labels, tracks, positiveLabels)
        # if 21 > len(trainTracks):
        #     print("batch_size is bigger than N")
        #     continue
        # if not os.path.exists(ckptPath + stationID):
        #     os.makedirs(ckptPath + stationID)
        # if not os.path.exists(summaries_dir + stationID):
        #     os.makedirs(summaries_dir + stationID)
        # 参数 trainOneModel(trainTracks,trainLabels,summaries_dir,stationID,ckptPath)
        # trainOneModel(trainTracks,trainLabels,summaries_dir+stationID,stationID,ckptPath + stationID +"//modelsave.ckpt",testTracks, testDateList)
    # 测试模型
    # for stationID in testTracks:
    #     print("stationID:", stationID)
    #     if not os.path.exists(ckptPath + stationID):
    #         print("no model")
    #         continue
    #     if not os.path.exists(summaries_dir + stationID):
    #         print("no model")
    #         continue
    #     tracks = testTracks[stationID]
    #     dateList = testDateList[stationID]
    #     # 参数 dectOutlier(stationID, dataInstance, dateList, trainLabels, ckptPath)
    #     dectOutlier(stationID,tracks,dateList,positiveLabels,ckptPath)


import networkx as nx
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib import pylab
import pandas as pd
import seaborn as sns

def create_one_graph_using_networkx(adj, nodes, edge_threshold):
    """
    This function create a graph using the adjacency matrix adj and
    list of nodes.

    :param adj: a numpy array representing the adjacency matrix
    :param nodes: a list representing the nodes in the graph
    :param edge_threshold: the threshold on the elements of adjacency
    matrix adj for that pair to be consider as an edge
    :return: the graph in networkX format
    """
    # create and empty graph
    g = nx.Graph()
    # add edges to the graph
    n = len(nodes)
    for node1 in range(n):
        for node2 in range(n):
            if node1 == node2 or abs(adj[node1][node2]) >= edge_threshold:
                g.add_edge(node1, node2)
                g[node1][node2]['weight'] = adj[node1][node2]
    return g

def create_all_graphs(mappings, nodes_list, edge_threshold=1e-10):
    """
    Transforms the adjacency matrix into the graph for all the instances
    :param mappings:
    :param nodes_list:
    :param edge_threshold:
    """
    graphs = {}
    for key in mappings.keys():
        graphs[key] = create_one_graph_using_networkx(mappings[key],
                                                      nodes_list[key],
                                                      edge_threshold)
    return graphs

def save_graph(graph, file_name):
    """
    This function saves the graph into a file called file_name
    :param graph:
    :param file_name:
    """
    # initialze Figure
    plt.figure(num=None, figsize=(20, 20), dpi=80)
    plt.axis('off')
    fig = plt.figure(1)
    pos = nx.spring_layout(graph)
    nx.draw_networkx_nodes(graph, pos)
    nx.draw_networkx_edges(graph, pos)
    nx.draw_networkx_labels(graph, pos)

    cut = 1.00
    xmax = cut * max(xx for xx, yy in pos.values())
    ymax = cut * max(yy for xx, yy in pos.values())
    plt.xlim(0, xmax)
    plt.ylim(0, ymax)

    plt.savefig('plots/' + file_name, bbox_inches="tight")
    pylab.close()
    del fig

def reduce_adjacency_matrix(adj, nodes, edge_threshold):
    """
    This function removes some rows and columns of the adjacency matrix
    according to their correlation with other elements in the adjacency
    matrix and returns a new (smaller) numpy array for adjacency and the
    new list of nodes considered in the matrix.
    :param adj: adjacency matrix
    :param nodes: name of nodes (row and column names)
    :param edge_threshold: minimum magnitude to search for and include
    """
    n = len(nodes)
    new_nodes_index = []
    kept_nodes_names = []
    # todo: return the names of the reduced data's rows, columns instead of
    # the index.

    # loop over the nodes (which is both a row and column) and look for
    # interactions that have greater magnitude than the specified
    # edge_threshold
    for node1 in range(n):
        remove = True
        for node2 in range(n):
            # look for whether this pair has a significant interaction.
            if abs(adj[node1][node2]) > edge_threshold or \
                            abs(adj[node2][node1]) > edge_threshold:
                remove = False
        if not remove:
            new_nodes_index.append(node1)
            kept_nodes_names.append(nodes[node1])
    new_adj = np.zeros([len(new_nodes_index), len(new_nodes_index)])
    for i, node1 in enumerate(new_nodes_index):
        for j, node2 in enumerate(new_nodes_index):
            new_adj[i][j] = adj[node1][node2]
    return new_adj, kept_nodes_names

def reduce_all_adjacency_matrixes_in_dict(adjacency_dict, node_dict,
                                          edge_threshold):
    """
    This function iterate over all replicates in the adjacency_dict
    which include 4 high O2 and 4 low O2 samples and applies the
    construct a new adjacency matrix based on the edge_treshold.
    edge_treshhold represents the minimum amount of interaction
    between bacteria needed to be considered as an edge in the
    network of bacteria.
    """
    reduced_array_dict = {}
    reduced_node_dict = {}
    for key in adjacency_dict.keys():
        reduced_array_dict[key], reduced_node_dict[key] = \
            reduce_adjacency_matrix(adjacency_dict[key],
                                    node_dict[key],
                                    edge_threshold)
    return reduced_array_dict, reduced_node_dict

def aggregate_adjacency_matrices(list_of_dfs):
    """
    This function take the list of all adjacency matrices found for each high/low
    replicate and calculate the mean, standard deviation and signal to noise ratio
    over the 4 replicates in high/low groups.
    """
    # Generalized aggregator.  Will write a wrapper that individually makes
    # one for each High/Low O2 condition.
    # Returns yet another dictionary!  (ha ha)

    # Use Pandas panel to gather the replicates, and to make summary
    # dataframes of the element-by-element averages, standard deviations,
    # and signal-to-noise.
    # Note that we are using 0, 1, 2... for keys in the Panel object.  We
    # could use the descriptive tuples, but there is currently no advantage.
    p = pd.Panel(data={n: df for n, df in enumerate(list_of_dfs)})
    # Use this Panel object to make summary statistics.
    summary_df_dict = {}
    summary_df_dict['mean'] = p.mean(axis=0)
    summary_df_dict['standard deviation'] = p.std(axis=0)
    summary_df_dict['signal to noise'] = summary_df_dict['mean']/summary_df_dict['standard deviation']
    # to get signal to noise, we need to make a panel of these new dataframes.
    p2 = pd.Panel(data={n: df for n, df in
                        enumerate([summary_df_dict['mean'],
                                  summary_df_dict['standard deviation'],
                                  summary_df_dict['signal to noise']])})
    return summary_df_dict

def summarize_replicate_adjacency_matrices(result_dict):
    """
    This function separates the high and low oxygen results before aggregating.
    The output is a dictionary of dictionaries.
    """
    high_oxygen_dfs = []
    low_oxygen_dfs = []
    # loop over the results dict.  The keys are tuples like ("Low",
    # 1) indicating their low/high oxygen content and replicate number.  Use
    #  the "Low" or "High" string to sort before aggregation.
    for oxy_rep_tuple in result_dict.keys():
        if oxy_rep_tuple[0] == "Low":
            low_oxygen_dfs.append(result_dict[oxy_rep_tuple])
        else:
            high_oxygen_dfs.append(result_dict[oxy_rep_tuple])
    # Now we can pass these lists of dataframes to
    # aggregate_adjacency_matrices(), which calculates the means and
    # standard deviations.  This will help us find the important and
    # reproducible correlations/effects.
    low_oxy_summary = aggregate_adjacency_matrices(low_oxygen_dfs)
    high_oxy_summary = aggregate_adjacency_matrices(high_oxygen_dfs)
    # create a dictionary to hold each dictionary.  Now we are wishing we
    # had classes!
    return {"Low": low_oxy_summary, "high": high_oxy_summary}

def plot_heatmap(dataframe, title=None, file_name=None, file_type='pdf',
                 width=10, height=10):
    """
    Plot a heat map of any data frame.

    :param dataframe: data to plot as a heat map
    :param title: optional title
    :param file_name: optional filename
    :param filetype: optional filetype
    :param width: figure width (inches)
    :param height: figue height (inches)
    :return: a seaborn heat map, whic was saved if file_name was provided.
    """
    ax = plt.axes()
    # generate a seaborn heatmap on the axis ax
    hmp = sns.heatmap(dataframe, ax=ax)
    # add a title if it was passed to the function
    if title:
        ax.set_title(title)
    # adjust the figure size
    hmp.figure.set_figwidth(width)
    hmp.figure.set_figheight(height)
    if file_name:
        hmp.figure.savefig(file_name + file_type, bbox_inches='tight')
    return hmp

def o2_rep_tuple_to_string(o2_rep_tuple):
    """
    this function create a string description for a sample
    called o2_rep_tuple
    """
    return str(o2_rep_tuple[0]) + '_oxygen_week_' + str(o2_rep_tuple[1])

def plot_all_adjacency_heatmaps(mappings_in_pandas):
    """
    plot and save the heat maps of the matrices given in pandas data frame
    :param mappings_in_pandas:
    """
    for key in mappings_in_pandas:
        file_name = 'plots/' + o2_rep_tuple_to_string(key)
        title = str(key[0])+' oxygen, week '+str(key[1])
        plot_heatmap(mappings_in_pandas[key], title, file_name,
                     file_type='.pdf')
        plt.clf()

def plot_aggregated_adjacency_heatmaps(mappings_in_pandas, dtype='Mean'):
    """
    plot and save the heat maps of the matrices given in pandas data frame
    :param mappings_in_pandas: a dictionary that containing two elements,
    including information for 'High' and 'Low' replicates
    :param dtype: the type of matrices to be plotted such as Mean, STD, SNR
    """
    # Todo: simplify by using plot_heatmap()
    for key in mappings_in_pandas:
        file_name = 'plots/'+key+"_oxygen_replicates_" + dtype
        ax = plt.axes()
        hmp = sns.heatmap(mappings_in_pandas[key], ax=ax)
        ax.set_title(key + ' oxygen replicates ' + dtype)
        hmp.figure.set_figwidth(10)
        hmp.figure.set_figheight(10)
        hmp.figure
        hmp.figure.savefig(file_name, bbox_inches='tight')
        plt.clf()


#!/usr/bin/python

import os
import time

import cv2
import numpy as np

import smbus
from picamera import PiCamera
from picamera.array import PiRGBArray

class Camera:
    __camera_resolution = {'low':    (160, 128),
                           'medium': (640, 480),
                           'high':   (800, 608)}

    def __init__(self, onRobot):
        self.__onRobot = onRobot
        self.__openCam = False

    def initCamera(self, camera='pi', resolution='low'):
        """
        Keyword arguments:
            camera -- which camera hardware to use:
                pi, logitech (default 'pi')

            resolution -- the camera resolution to use:
                low, medium, high (default 'low')
        """
        if resolution not in Camera.__camera_resolution:
            print('[ERROR] Camera.__init__(): Unknown \'{}\' camera resolution.'.format(resolution))
            return

        if camera == 'pi':
            self.__camera = PiCamera(resolution=Camera.__camera_resolution[resolution])
            self.__rawCapture = PiRGBArray(self.__camera, size=Camera.__camera_resolution[resolution])
            time.sleep(0.2)
            self.getFrame = self.__getFramePi
        elif camera == 'logitech':
            self.__openLogitech()
            self.__setLogitechResolution(resolution)
            self.getFrame = self.__getFrameLogitech
        else:
            print('[ERROR] Camera.__init__(): Unknown \'{}\' camera.'.format(camera))

        return self

    def imshow(self, wnd, img):
        if not self.__onRobot:
            if img.__class__ != np.ndarray:
                print('[ERROR] Camera.imshow(): Invalid image.')
                return False
            else:
                cv2.imshow(wnd, img)
                cv2.waitKey(1) & 0xFF

    def destroy(self):
        if self.__openCam:
            self.__cap.release()
        self.__openCam = False

    def __getFramePi(self):
        self.__camera.capture(self.__rawCapture, format='bgr', use_video_port=True)
        frame = self.__rawCapture.array
        self.__rawCapture.truncate(0)
        return frame

    def __getFrameLogitech(self):
        self.__cap.grab()
        (_, img) = self.__cap.retrieve()
        return img

    def __openLogitech(self):
        if not os.path.exists('/dev/video0'):
            return False
        self.__cap = cv2.VideoCapture()
        if not self.__cap.open(-1):
            return False
        self.__openCam = True

    def __setLogitechResolution(self, resolution):
        while not self.__openCam:
            print('Setting camera resolution to {} - {}'.format(resolution, self.__openCam))
            time.sleep(0.2)
        if self.__openCam:
            self.__cap.set(3, Camera.__camera_resolution[resolution][0])
            self.__cap.set(4, Camera.__camera_resolution[resolution][1])

class IOTools:
    __version = '2018a'

    def __init__(self, onRobot):
        print('[IOTools] R:SS IOTools ' + IOTools.__version)
        self.camera = Camera(onRobot)

    def destroy(self):
        self.camera.destroy()
        self.motor_control.stopMotors()


import datetime
import os

from sqlalchemy import JSON, String, Text, Time, Integer
from sqlalchemy.ext.hybrid import hybrid_property

from app import db, flask_app
from app.models.model_base import ModelBase
from app.utils.mixins import SearchableMixin
from app.models.user import User

class Restaurant(ModelBase, SearchableMixin):
    __tablename__ = "restaurant"
    __searchable__ = ["user_name", "description"]

    serialize_only = (*ModelBase.serialize_only, "id", "user_id", "position", "description", "phone", "image_path",
                      "wday_opening", "wday_closing", "wend_opening", "wend_closing")

    id = db.Column(Integer, primary_key=True)
    user_id = db.Column(Integer, nullable=False)
    position = db.Column(JSON, nullable=False)
    description = db.Column(Text)
    phone = db.Column(String(120), unique=True, nullable=False)
    image_path = db.Column(String(200))

    wday_opening = db.Column(Time, nullable=False)
    wday_closing = db.Column(Time, nullable=False)
    wend_opening = db.Column(Time)
    wend_closing = db.Column(Time)

    @hybrid_property
    def user_name(self):
        user = User.query.filter_by(id=self.user_id).first()
        return user.name

    @classmethod
    def validate_update(cls, rest_id, **attributes):
        obj = Restaurant.query.filter_by(id=rest_id).first()
        cls.validate_work_hours(attributes.get('wday_opening', obj.wday_opening),
                                attributes.get('wday_closing', obj.wday_closing))
        cls.validate_work_hours(attributes.get('wend_opening', obj.wend_opening),
                                attributes.get('wend_closing', obj.wend_closing))
        if 'phone' in attributes and attributes['phone'] != obj.phone and \
            cls.query.filter(phone=attributes['phone']).first() is not None:
            raise ValueError(f"Restaurant with phone number {attributes['phone']} already exist")

    @classmethod
    def update(cls, rest_id, **attributes):
        obj = Restaurant.query.filter_by(id=rest_id).first()

        if 'image_path' in attributes:
            if obj.image_path:
                old_path = os.path.abspath(os.path.join(flask_app.config['UPLOAD_FOLDER'], obj.image_path))
                if os.path.exists(old_path):
                    os.remove(old_path)
            obj.image_path = attributes['image_path']

        obj.phone = attributes.get('phone', obj.phone)
        obj.position = attributes.get('position', obj.position)
        obj.description = attributes.get('description', obj.description)

        obj.wday_opening = attributes.get('wday_opening', obj.wday_opening)
        obj.wday_closing = attributes.get('wday_closing', obj.wday_closing)
        obj.wend_opening = attributes.get('wend_opening', obj.wend_opening)
        obj.wend_closing = attributes.get('wend_closing', obj.wend_closing)
        db.session.commit()

    @staticmethod
    def validate_work_hours(opening_time: datetime.time, closing_time: datetime.time):
        if opening_time > closing_time:
            raise ValueError("")


import torch.nn as nn
class DiceLoss(nn.Module):
    """
    Dice score loss function
    """
    def __init__(self):
        super(DiceLoss, self).__init__()
        self.smooth = 1.0

    def forward(self, output, label):
        assert output.size() == label.size()
        output = output[:, 0].contiguous().view(-1)
        label = label[:, 0].contiguous().view(-1)
        intersection = (output * label).sum()
        dsc = (2. * intersection + self.smooth) / (output.sum() + label.sum() + self.smooth)

        return 1. - dsc