class RouteTrieNode:
    def __init__(self, handler = None):
        # Initialize the node with children as before, plus a handler
        self.children = {}
        self.handler = handler

    def insert(self, path_section):
        # Insert the node as before
        if path_section not in self.children:
            self.children[path_section] = RouteTrieNode()

        return self.children[path_section]

class RouteTrie:
    def __init__(self, root_handler):
        # Initialize the trie with an root node and a handler, this is the root path or home page node
        self.root = RouteTrieNode(root_handler)

    def insert(self, path_sections, handler):
        # Similar to our previous example you will want to recursively add nodes
        # Make sure you assign the handler to only the leaf (deepest) node of this path
        node = self.root
        for path_section in path_sections:
            node = node.insert(path_section)
        node.handler = handler

    def find(self, path_sections):
        # Starting at the root, navigate the Trie to find a match for this path
        # Return the handler for a match, or None for no match
        node = self.root
        for path_section in path_sections:
            if path_section not in node.children:
                return None
            node = node.children[path_section]

        return node.handler

# The Router class will wrap the Trie and handle
class Router:
    def __init__(self, root_handler, not_found_handler):
        # Create a new RouteTrie for holding our routes
        # You could also add a handler for 404 page not found responses as well!
        self.router = RouteTrie(root_handler)
        self.not_found_handler = not_found_handler

    def add_handler(self, full_path, handler):
        # Add a handler for a path
        # You will need to split the path and pass the pass parts
        # as a list to the RouteTrie
        path_sections = self.split_path(full_path)
        self.router.insert(path_sections, handler)

    def lookup(self, full_path):
        # lookup path (by parts) and return the associated handler
        # you can return None if it's not found or
        # return the "not found" handler if you added one
        # bonus points if a path works with and without a trailing slash
        # e.g. /about and /about/ both return the /about handler
        path_sections = self.split_path(full_path)
        handler = self.router.find(path_sections)
        if handler is None:
            return self.not_found_handler
        else:
            return handler

    def split_path(self, full_path):
        # you need to split the path into parts for
        # both the add_handler and loopup functions,
        # so it should be placed in a function here
        path_sections = [path_section for path_section in full_path.split('/') if path_section != '']
        return path_sections

if __name__ == "__main__":
    # Here are some test cases and expected outputs you can use to test your implementation

    # create the router and add a route
    router = Router("root handler", "not found handler") # remove the 'not found handler' if you did not implement this
    router.add_handler("/home/about", "about handler")  # add a route

    # some lookups with the expected output
    print(router.lookup("/")) # prints 'root handler'
    print(router.lookup("/home")) # prints 'not found handler'
    print(router.lookup("/home/about")) # prints 'about handler'
    print(router.lookup("/home/about/")) # prints 'about handler'
    print(router.lookup("/home/about/me")) # prints 'not found handler'

    router.add_handler("/home", "home handler")
    router.add_handler("/home/contact", "contact handler")
    router.add_handler("/home/login", "login handler")

    print(router.lookup("/home")) # prints 'home handler'
    print(router.lookup("/home/contact")) # prints 'contact handler'
    print(router.lookup("/home/login/")) # prints 'login handler'


import dill
from sqlalchemy.sql.expression import true
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from dnc import DNC
import dhg
from layers import GraphConvolution
import math
from torch.nn.parameter import Parameter
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.autograd as autograd
import torch.optim as optim
from torch.autograd import Variable
from torch.nn.parameter import Parameter
from torch.utils.data import Dataset, DataLoader
from transformers import AutoTokenizer, AutoModel
import numpy as np
import random
import pickle as pkl

longTensor = torch.cuda.LongTensor
floatTensor = torch.cuda.FloatTensor

class MolecularGraphNeuralNetwork(nn.Module):
    def __init__(self, inputs, average_projection, ddi_adj_all, ddi_adj_pn, N_fingerprint, dim, layer_hidden, device):
        super(MolecularGraphNeuralNetwork, self).__init__()
        self.device = device
        self.embed_fingerprint = nn.Embedding(N_fingerprint, dim).to(self.device)
        self.W_fingerprint = nn.ModuleList([nn.Linear(dim, dim).to(self.device)
                                            for _ in range(layer_hidden)])
        self.layer_hidden = layer_hidden
        self.conv = nn.Linear(64, 64)
        self.loss_function = marginLoss()

        self.ddi_adj_all = ddi_adj_all
        self.ddi_adj_pn = ddi_adj_pn

        self.fingerprints, self.adjacencies, self.molecular_sizes = inputs
        self.average_projection = average_projection
        self.adjacencies = self.pad(self.adjacencies, 0)

    def pad(self, matrices, pad_value):
        """Pad the list of matrices
        with a pad_value (e.g., 0) for batch processing.
        For example, given a list of matrices [A, B, C],
        we obtain a new matrix [A00, 0B0, 00C],
        where 0 is the zero (i.e., pad value) matrix.
        """
        shapes = [m.shape for m in matrices]
        M, N = sum([s[0] for s in shapes]), sum([s[1] for s in shapes])
        zeros = torch.FloatTensor(np.zeros((M, N))).to(self.device)
        pad_matrices = pad_value + zeros
        i, j = 0, 0
        for k, matrix in enumerate(matrices):
            m, n = shapes[k]
            pad_matrices[i:i+m, j:j+n] = matrix
            i += m
            j += n
        return pad_matrices

    def update(self, matrix, vectors, layer):
        hidden_vectors = torch.relu(self.W_fingerprint[layer](vectors))
        return hidden_vectors + torch.mm(matrix, hidden_vectors)

    def sum(self, vectors, axis):
        sum_vectors = [torch.sum(v, 0) for v in torch.split(vectors, axis)]
        return torch.stack(sum_vectors)

    def mean(self, vectors, axis):
        mean_vectors = [torch.mean(v, 0) for v in torch.split(vectors, axis)]
        return torch.stack(mean_vectors)

    def triplet_loss(self, MPNN_emb):
        # MPNN_emb triplet loss calculation

        ent_batch = []
        pos_batch = []
        neg_batch = []
        for i in range(128):
            while True:
                ent = random.randint(0, 265)
                idx = np.argwhere(self.ddi_adj_pn[ent] == 1)
                if len(idx) == 0:
                    continue
                pos = idx[random.randint(0, len(idx) - 1)][0]
                idx = np.argwhere(self.ddi_adj_all[ent] == 1)
                if len(idx) == 0:
                    continue
                neg = idx[random.randint(0, len(idx) - 1)][0]
                break

            ent_batch.append(ent)
            pos_batch.append(pos)
            neg_batch.append(neg)

        ent_batch = torch.LongTensor(ent_batch)
        pos_batch = torch.LongTensor(pos_batch)
        neg_batch = torch.LongTensor(neg_batch)

        ent_emb = MPNN_emb[ent_batch, :].unsqueeze(1)
        pos_emb = MPNN_emb[pos_batch, :].unsqueeze(1)
        neg_emb = MPNN_emb[neg_batch, :].unsqueeze(1)

        ent_emb = self.conv(ent_emb).squeeze(1)
        pos_emb = self.conv(pos_emb).squeeze(1)
        neg_emb = self.conv(neg_emb).squeeze(1)

        pos = torch.sum(torch.abs(ent_emb - pos_emb), 1)
        neg = torch.sum(torch.abs(ent_emb - neg_emb), 1)

        margin = torch.FloatTensor([1.0])
        emb_loss = self.loss_function(pos, neg, margin) + normLoss(ent_emb) + normLoss(pos_emb) + normLoss(neg_emb)
        # new_MPNN_emb = self.conv(self.MPNN_emb)

        return emb_loss

    def forward(self):

        """Cat or pad each input data for batch processing."""
        fingerprints = torch.cat(self.fingerprints)
        # self.fingerprints= self.fingerprints[0:2]

        """MPNN layer (update the fingerprint vectors)."""
        fingerprint_vectors = self.embed_fingerprint(fingerprints)

        for l in range(self.layer_hidden):
            hs = self.update(self.adjacencies, fingerprint_vectors, l)
            # fingerprint_vectors = F.normalize(hs, 2, 1)  # normalize.
            fingerprint_vectors = hs

        """Molecular vector by sum or mean of the fingerprint vectors."""
        molecular_vectors = self.sum(fingerprint_vectors, self.molecular_sizes)
        # print(molecular_vectors)
        # molecular_vectors = self.mean(fingerprint_vectors, molecular_sizes)
        MPNN_emb = torch.mm(self.average_projection, molecular_vectors)

        return MPNN_emb

class GCN(nn.Module):
    def __init__(self, voc_size, emb_dim, adj, device=torch.device('cpu:0')):
        super(GCN, self).__init__()
        self.voc_size = voc_size
        self.emb_dim = emb_dim
        self.device = device

        adj = self.normalize(adj + np.eye(adj.shape[0]))

        self.adj = torch.FloatTensor(adj).to(device)
        self.x = torch.eye(voc_size).to(device)

        self.gcn1 = GraphConvolution(voc_size, emb_dim)
        self.dropout = nn.Dropout(p=0.3)
        self.gcn2 = GraphConvolution(emb_dim, emb_dim)

    def forward(self):
        node_embedding = self.gcn1(self.x, self.adj)
        node_embedding = F.relu(node_embedding)
        node_embedding = self.dropout(node_embedding)
        node_embedding = self.gcn2(node_embedding, self.adj)
        return node_embedding

    def normalize(self, mx):
        """Row-normalize sparse matrix"""
        rowsum = np.array(mx.sum(1))
        r_inv = np.power(rowsum, -1).flatten()
        r_inv[np.isinf(r_inv)] = 0.
        r_mat_inv = np.diagflat(r_inv)
        mx = r_mat_inv.dot(mx)
        return mx

class marginLoss(nn.Module):
    def __init__(self):
        super(marginLoss, self).__init__()

    def forward(self, pos, neg, margin):
        zero_tensor = floatTensor(pos.size()).cpu()
        zero_tensor.zero_()
        zero_tensor = autograd.Variable(zero_tensor)
        return torch.sum(torch.max(pos - neg + margin, zero_tensor))

def normLoss(embeddings):
    norm = torch.sum(embeddings)
    return torch.sum(torch.max(norm - autograd.Variable(floatTensor([1.0])), autograd.Variable(floatTensor([0.0])))).cpu()

def weighted_sum(matrix: torch.Tensor, attention: torch.Tensor) -> torch.Tensor:
    if attention.dim() == 2 and matrix.dim() == 3:
        return attention.unsqueeze(1).bmm(matrix).squeeze(1)
    if attention.dim() == 3 and matrix.dim() == 3:
        return attention.bmm(matrix)
    if matrix.dim() - 1 < attention.dim():
        expanded_size = list(matrix.size())
        for i in range(attention.dim() - matrix.dim() + 1):
            matrix = matrix.unsqueeze(1)
            expanded_size.insert(i + 1, attention.size(i + 1))
        matrix = matrix.expand(*expanded_size)
    intermediate = attention.unsqueeze(-1).expand_as(matrix) * matrix
    return intermediate.sum(dim=-2)

def masked_sum(
    vector: torch.Tensor, mask: torch.BoolTensor, dim: int, keepdim: bool = False) -> torch.Tensor:
    replaced_vector = vector.masked_fill(~mask, 0.0)
    value_sum = torch.sum(replaced_vector, dim=dim, keepdim=keepdim)
    return value_sum

class HGNN_conv(nn.Module):
    def __init__(self, in_ft, out_ft, bias=True):
        super(HGNN_conv, self).__init__()

        self.weight = Parameter(torch.Tensor(in_ft, out_ft))
        if bias:
            self.bias = Parameter(torch.Tensor(out_ft))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def reset_parameters_xavier(self):
        nn.init.xavier_uniform_(self.weight)
        if self.bias is not None:
            nn.init.xavier_uniform_(self.bias)

    def forward(self, x: torch.Tensor, G: torch.Tensor):
        x = x.matmul(self.weight)
        if self.bias is not None:
            x = x + self.bias
        x = G.matmul(x)
        return x

class HGNN_fc(nn.Module):
    def __init__(self, in_ch, out_ch):
        super(HGNN_fc, self).__init__()
        self.fc = nn.Linear(in_ch, out_ch)

    def forward(self, x):
        return self.fc(x)

class HGNN_sg_attn_simple(nn.Module):

    def __init__(self, edim, ddim, atype='additive'):
        super(HGNN_sg_attn, self).__init__()
        if atype == 'additive':
            self.attention = attention.AdditiveAttention(edim, ddim)
        elif atype == 'multiplicative':
            self.attention = attention.MultiplicativeAttention(edim, ddim)
        else:
            exit(f'unrecognized attention type {atype}')

    def forward(self, x, sgs):
        xsize = list(x.size())
        bsize = sgs.shape[0]
        b_attn = torch.matmul(sgs, x)
        y = self.attention(b_attn, x)

        return y

class HGNN_sg_attn_Allen(nn.Module):

    def __init__(self, vdim, mdim, atype='additive'):
        super(HGNN_sg_attn, self).__init__()
        if atype == 'additive':
            self.attention = attention.AdditiveAttention(vdim, mdim)
        elif atype == 'dotprod':
            self.attention = attention.DotProductAttention()
        else:
            exit(f'unrecognized attention type {atype}')

    def forward(self, x, sgs):
        xsize = list(x.size())
        bsize = sgs.shape[0]
        b_attn = torch.matmul(sgs, x)
        attn_wts = self.attention(b_attn, x.unsqueeze(0).expand(bsize, *xsize))
        x = torch.matmul(attn_wts, x)
        return x

class HGNN_sg_attn(nn.Module):

    def __init__(self, vdim, mdim, atype='additive'):
        super(HGNN_sg_attn, self).__init__()
        self.attn_vector = torch.nn.Parameter(torch.zeros((vdim,1), dtype=torch.float), requires_grad=True)

        stdv = 1. / math.sqrt(vdim)
        self.attn_vector.data.uniform_(-stdv, stdv)

    def forward(self, x, sgs):
        xsize = list(x.size())
        bsize = sgs.shape[0]
        attn_wts = torch.matmul(x, self.attn_vector)
        attn_wts = attn_wts.squeeze().unsqueeze(0).expand(bsize, xsize[0])
        x = torch.matmul(sgs*attn_wts, x)
        return x

class HGNN_sg_attn_multiplicative(nn.Module):

    def __init__(self, vdim, mdim, atype='additive'):
        super(HGNN_sg_attn, self).__init__()
        self.W = nn.Parameter(torch.FloatTensor(mdim, vdim), requires_grad=True)
        nn.init.xavier_uniform_(self.W)

    def forward(self, x, sgs):
        x = sgs.matmul(x).matmul(self.W)
        return x

class HGNN_embedding(nn.Module):
    def __init__(self, in_ch, n_hid, dropout=0.5):
        super(HGNN_embedding, self).__init__()
        self.dropout = dropout
        self.hgc1 = HGNN_conv(in_ch, n_hid)
        self.hgc2 = HGNN_conv(n_hid, n_hid)

    def forward(self, x, G):
        x = F.relu(self.hgc1(x, G))
        x = F.dropout(x, self.dropout)
        x = F.relu(self.hgc2(x, G))
        return x

class HGNN_classifier(nn.Module):
    def __init__(self, n_hid, n_class):
        super(HGNN_classifier, self).__init__()
        self.fc1 = nn.Linear(n_hid, n_class)

    def forward(self, x):
        x = self.fc1(x)
        return x

class MEGACare(nn.Module):
    def __init__(self, vocab_size, ddi_adj_pn, ddi_adj_all, MPNNSet, N_fingerprints, average_projection, emb_dim=256, device=torch.device('cpu:0')):
        super(MEGACare, self).__init__()

        self.device = device

        self.average_projection = average_projection.to(device=self.device)

        # pre-embedding
        self.embeddings = nn.ModuleList(
            [nn.Embedding(vocab_size[i], emb_dim) for i in range(2)])
        self.dropout = nn.Dropout(p=0.5)
        self.encoders = nn.ModuleList([nn.GRU(emb_dim, emb_dim, batch_first=True) for _ in range(2)])
        self.query = nn.Sequential(
                nn.ReLU(),
                nn.Linear(2 * emb_dim, emb_dim)
        )

        # MPNN global embedding
        MPNN_molecule_Set = list(zip(*MPNNSet))

        self.MPNN_emb = MolecularGraphNeuralNetwork(N_fingerprints, emb_dim, layer_hidden=2, device=device).forward(self.MPNN_molecule_Set)
        self.MPNN_emb = torch.mm(average_projection.to(device=self.device), self.MPNN_emb.to(device=self.device))

        # self.MPNN_emb.to(device=self.device)
        self.MPNN_emb = torch.tensor(self.MPNN_emb, requires_grad=True)

        self.MPNN_net = MolecularGraphNeuralNetwork(MPNN_molecule_Set, self.average_projection, ddi_adj_all, ddi_adj_pn, \
                                                     N_fingerprints, emb_dim, layer_hidden=2, device=self.device)
        self.MPNN_emb = torch.load("../MPNN_net_embeddings").detach().to(device)
        # self.MPNN_net.load_state_dict(stat_dic)
        self.MPNN_output = nn.Linear(vocab_size[2], vocab_size[2])
        self.MPNN_layernorm = nn.LayerNorm(vocab_size[2])

        self.loss_function = marginLoss()

        # graphs, bipartite matrix
        # Create POS and NEG ddi metrix
        self.tensor_ddi_adj_all = torch.FloatTensor(ddi_adj_all).to(device)
        self.tensor_ddi_adj_pn = torch.FloatTensor(ddi_adj_pn).to(device)
        self.ddi_adj_all = ddi_adj_all
        self.ddi_adj_pn = ddi_adj_pn

        # self.tensor_ddi_mask_H = torch.FloatTensor(ddi_mask_H).to(device)
        self.init_weights()

        # self.add_Linear = add_Linear

        # if add_Linear == True:
        self.mlp = nn.Linear(64, 64)
        # self.new_MPNN_emb = self.conv()

    def triplet_loss_v2(self):
        # MPNN_emb triplet loss calculation
        ent_batch = []
        pos_batch = []
        neg_batch = []
        for batch in range(128):
            while True:
                ent = random.randint(0, 265)
                idx = np.argwhere(self.ddi_adj_pn[ent] == 1)
                if len(idx) == 0:
                    continue
                pos = idx[random.randint(0, len(idx) - 1)][0]
                idx = np.argwhere(self.ddi_adj_all[ent] == 1)
                if len(idx) == 0:
                    continue
                neg = idx[random.randint(0, len(idx) - 1)][0]
                break

            ent_batch.append(ent)
            pos_batch.append(pos)
            neg_batch.append(neg)

        ent_batch = autograd.Variable(longTensor(ent_batch))
        pos_batch = autograd.Variable(longTensor(pos_batch))
        neg_batch = autograd.Variable(longTensor(neg_batch))

        ent_emb = self.MPNN_emb[ent_batch, :]
        pos_emb = self.MPNN_emb[pos_batch, :]
        neg_emb = self.MPNN_emb[neg_batch, :]

        pos = torch.sum(torch.abs(ent_emb - pos_emb), 1)
        neg = torch.sum(torch.abs(ent_emb - neg_emb), 1)

        margin = autograd.Variable(floatTensor([1.0]))
        emb_loss = self.loss_function(pos, neg, margin) + normLoss(ent_emb) + normLoss(pos_emb) + normLoss(neg_emb)
        # new_MPNN_emb = self.conv(self.MPNN_emb)
        return emb_loss

    def forward(self, input):

        loss_1, loss_2 = 0, 0
        all_patient_loss_1 = torch.zeros(size=[0, self.vocab_size[2]]).to(self.device)
        all_patient_loss_2 = torch.zeros(size=[0, self.vocab_size[2]], dtype=torch.long).to(self.device)

        self.MPNN_emb = self.MPNN_net().detach()
        self.MPNN_emb = torch.tensor(self.MPNN_emb, requires_grad=True)
        loss_tri = self.triplet_loss()

	    # patient health representation
        i1_seq = []
        i2_seq = []
        def sum_embedding(embedding):
            return embedding.sum(dim=1).unsqueeze(dim=0)  # (1,1,dim)
        for adm in input:
            i1 = sum_embedding(self.dropout(self.embeddings[0](torch.LongTensor(adm[0]).unsqueeze(dim=0).to(self.device)))) # (1,1,dim)
            i2 = sum_embedding(self.dropout(self.embeddings[1](torch.LongTensor(adm[1]).unsqueeze(dim=0).to(self.device))))
            i1_seq.append(i1)
            i2_seq.append(i2)
        i1_seq = torch.cat(i1_seq, dim=1) #(1,seq,dim)
        i2_seq = torch.cat(i2_seq, dim=1) #(1,seq,dim)

        o1, h1 = self.encoders[0](
            i1_seq
        )
        o2, h2 = self.encoders[1](
            i2_seq
        )
        patient_representations = torch.cat([o1, o2], dim=-1).squeeze(dim=0) # (seq, dim*2)
        query = self.query(patient_representations)[-1:, :] # (seq, dim)

	    # MPNN embedding

        # MPNN with a MLP layer
        # new_MPNN_emb = self.mlp(self.MPNN_emb)
        # MPNN_match = F.sigmoid(torch.mm(query, new_MPNN_emb.t()))

        # MPNN without a MLP layer
        MPNN_match = F.sigmoid(torch.mm(query, self.MPNN_emb.t()))
        MPNN_att = self.MPNN_layernorm(MPNN_match + self.MPNN_output(MPNN_match))

        result = MPNN_att

        # result = torch.mul(bipartite_emb, MPNN_att)
        # result = torch.cat(bipartite_emb, MPNN_att)

        neg_pred_prob = F.sigmoid(result)
        neg_pred_prob = neg_pred_prob.t() * neg_pred_prob  # (voc_size, voc_size)

        # tensor

        batch_neg = 0.0005 *  neg_pred_prob.mul(self.tensor_ddi_adj_all).sum()
        batch_pos = 0.0005 *  neg_pred_prob.mul(self.tensor_ddi_adj_pn).sum()

        # LOSS
        loss = (- torch.log(batch_pos / (batch_pos + batch_neg) ))

        return all_patient_loss_1, all_patient_loss_2, prediction, loss_1, loss_2, batch_neg, encoder_visit

    def init_weights(self):
        """Initialize weights."""
        initrange = 0.1
        for item in self.embeddings:
            item.weight.data.uniform_(-initrange, initrange)

class MaskLinear(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super(MaskLinear, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.FloatTensor(in_features, out_features))
        if bias:
            self.bias = Parameter(torch.FloatTensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, input, mask):
        weight = torch.mul(self.weight, mask)
        output = torch.mm(input, weight)

        if self.bias is not None:
            return output + self.bias
        else:
            return output

    def __repr__(self):
        return self.__class__.__name__ + ' (' \
               + str(self.in_features) + ' -> ' \
               + str(self.out_features) + ')'

# DMNC
class DMNC(nn.Module):
    def __init__(self, vocab_size, emb_dim=64, device=torch.device('cpu:0')):
        super(DMNC, self).__init__()
        K = len(vocab_size)
        self.K = K
        self.vocab_size = vocab_size
        self.device = device

        self.token_start = vocab_size[2]
        self.token_end = vocab_size[2] + 1

        self.embeddings = nn.ModuleList(
            [nn.Embedding(vocab_size[i] if i != 2 else vocab_size[2] + 2, emb_dim) for i in range(K)])
        self.dropout = nn.Dropout(p=0.5)

        self.encoders = nn.ModuleList([DNC(
            input_size=emb_dim,
            hidden_size=emb_dim,
            rnn_type='gru',
            num_layers=1,
            num_hidden_layers=1,
            nr_cells=16,
            cell_size=emb_dim,
            read_heads=1,
            batch_first=True,
            gpu_id=0,
            independent_linears=False
        ) for _ in range(K - 1)])

        self.decoder = nn.GRU(emb_dim + emb_dim * 2, emb_dim * 2,
                              batch_first=True)  # input: (y, r1, r2,) hidden: (hidden1, hidden2)
        self.interface_weighting = nn.Linear(emb_dim * 2, 2 * (emb_dim + 1 + 3))  # 2 read head (key, str, mode)
        self.decoder_r2o = nn.Linear(2 * emb_dim, emb_dim * 2)

        self.output = nn.Linear(emb_dim * 2, vocab_size[2] + 2)

    def forward(self, input, i1_state=None, i2_state=None, h_n=None, max_len=20):
        # input (3, code)
        i1_input_tensor = self.embeddings[0](
            torch.LongTensor(input[0]).unsqueeze(dim=0).to(self.device))  # (1, seq, codes)
        i2_input_tensor = self.embeddings[1](
            torch.LongTensor(input[1]).unsqueeze(dim=0).to(self.device))  # (1, seq, codes)

        o1, (ch1, m1, r1) = \
            self.encoders[0](i1_input_tensor, (None, None, None) if i1_state is None else i1_state)
        o2, (ch2, m2, r2) = \
            self.encoders[1](i2_input_tensor, (None, None, None) if i2_state is None else i2_state)

        # save memory state
        i1_state = (ch1, m1, r1)
        i2_state = (ch2, m2, r2)

        predict_sequence = [self.token_start] + input[2]
        if h_n is None:
            h_n = torch.cat([ch1[0], ch2[0]], dim=-1)

        output_logits = []
        r1 = r1.unsqueeze(dim=0)
        r2 = r2.unsqueeze(dim=0)

        if self.training:
            for item in predict_sequence:
                # teacher force predict drug
                item_tensor = self.embeddings[2](
                    torch.LongTensor([item]).unsqueeze(dim=0).to(self.device))  # (1, seq, codes)

                o3, h_n = self.decoder(torch.cat([item_tensor, r1, r2], dim=-1), h_n)
                read_keys, read_strengths, read_modes = self.decode_read_variable(h_n.squeeze(0))

                # read from i1_mem, i2_mem and i3_mem
                r1, _ = self.read_from_memory(self.encoders[0],
                                              read_keys[:, 0, :].unsqueeze(dim=1),
                                              read_strengths[:, 0].unsqueeze(dim=1),
                                              read_modes[:, 0, :].unsqueeze(dim=1), i1_state[1])

                r2, _ = self.read_from_memory(self.encoders[1],
                                              read_keys[:, 1, :].unsqueeze(dim=1),
                                              read_strengths[:, 1].unsqueeze(dim=1),
                                              read_modes[:, 1, :].unsqueeze(dim=1), i2_state[1])

                output = self.decoder_r2o(torch.cat([r1, r2], dim=-1))
                output = self.output(output + o3).squeeze(dim=0)
                output_logits.append(output)
        else:
            item_tensor = self.embeddings[2](
                torch.LongTensor([self.token_start]).unsqueeze(dim=0).to(self.device))  # (1, seq, codes)
            for idx in range(max_len):
                # predict
                # teacher force predict drug
                o3, h_n = self.decoder(torch.cat([item_tensor, r1, r2], dim=-1), h_n)
                read_keys, read_strengths, read_modes = self.decode_read_variable(h_n.squeeze(0))

                # read from i1_mem, i2_mem and i3_mem
                r1, _ = self.read_from_memory(self.encoders[0],
                                              read_keys[:, 0, :].unsqueeze(dim=1),
                                              read_strengths[:, 0].unsqueeze(dim=1),
                                              read_modes[:, 0, :].unsqueeze(dim=1), i1_state[1])

                r2, _ = self.read_from_memory(self.encoders[1],
                                              read_keys[:, 1, :].unsqueeze(dim=1),
                                              read_strengths[:, 1].unsqueeze(dim=1),
                                              read_modes[:, 1, :].unsqueeze(dim=1), i2_state[1])

                output = self.decoder_r2o(torch.cat([r1, r2], dim=-1))
                output = self.output(output + o3).squeeze(dim=0)
                output = F.softmax(output, dim=-1)
                output_logits.append(output)

                input_token = torch.argmax(output, dim=-1)
                input_token = input_token.item()
                item_tensor = self.embeddings[2](
                    torch.LongTensor([input_token]).unsqueeze(dim=0).to(self.device))  # (1, seq, codes)

        return torch.cat(output_logits, dim=0), i1_state, i2_state, h_n

    def read_from_memory(self, dnc, read_key, read_str, read_mode, m_hidden):
        read_vectors, hidden = dnc.memories[0].read(read_key, read_str, read_mode, m_hidden)
        return read_vectors, hidden

    def decode_read_variable(self, input):
        w = 64
        r = 2
        b = input.size(0)

        input = self.interface_weighting(input)
        # r read keys (b * w * r)
        read_keys = F.tanh(input[:, :r * w].contiguous().view(b, r, w))
        # r read strengths (b * r)
        read_strengths = F.softplus(input[:, r * w:r * w + r].contiguous().view(b, r))
        # read modes (b * 3*r)
        read_modes = F.softmax(input[:, (r * w + r):].contiguous().view(b, r, 3), -1)
        return read_keys, read_strengths, read_modes

# GAMENet
class GCN(nn.Module):
    def __init__(self, voc_size, emb_dim, adj, device=torch.device('cpu:0')):
        super(GCN, self).__init__()
        self.voc_size = voc_size
        self.emb_dim = emb_dim
        self.device = device

        adj = self.normalize(adj + np.eye(adj.shape[0]))

        self.adj = torch.FloatTensor(adj).to(device)
        self.x = torch.eye(voc_size).to(device)

        self.gcn1 = GraphConvolution(voc_size, emb_dim)
        self.dropout = nn.Dropout(p=0.3)
        self.gcn2 = GraphConvolution(emb_dim, emb_dim)

    def forward(self):
        node_embedding = self.gcn1(self.x, self.adj)
        node_embedding = F.relu(node_embedding)
        node_embedding = self.dropout(node_embedding)
        node_embedding = self.gcn2(node_embedding, self.adj)
        return node_embedding

    def normalize(self, mx):
        """Row-normalize sparse matrix"""
        rowsum = np.array(mx.sum(1))
        r_inv = np.power(rowsum, -1).flatten()
        r_inv[np.isinf(r_inv)] = 0.
        r_mat_inv = np.diagflat(r_inv)
        mx = r_mat_inv.dot(mx)
        return mx

class GAMENet(nn.Module):
    def __init__(self, vocab_size, ehr_adj, ddi_adj, emb_dim=64, device=torch.device('cpu:0'), ddi_in_memory=True):
        super(GAMENet, self).__init__()
        K = len(vocab_size)
        self.K = K
        self.vocab_size = vocab_size
        self.device = device
        self.tensor_ddi_adj = torch.FloatTensor(ddi_adj).to(device)
        self.ddi_in_memory = ddi_in_memory
        self.embeddings = nn.ModuleList(
            [nn.Embedding(vocab_size[i], emb_dim) for i in range(K-1)])
        self.dropout = nn.Dropout(p=0.5)

        self.encoders = nn.ModuleList([nn.GRU(emb_dim, emb_dim * 2, batch_first=True) for _ in range(K-1)])

        self.query = nn.Sequential(
            nn.ReLU(),
            nn.Linear(emb_dim * 4, emb_dim),
        )

        self.ehr_gcn = GCN(voc_size=vocab_size[2], emb_dim=emb_dim, adj=ehr_adj, device=device)
        self.ddi_gcn = GCN(voc_size=vocab_size[2], emb_dim=emb_dim, adj=ddi_adj, device=device)
        self.inter = nn.Parameter(torch.FloatTensor(1))

        self.output = nn.Sequential(
            nn.ReLU(),
            nn.Linear(emb_dim * 3, emb_dim * 2),
            nn.ReLU(),
            nn.Linear(emb_dim * 2, vocab_size[2])
        )

        self.init_weights()

    def forward(self, input):
        # input (adm, 3, codes)

        # generate medical embeddings and queries
        i1_seq = []
        i2_seq = []
        def mean_embedding(embedding):
            return embedding.mean(dim=1).unsqueeze(dim=0)  # (1,1,dim)
        for adm in input:
            i1 = mean_embedding(self.dropout(self.embeddings[0](torch.LongTensor(adm[0]).unsqueeze(dim=0).to(self.device)))) # (1,1,dim)
            i2 = mean_embedding(self.dropout(self.embeddings[1](torch.LongTensor(adm[1]).unsqueeze(dim=0).to(self.device))))
            i1_seq.append(i1)
            i2_seq.append(i2)
        i1_seq = torch.cat(i1_seq, dim=1) #(1,seq,dim)
        i2_seq = torch.cat(i2_seq, dim=1) #(1,seq,dim)

        o1, h1 = self.encoders[0](
            i1_seq
        ) # o1:(1, seq, dim*2) hi:(1,1,dim*2)
        o2, h2 = self.encoders[1](
            i2_seq
        )
        patient_representations = torch.cat([o1, o2], dim=-1).squeeze(dim=0) # (seq, dim*4)
        queries = self.query(patient_representations) # (seq, dim)

        # graph memory module
        '''I:generate current input'''
        query = queries[-1:] # (1,dim)

        '''G:generate graph memory bank and insert history information'''
        if self.ddi_in_memory:
            drug_memory = self.ehr_gcn() - self.ddi_gcn() * self.inter  # (size, dim)
        else:
            drug_memory = self.ehr_gcn()

        if len(input) > 1:
            history_keys = queries[:(queries.size(0)-1)] # (seq-1, dim)

            history_values = np.zeros((len(input)-1, self.vocab_size[2]))
            for idx, adm in enumerate(input):
                if idx == len(input)-1:
                    break
                history_values[idx, adm[2]] = 1
            history_values = torch.FloatTensor(history_values).to(self.device) # (seq-1, size)

        '''O:read from global memory bank and dynamic memory bank'''
        key_weights1 = F.softmax(torch.mm(query, drug_memory.t()), dim=-1)  # (1, size)
        fact1 = torch.mm(key_weights1, drug_memory)  # (1, dim)

        if len(input) > 1:
            visit_weight = F.softmax(torch.mm(query, history_keys.t())) # (1, seq-1)
            weighted_values = visit_weight.mm(history_values) # (1, size)
            fact2 = torch.mm(weighted_values, drug_memory) # (1, dim)
        else:
            fact2 = fact1
        '''R:convert O and predict'''
        output = self.output(torch.cat([query, fact1, fact2], dim=-1)) # (1, dim)

        if self.training:
            neg_pred_prob = F.sigmoid(output)
            neg_pred_prob = neg_pred_prob.t() * neg_pred_prob  # (voc_size, voc_size)
            batch_neg = neg_pred_prob.mul(self.tensor_ddi_adj).mean()

            return output, batch_neg
        else:
            return output

    def init_weights(self):
        """Initialize weights."""
        initrange = 0.1
        for item in self.embeddings:
            item.weight.data.uniform_(-initrange, initrange)

        self.inter.data.uniform_(-initrange, initrange)

# LEAP
class Leap(nn.Module):
    def __init__(self, voc_size, emb_dim=64, device=torch.device('cpu:0')):
        super(Leap, self).__init__()
        self.voc_size = voc_size
        self.device = device
        self.SOS_TOKEN = voc_size[2]
        self.END_TOKEN = voc_size[2]+1

        self.enc_embedding = nn.Sequential(
            nn.Embedding(voc_size[0], emb_dim, ),
            nn.Dropout(0.3)
        )
        self.dec_embedding = nn.Sequential(
            nn.Embedding(voc_size[2] + 2, emb_dim, ),
            nn.Dropout(0.3)
        )

        self.dec_gru = nn.GRU(emb_dim*2, emb_dim, batch_first=True)

        self.attn = nn.Linear(emb_dim*2, 1)

        self.output = nn.Linear(emb_dim, voc_size[2]+2)

    def forward(self, input, max_len=20):
        device = self.device
        # input (3, codes)
        input_tensor = torch.LongTensor(input[0]).to(device)
        # (len, dim)
        input_embedding = self.enc_embedding(input_tensor.unsqueeze(dim=0)).squeeze(dim=0)

        output_logits = []
        hidden_state = None
        if self.training:
            for med_code in [self.SOS_TOKEN] + input[2]:
                dec_input = torch.LongTensor([med_code]).unsqueeze(dim=0).to(device)
                dec_input = self.dec_embedding(dec_input).squeeze(dim=0) # (1,dim)

                if hidden_state is None:
                    hidden_state = dec_input
                hidden_state_repeat = hidden_state.repeat(input_embedding.size(0), 1) # (len, dim)

                combined_input = torch.cat([hidden_state_repeat, input_embedding], dim=-1) # (len, dim*2)
                attn_weight = F.softmax(self.attn(combined_input).t(), dim=-1) # (1, len)
                input_embedding = attn_weight.mm(input_embedding) # (1, dim)

                _, hidden_state = self.dec_gru(torch.cat([input_embedding, dec_input], dim=-1).unsqueeze(dim=0), hidden_state.unsqueeze(dim=0))
                hidden_state = hidden_state.squeeze(dim=0) # (1,dim)

                output_logits.append(self.output(F.relu(hidden_state)))
            return torch.cat(output_logits, dim=0)

        else:
            for di in range(max_len):
                if di == 0:
                    dec_input = torch.LongTensor([[self.SOS_TOKEN]]).to(device)
                dec_input = self.dec_embedding(dec_input).squeeze(dim=0) # (1,dim)
                if hidden_state is None:
                    hidden_state = dec_input
                hidden_state_repeat = hidden_state.repeat(input_embedding.size(0), 1)  # (len, dim)
                combined_input = torch.cat([hidden_state_repeat, input_embedding], dim=-1)  # (len, dim*2)
                attn_weight = F.softmax(self.attn(combined_input).t(), dim=-1)  # (1, len)
                input_embedding = attn_weight.mm(input_embedding)  # (1, dim)
                _, hidden_state = self.dec_gru(torch.cat([input_embedding, dec_input], dim=-1).unsqueeze(dim=0),
                                               hidden_state.unsqueeze(dim=0))
                hidden_state = hidden_state.squeeze(dim=0)  # (1,dim)
                output = self.output(F.relu(hidden_state))
                topv, topi = output.data.topk(1)
                output_logits.append(F.softmax(output, dim=-1))
                dec_input = topi.detach()
            return torch.cat(output_logits, dim=0)

# RETAIN
class Retain(nn.Module):
    def __init__(self, voc_size, emb_size=64, device=torch.device('cpu:0')):
        super(Retain, self).__init__()
        self.device = device
        self.voc_size = voc_size
        self.emb_size = emb_size
        self.input_len = voc_size[0] + voc_size[1] + voc_size[2]
        self.output_len = voc_size[2]

        self.embedding = nn.Sequential(
            nn.Embedding(self.input_len + 1, self.emb_size, padding_idx=self.input_len),
            nn.Dropout(0.5)
        )

        self.alpha_gru = nn.GRU(emb_size, emb_size, batch_first=True)
        self.beta_gru = nn.GRU(emb_size, emb_size, batch_first=True)

        self.alpha_li = nn.Linear(emb_size, 1)
        self.beta_li = nn.Linear(emb_size, emb_size)

        self.output = nn.Linear(emb_size, self.output_len)

    def forward(self, input):
        device = self.device
        # input: (visit, 3, codes )
        max_len = max([(len(v[0]) + len(v[1]) + len(v[2])) for v in input])
        input_np = []
        for visit in input:
            input_tmp = []
            input_tmp.extend(visit[0])
            input_tmp.extend(list(np.array(visit[1]) + self.voc_size[0]))
            input_tmp.extend(list(np.array(visit[2]) + self.voc_size[0] + self.voc_size[1]))
            if len(input_tmp) < max_len:
                input_tmp.extend( [self.input_len]*(max_len - len(input_tmp)) )

            input_np.append(input_tmp)

        visit_emb = self.embedding(torch.LongTensor(input_np).to(device)) # (visit, max_len, emb)
        visit_emb = torch.sum(visit_emb, dim=1) # (visit, emb)

        g, _ = self.alpha_gru(visit_emb.unsqueeze(dim=0)) # g: (1, visit, emb)
        h, _ = self.beta_gru(visit_emb.unsqueeze(dim=0)) # h: (1, visit, emb)

        g = g.squeeze(dim=0) # (visit, emb)
        h = h.squeeze(dim=0) # (visit, emb)
        attn_g = F.softmax(self.alpha_li(g), dim=-1) # (visit, 1)
        attn_h = F.tanh(self.beta_li(h)) # (visit, emb)

        c = attn_g * attn_h * visit_emb # (visit, emb)
        c = torch.sum(c, dim=0).unsqueeze(dim=0) # (1, emb)

        return self.output(c)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Activity',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('activity_type', models.CharField(max_length=256, verbose_name='Activity type', choices=[(b'running', b'Running'), (b'walking', b'Walking'), (b'cycling', b'Cycling')])),
                ('date', models.DateField(verbose_name='Date of activity')),
                ('start_time', models.TimeField(null=True, verbose_name='Start time', blank=True)),
                ('duration', models.TimeField(verbose_name='Duration')),
                ('distance', models.FloatField(null=True, verbose_name='Distance', blank=True)),
                ('calories', models.IntegerField(null=True, verbose_name='Calories', blank=True)),
                ('activity_notes', models.TextField(null=True, verbose_name='Notes', blank=True)),
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]


from DS.const import FPS, TILE_SIZE
from DS.game import Game
import pygame

def get_click_position(pos):
    x, y = pos
    col = x // TILE_SIZE
    row = y // TILE_SIZE

    return col, row

def main():
    run = True
    clock = pygame.time.Clock()
    game = Game()

    while run:
        clock.tick(FPS)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.MOUSEBUTTONDOWN:
                pos = pygame.mouse.get_pos()
                col, row = get_click_position(pos)
                game.make_move(col, row)

        pygame.display.update()

main()


import re
import requests
import pandas as pd
from typing import Tuple
from bs4 import BeautifulSoup

def get_twitter_followers(link) -> int:

    r = requests.get(link)
    soup = BeautifulSoup(r.content,'lxml')

    try:
        f = soup.find('li', class_="ProfileNav-item--followers")
        title = f.find('a')['title']
        # attention for the separator here
        num_followers = int(title.split(' ')[0].replace('.',''))

    except:
        num_followers = 'twitter url yielded nothing'

    return num_followers

def get_address(soup) -> str:

    address = soup.find('div', attrs ={'class': "_2wzd"}).get_text()
    #begin = "[aA-zZ]+\s[0-9]+" # gets all streetnames with numbers
    #end = "[0-9]+\s[a-zA-ZäöüÄÖÜß]+" # postcode and city name (Umlaute because of DE/AT/CH)
    address = re.sub("[\(\[].*?[\)\]]", "", address)
    address = address.replace('  ',', ')

    # ATTENTION! this does not work for street names with hyphens
    #result_beg = re.findall(begin,address)
    #result_end = re.findall(end,address)
    #address = result_beg[0]+', '+result_end[0]
    return address

def get_phone_number(soup) -> str:
    # this html classes text still seems to contain both address and phone number
    phone_number = soup.find('div', attrs ={'class':"_4-u2 _u9q _3xaf _4-u8"}).get_text()
    #finds '+43 6452 4033360', '+41 27 603 40 00' or '089 72014342'
    # does not work for US numbers (yet), they are in format +1-123-456-789 typically
    phone_regex = re.findall("[0]+.[0-9]+.[0-9]+|[\+0-9]+\s[0-9]"+\
        "+\s[0-9]+\s[0-9]+\s[0-9]+|[\+0-9]+\s[0-9]+\s[0-9]+",phone_number)
    #print('content from phone_number',phone_regex) # beware, this is a list!
    # remove short matches such as '0 8008' from addresses that are found inside here
    phone_sane = [i for i in phone_regex if len(i) >7] #POSSIBLY missing some phone_numbers, check again
    tel = phone_sane[0].replace(" ","").replace('-','').replace('(0)','').replace(")",'').replace("(","")

    return tel

def scrape_likes(fb_url) -> Tuple[int, str, str]:

    fb_likes = 0
    response = requests.get(fb_url)
    soup = BeautifulSoup(response.content,'lxml')
    #print('soup is',soup)
    f = soup.find('div', attrs={'class': '_4-u3 _5sqi _5sqk'})

    try:
        # find span tag inside class
        likes=f.find('span',attrs={'class':'_52id _50f5 _50f7'})
        # find numbers separated by 2 pts
        x= re.findall("[0-9]+.[0-9]+.[0-9]+|[0-9]+",likes.text)

        # change line to x[0].replace(',','') when using a non-german IP
        # only german site uses '.' as separator
        x[0]=x[0].replace('.','')

        fb_likes = int(x[0])

        # getting tel or address might fail
        try:
            tel = get_phone_number(soup)
        except:
            tel = None
        try:
            address = get_address(soup)
        except:
            address = None

    except:
        fb_likes = 'fb url did not work'
        tel = None
        address = None

    return fb_likes, tel, address

def scrape_site(url) -> object:

    soup = BeautifulSoup(requests.get(url).content,'lxml')
    links = [link.get('href') for link in soup.find_all('a')]

    #sites can be extended by '(at)' regex
    # list of strings to search for
    sites = ['facebook.com', 'twitter.com', 'linkedin.com','@']
    links_sane = [i for i in links if i]

    #create list with matched strings
    try:
        new_links = [l for l in links_sane for s in sites if s in l]
    except:
        print('No usable link detected.'+\
            ' life is beautiful but you have to continue with another one')

    facebook = [i for i in new_links if sites[0] in i]

    facebook = [i for i in facebook if not 'sharer' in i]

    twitter = [i for i in new_links if sites[1] in i]
    twitter = [i for i in twitter if not len(i)>50]

    linkedin = [i for i in new_links if sites[2] in i]
    linkedin = [i for i in linkedin if not 'share' in i]

    email = [i for i in new_links if sites[3] in i]
    email = [i.replace('mailto:','') for i in email]

    if twitter:
        # num_followers
        num_followers = get_twitter_followers(twitter[0])

    else:

        twitter = [None]
        num_followers = None

    if facebook:
        # returns fb_likes, tel, address
        fb_likes,tel,address = scrape_likes(facebook[0])

    else:
        # set all to None
        facebook = [None]
        fb_likes = None
        tel = None
        address = None

    if email:

        if type(email)==list:

            email = email[0]

        else:
            email = email
    else:
        email = None

    if linkedin:
        linkedinurl = linkedin[0]
    else:
        linkedinurl = None

    # DataFrame structure from retrieved data
    df = pd.DataFrame(columns=['fb_likes','twitter_followers',
                                'tel','url','fb_url','address',
                                'twitter_url','email','linkedin'])

    try:
        # better formatting for search in SF
        url = url.replace('https://','').replace('http://','')

        if(url.endswith('/')):
            url=url[:-1]
        stuff = [fb_likes,num_followers,tel,url,facebook[0],
                    address,twitter[0],email,linkedinurl]

        # displays entire columns, change second parameter to char length
        pd.set_option('display.max_colwidth', -1)

        df = df.append(pd.Series(stuff,index=df.columns),ignore_index=True)

        print(facebook[0])
        if linkedin:
            print(linkedin[0])

    except:

        print('no stuff for',url)

    return df

# example function call
df = scrape_site(input("Enter URL:"))


'''
Taken from https://github.com/bdhammel/least-squares-ellipse-fitting/blob/master/ellipse.py
'''
import numpy as np
import numpy.linalg as la

class LsqEllipse:
    """Lest Squares fitting of Elliptical data
    Attributes
    ----------
    coef_ : array
        Estimated coefficients for the Least squares fit to the elliptical data
        containing the values [a,b,c,d,f,g].T corresponding to
        ax**2 + 2bxy + cy**2 + 2dx + 2fy + g
    References
    ----------
    (*) Halir R., Flusser J. 'Numerically Stable Direct Least Squares
    Fitting of Ellipses'
    (**) Weisstein, Eric W. "Ellipse." From MathWorld--A Wolfram Web Resource.
    http://mathworld.wolfram.com/Ellipse.html
    Examples
    --------
    >>> import numpy as np
    >>> from sklearn.linear_model import LsqEllipse
    >>> x = np.array([ 1.,  0., -1., -0.,  1.])
    >>> y = np.array([ 0. ,  0.5,  0. , -0.5, -0. ])
    >>> X = np.c_[x, y]
    >>> reg = LsqEllipse().fit(X)
    >>> reg.as_parameters()
    """
    ALLOWED_FEATURES = 2

    def __init__(self):
        self.coef_ = None

    def _check_data(self, X):

        n_samples, n_features = X.shape
        if not n_features == self.ALLOWED_FEATURES:
            raise ValueError("Incorrect number of features. "
                             "Got {} features, expected 2. ".format(n_features))

        if n_samples < 5:
            raise ValueError("Received too few samples"
                             "Got {} features, 5 or more required. ".format(n_samples))

        return X

    def fit(self, X):
        """Fit the data
        Parameters
        ----------
        X : array, shape (n_points, 2)
            Data values for the x-y data pairs to fit
        Returns
        -------
        self : returns an instance of self.
        """
        X = self._check_data(X)

        # extract x-y pairs
        x, y = X.T

        # Quadratic part of design matrix [eqn. 15] from (*)
        D1 = np.vstack([x**2, x * y, y**2]).T
        # Linear part of design matrix [eqn. 16] from (*)
        D2 = np.vstack([x, y, np.ones_like(x)]).T

        # Forming scatter matrix [eqn. 17] from (*)
        S1 = D1.T @ D1
        S2 = D1.T @ D2
        S3 = D2.T @ D2

        # Constraint matrix [eqn. 18]
        C1 = np.array([[0., 0., 2.], [0., -1., 0.], [2., 0., 0.]])

        # Reduced scatter matrix [eqn. 29]
        M = la.inv(C1) @ (S1 - S2 @ la.inv(S3) @ S2.T)

        # M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors from this
        # equation [eqn. 28]
        eigval, eigvec = np.linalg.eig(M)

        # Eigenvector must meet constraint 4ac - b^2 to be valid.
        cond = (
            4*np.multiply(eigvec[0, :], eigvec[2, :])
            - np.power(eigvec[1, :], 2)
        )
        a1 = eigvec[:, np.nonzero(cond > 0)[0]]

        # |d f g> = -S3^(-1) * S2^(T)*|a b c> [eqn. 24]
        a2 = la.inv(-S3) @ S2.T @ a1

        # Eigenvectors |a b c d f g>
        # list of the coefficients describing an ellipse [a,b,c,d,f,g]
        # corresponding to ax**2 + 2bxy + cy**2 + 2dx + 2fy + g
        self.coef_ = np.vstack([a1, a2])

        return self

    @property
    def coefficients(self):
        """
        List of the coefficients describing the fitted ellipse
        Returns
        -------
        [a,b,c,d,f,g] corresponding to ax**2 + 2bxy + cy**2 + 2dx + 2fy + g
        """
        return np.asarray(self.coef_).ravel()

    def as_parameters(self):
        """Returns the definition of the fitted ellipse as localized parameters
        Returns
        _______
        center : list
            [x0, y0]
        width : float
            Semimajor axis
        height : float
            Semiminor axis
        phi : float
            The counterclockwise angle of rotation from the x-axis to the major
            axis of the ellipse
        """

        # Eigenvectors are the coefficients of an ellipse in general form
        # a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0
        # [eqn. 15) from (**) or (***)
        a = self.coefficients[0]
        b = self.coefficients[1] / 2.
        c = self.coefficients[2]
        d = self.coefficients[3] / 2.
        f = self.coefficients[4] / 2.
        g = self.coefficients[5]

        # Finding center of ellipse [eqn.19 and 20] from (**)
        x0 = (c*d - b*f) / (b**2. - a*c)
        y0 = (a*f - b*d) / (b**2. - a*c)
        center = [x0, y0]

        # Find the semi-axes lengths [eqn. 21 and 22] from (**)
        numerator = 2 * (a*f**2 + c*d**2 + g*b**2 - 2*b*d*f - a*c*g)
        denominator1 = (b * b - a * c) * (
            (c - a) * np.sqrt(1 + 4*b*b / ((a - c)*(a - c))) - (c + a)
        )
        denominator2 = (b*b - a*c) * (
            (a - c) * np.sqrt(1 + 4*b*b / ((a - c) * (a - c))) - (c + a)
        )
        width = np.sqrt(numerator / denominator1)
        height = np.sqrt(numerator / denominator2)

        # Angle of counterclockwise rotation of major-axis of ellipse to x-axis
        # [eqn. 23] from (**) or [eqn. 26] from (***).
        phi = .5 * np.arctan((2.*b) / (a - c))

        return center, width, height, phi

    def return_fit(self, n_points=None, t=None):
        """Return the X, Y values of the predicted ellipse
        Points are returned along the parametric curve of the ellipse as evenly
        spaced points starting at t=0 to t=2pi
        Parameters
        ---------
        n_points : int
            Number of points to return
        t : array
            Parametric points used to generate x-y pairs, If provided,
            `n_points` will be ignored
        Returns
        -------
        X : array, shape (n_points, 2)
            data values for the x-y data pairs
        """
        if self.coef_ is None:
            raise ValueError("Must call .fit() before using .return_fit()")

        if n_points is None and t is None:
            raise AttributeError("A value for `n_points` or `t` must be ",
                                 "provided")

        if t is None:
            t = np.linspace(0, 2 * np.pi, n_points)

        center, width, height, phi = self.as_parameters()

        x = (center[0]
             + width * np.cos(t) * np.cos(phi)
             - height * np.sin(t) * np.sin(phi))
        y = (center[1]
             + width * np.cos(t) * np.sin(phi)
             + height * np.sin(t) * np.cos(phi))

        return np.c_[x, y]


from helpers.database import db
from helpers.serialization import serialize_model_list

class ExtraCategoryDBModel(db.Model):

    __tablename__ = 'extras_categories'

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), nullable=False)

    # Eager load extras.
    extras = db.relationship("ExtraDBModel", backref='extra', lazy='joined')

    def __init__(self, data):
        self.title = data['title']

    def serialize(self, relations=[]):
        return {
            'id': self.id,
            'title': self.title,
            'items': serialize_model_list(self.extras)
        }


from regex import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from searching import *
import itertools
import os
import sys

from PyQt5.uic import loadUiType
from PyQt5.uic.Compiler.indenter import createCodeIndenter

ui, _ = loadUiType('search.ui')
browsedata, _ = loadUiType('browse.ui')

class browse(QDialog, browsedata):
    def __init__(self):  # constructor
        QDialog.__init__(self)
        self.setupUi(self)
        self.setWindowTitle('Browse Files')
        style = open('themes/amoled.css', 'r')
        style = style.read()
        self.setStyleSheet(style)
        self.browse.clicked.connect(self.browsefiles)
        self.pushButton.clicked.connect(self.addfile)
        self.pushButton_2.clicked.connect(self.close_browse)

    def browsefiles(self):
        fname = QFileDialog.getOpenFileName(
        self, 'Open file', r'C:\Users\Safi\Desktop\IndexBasedSearchEngine-main\DataSet', 'Files (*.JSON)')
        file = self.filename.setText(fname[0])

    def addfile(self):
        filepath = self.filename.text() # address of file
        filename = os.path.basename(filepath)   #extracting name of file

        dir = "C:\\Users\Safi\Desktop\\IndexBasedSearchEngine-main\\DataSet"
        newfile = os.path.join(dir, filename)
        with open(filepath,'r') as firstfile, open(newfile,'a') as secondfile:

            for line in firstfile:

             # append content to second file
                secondfile.write(line)

        self.label.setText('New File Added Successfully!')

    def close_browse(self):
        self.close()

class MainApp(QMainWindow, ui):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.tabWidget.tabBar().setVisible(False)
        self.setWindowTitle('Search Engine')
        style = open('themes/amoled.css', 'r')
        style = style.read()
        self.setStyleSheet(style)
        self.showMaximized()
        self.pushButton.clicked.connect(self.show_results)
        self.pushButton_3.clicked.connect(self.show_home)
        self.pushButton_2.clicked.connect(self.show_results2)
        self.pushButton_4.clicked.connect(self.handle_browse)

    def show_results(self):
        if self.lineEdit.text() == '':
            self.label_4.setText('Please Enter a Query First!')
            return

        else:
            self.tabWidget.setCurrentIndex(1)
            self.textBrowser.setText('')
        # self.tabWidget.setStyleSheet("background-image: url(:/icons/logo3.png)")
            self.textBrowser.setStyleSheet("border: 0px")
        # self.textBrowser.setStyleSheet("background-color: rgb(230, 230, 230)")
        # self.textBrowser.setStyleSheet("background-color: rgb(10, 10, 10)")

            searchinput = self.lineEdit.text()
            self.lineEdit_2.setText(searchinput)

            data = search(searchinput)
            data = dict(itertools.islice(data.items(), 10))

            if not (data == None):
                for key in data:
                    data1 = key
                    data2 = data[key]
                    data2 = data2[:-1]
                    self.textBrowser.append('%s' % (data2, data1))
                    self.textBrowser.append(data2)
                    self.textBrowser.append('')

    def show_results2(self):
        if self.lineEdit_2.text() == '':
            self.label_7.setText('Please Enter a Query First!')
            return
        else:

            self.textBrowser.setStyleSheet("border: 0px")
        # self.tabBrowser.setStyleSheet("background-color: rgb(240, 212, 215)")
            self.textBrowser.setText('')
            searchinput = self.lineEdit_2.text()

            data = search(searchinput)
            data = dict(itertools.islice(data.items(), 10))

            if not (data == None):
                for key in data:

                    data1 = key
                    data2 = data[key]
                    data2 = data2[:-1]
                    self.textBrowser.append('%s' % (data2, data1))
                    self.textBrowser.append(data2)
                    self.textBrowser.append('')

    def handle_browse(self):
        self.window2 = browse()
        self.window2.show()

    def show_home(self):
        self.tabWidget.setCurrentIndex(0)
        self.lineEdit.setText('')
        self.lineEdit_2.setText('')
        self.textBrowser.setText('')
        self.label_4.setText('')
        self.label_7.setText('')

def main():
    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    app.exec()

if __name__ == '__main__':
    main()


import time
import requests
import asyncio

#正常情况下爬取网页
URL = 'https://morvanzhou.github.io/'

#爬取两遍
def normal():
    for i in range(2):
        r = requests.get(URL)
        url = r.url
        print(url)

t1 = time.time()
normal()
print('Normal Total time: ',time.time()-t1)

#Asyncio下爬取网页
import aiohttp

async def job(session):
    response = await session.get(URL)
    return str(response.url)

async def main(loop):
    async with aiohttp.ClientSession() as session:
        tasks = [loop.create_task(job(session)) for _ in range(2)]
        finished, unfinished = await asyncio.wait(tasks)
        #这些tasks执行后的返回对象，需要用result()得到原始函数的返回值
        all_results = [r.result() for r in finished]
        print(all_results)

t1 = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
loop.close()
print('Asyncio Total time: ',time.time()-t1)


import kaldi_io
import argparse
import numpy as np
import pickle
import os
from collections import defaultdict
import logging
import glob
from tqdm import tqdm
import sys
from scipy.special import softmax

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.handlers.clear()

# default output
c_handler = logging.StreamHandler(sys.stderr)
c_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
c_handler.setFormatter(c_format)
logger.addHandler(c_handler)

class AliStretchModel:
    @staticmethod
    def load_from_file(fname):
        with open(fname, 'rb') as f:
            obj = pickle.load(f)
        return obj

    def save_to_file(self, fname=None):
        if fname is None:
            fname = self.model_path
        os.makedirs(os.path.dirname(fname), exist_ok=True)
        with open(fname, 'wb') as f:
            pickle.dump(self, f)

    @staticmethod
    def add_args(parser: argparse.ArgumentParser):
        parser.add_argument("--stretch_model_path", required=True, type=str,
                            help='Model path for saving or loading stretch aligment model')

    @staticmethod
    def build_kwargs(args):
        kwargs = {'model_path': args.stretch_model_path,
                  'max_pdf_id': args.max_pdf_id}
        return kwargs

    @classmethod
    def build_from_disk(cls, args):
        logger.info(f"Loading saved model from {args.stretch_model_path}")
        return cls.load_from_file(args.stretch_model_path)

    @classmethod
    def build(cls, args, load_from_cashe=True):
        if os.path.exists(args.stretch_model_path):
            if not load_from_cashe:
                raise RuntimeError(f"Model {args.stretch_model_path} already exists")
            cls.build_from_disk(args)
        assert args.max_pdf_id is not None, RuntimeError("--max_pdf_id  required!")
        kwargs = cls.build_kwargs(args)
        return cls(**kwargs)

    def __init__(self, model_path, max_pdf_id):
        self.model_path = model_path
        self.max_pdf_id = max_pdf_id
        self.id2count = np.zeros((max_pdf_id,))
        self.id2seq_count = [defaultdict(int) for _ in range(self.max_pdf_id)]

    def add_utts(self, ali):
        assert len(ali.shape) == 1, RuntimeError(f"Wrong shape in add_utts")
        prev_id = None
        seq_len = 0
        for t_id in ali:
            self.id2count[t_id] += 1
            if t_id != prev_id:
                if prev_id is not None:
                    self.id2seq_count[prev_id][seq_len] += 1
                    seq_len = 0
                prev_id = t_id
            seq_len += 1

    def compute(self):
        logger.info("Starting AliStretchModel Compute")
        if np.any(self.id2count == 0):
            bad_ids = np.where(self.id2count == 0)[0]
            logger.warning(f"Not all pdf ids found in train data. bad pdf_ids = {bad_ids}. shape={bad_ids.shape}"
                           f"({round(bad_ids.shape[-1]/self.max_pdf_id*100, 2)}%)")
            self.id2count[bad_ids] = 1
        for i in range(self.id2count.shape[0]):
            total_count = sum(self.id2seq_count[i].values())
            for seqlen in self.id2seq_count[i]:
                self.id2seq_count[i][seqlen] /= total_count
        self.id2count = np.zeros_like(self.id2count)

    def forward(self, ids):
        # ids - [0, 1, 0,...]
        dup_ids = np.concatenate([np.array([index] * self.sample_seq_len(index), dtype=np.int32) for index in ids])
        return dup_ids

    def sample_seq_len(self, index):
        distr = self.id2seq_count[index]
        if len(distr) == 0:
            return 1
        population, weights = np.asarray(list(distr.keys())), np.asarray(list(distr.values()))
        return np.random.choice(population, p=weights)

    def __call__(self, *args, **kwargs):
        return self.forward(*args, **kwargs)

class Id2LoglikeAMModel:
    @staticmethod
    def load_from_file(fname):
        with open(fname, 'rb') as f:
            key = kaldi_io.read_key(f)
            assert key == 'fam_model', RuntimeError(f"Bad fam model {fname}")
            id2sum = kaldi_io.read_mat(f)
        return id2sum

    def save_to_file(self, fname=None):
        if fname is None:
            fname = self.model_path
        os.makedirs(os.path.dirname(fname), exist_ok=True)
        with open(fname, 'wb') as f:
            kaldi_io.write_mat(f, self.id2sum, key='fam_model')
            # kaldi_io.write_mat(f, self.id2priors.reshape((1, -1)), key='prior')

    @staticmethod
    def add_args(parser: argparse.ArgumentParser):
        parser.add_argument("--id2ll_model_path", required=True, type=str,
                            help='Model path for saving or loading id2loglike model')
        parser.add_argument('--apply_smoothing', action='store_true',
                            help='Add smoothing matrix to avg likelihoods.')
        parser.add_argument('--label_smoothing', default=1, type=float,
                            help='Weight for diagonal elements')
        parser.add_argument('--eps', default=5e-5, type=float,
                            help='Epsilon. all zeros are replaced by eps')
        parser.add_argument('--apply_priors', action='store_true',
                            help="div smoothing matrix by priors vector")

    @staticmethod
    def build_kwargs(args):
        kwargs = {'model_path': args.id2ll_model_path,
                  'max_pdf_id': args.max_pdf_id,
                  'label_smoothing': args.label_smoothing,
                  'eps': args.eps,
                  'apply_priors': args.apply_priors,
                  'apply_smoothing': args.apply_smoothing}
        return kwargs

    @classmethod
    def build_from_disk(cls, args):
        logger.info(f"Loading saved model from {args.id2ll_model_path}")
        id2sum = cls.load_from_file(args.id2ll_model_path)
        obj = cls(args.id2ll_model_path, id2sum=id2sum)
        obj.id2sum = id2sum

    @classmethod
    def build(cls, args, load_from_cashe=True):
        if os.path.exists(args.id2ll_model_path):
            if not load_from_cashe:
                raise RuntimeError(f"Model {args.id2ll_model_path} already exists")
            cls.build_from_disk(args)
        assert args.max_pdf_id is not None, RuntimeError("--max_pdf_id  required!")
        kwargs = cls.build_kwargs(args)
        return cls(**kwargs)

    def __init__(self, model_path, max_pdf_id=None, id2sum=None, label_smoothing=1, eps=2e-4,
                 apply_priors=True, apply_smoothing=True):
        self.model_path = model_path
        self.apply_smoothing=apply_smoothing
        self.label_smoothing = label_smoothing
        self.eps = eps
        self.apply_priors = apply_priors
        if id2sum is not None:
            self.id2sum = id2sum
            self.max_pdf_id = id2sum.shape[-1]
            self.id2count = np.ones((max_pdf_id,))
        else:
            self.max_pdf_id = max_pdf_id
            self.id2sum = np.zeros((max_pdf_id, max_pdf_id))
            self.id2count = np.zeros((max_pdf_id,))

        self.id2priors = np.zeros_like(self.id2count)

    def add_prob(self, index, prob):
        self.id2sum[index] += softmax(prob, axis=-1)
        self.id2count[index] += 1

    def add_utts(self, ids, probs):
        assert len(ids.shape) == 1 and len(probs.shape) == 2, RuntimeError(f"Wrong shape in add_probs")
        assert ids.shape[0] == probs.shape[0] , RuntimeError(f"Ali ({ids.shape[0]}) and features ({probs.shape[0]}) len not the same!")
        for t_id, prob in zip(ids, probs):
            self.add_prob(t_id, prob)

    def compute(self):
        logger.info("Starting Id2LoglikeAMModel Compute")
        # if np.any(self.id2count == 0):
        #     logger.warning(f"Not all pdf ids found in train data. bad pdf_ids = {np.where(self.id2count == 0)}")

        self.id2sum[self.id2sum == 0] = self.eps
        self.id2count[self.id2count == 0] = 1
        self.id2priors = self.id2count / self.id2count.sum()
        if self.apply_smoothing:
            self.id2count += 1
            self.id2sum += self.get_smooth()
        self.id2sum = self.id2sum / self.id2count.reshape(-1, 1)

    def get_smooth(self):
        id2smooth = np.zeros_like(self.id2sum)
        if self.label_smoothing != 1:
            other_p = (1 - self.label_smoothing) / (self.max_pdf_id - 1)
            id2smooth.fill(other_p)
        np.fill_diagonal(id2smooth, self.label_smoothing)
        if self.apply_priors:
            id2smooth /= self.id2priors.reshape(1, -1)
        return id2smooth

    def get_prob(self, index):
        return self.id2sum[index]

    def forward(self, ids):
        # ids - [0, 1, 0,...]
        return np.log(self.id2sum[ids])

    def __call__(self, *args, **kwargs):
        return self.forward(*args, **kwargs)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    AliStretchModel.add_args(parser)
    Id2LoglikeAMModel.add_args(parser)
    parser.add_argument('ali_dir')
    parser.add_argument('--max_pdf_id', type=int, default=None, help="Maximum pdf_id")
    args = parser.parse_args()

    ali_stretch_model = AliStretchModel.build(args, load_from_cashe=False)
    id2ll_model = Id2LoglikeAMModel.build(args, load_from_cashe=False)

    logger.info(f"Loading {args.ali_dir}/ali.*.gz")
    utt2ali = {key: ali for key, ali in tqdm(kaldi_io.read_vec_int_ark(f'ark: gunzip -c {args.ali_dir}/ali_pdf.1.gz|'))}
    i = 0
    for key, ali in tqdm(utt2ali.items()):
        i += 1
        ali_stretch_model.add_utts(ali)
    logger.info(f"AliStretchModel processed {i} utterances")
    ali_stretch_model.compute()
    ali_stretch_model.save_to_file()

    logger.info(f"Loaded {len(utt2ali)} alis")
    logger.info(f"Loading logprobs and train model")
    i = 0
    for k, m in tqdm(kaldi_io.read_mat_ark(f'ark: cat {args.ali_dir}/output.1.ark |'), total=len(utt2ali)):
        i += 1
        if k not in utt2ali.keys():
            logger.warning(f"Ali for {k} does not exist")
            continue
        ali = utt2ali[k]
        id2ll_model.add_utts(ali, m)
    logger.info(f"Id2LoglikeAMModel processed {i} utterances")
    id2ll_model.compute()
    id2ll_model.save_to_file()
    logger.info(f"Done.")


import os

def over_20_chr():
    fin = open("C:/Users/Lorenzo/Desktop/Informatica/tomorrowDevs/learn_python/python_exercises/tp_chapter8/words.txt")
    for line in fin:
        word = line.strip()
        if len(word) > 20:
            print(word)

def has_e():
    fin = open(os.path.abspath('words.txt'))
    counter = 0
    for line in fin:
        word = line.strip()
        for c in word:
            if c == 'e':
                print(word)
                counter = counter + 1
                break
    print(counter)

def has_r():
    fin = open(os.path.abspath('words.txt'))
    counter = 0
    for line in fin:
        word = line.strip()
        for c in word:
            if c == 'r':
                print(word)
                counter = counter + 1
                break
    print(counter)

def words_counter():
    fin = open(os.path.abspath('words.txt'))
    words_num = 0
    for line in fin:
        word = line.strip()
        print(word)
        words_num = words_num + 1
    print(words_num)

def avoids(word, unusable):
    for letter in word:
        if letter in unusable:
            return False
    return True

def uses_only(word, usable):
    for letter in word:
        if letter not in usable:
            return False
    return True

def uses_all(word, to_use):
    for letter in to_use:
        if letter not in word:
            return False
    return True

def uses_all1(word, to_use):
    return uses_only(to_use, word)

def has_3consecutive_double_letters(word):
    i = 0
    count = 0
    while i < len(word) - 1:
        if word[i] == word[i + 1]:
            count = count + 1
            if count == 3:
                return True
            i = i + 2
        else:
            count = 0
            i = i + 1
    return False

def has_2consecutive_double_letters(word):
    i = 0
    count = 0
    while i < len(word) - 1:
        if word[i] == word[i + 1]:
            count = count + 1
            if count == 2:
                return True
            i = i + 2
        else:
            count = 0
            i = i + 1
    return False

def authentication(f, word):
    if f(word):
        return 'Sei autenticato'
    return 'Password errata!'

def is_palindronic_odometer(number):
    if len(number) != 6:
        return
    i = 2
    j = 5
    count = 0
    while count <= 3:
        if number[i] != number[j] or number[i+1] != number[j-1]:
            return False
        count = count + 1
        number = str(int(number) + 1)
        if count == 1:
            i = i - 1
        if count == 2:
            j = j - 1
        if count == 3:
            i = i - 1
            j = j + 1
            if number [i+2] != number [j-2]:
                return False
    return True

def has_palindrome(i, start, length):
    """Checks if the string representation of i has a palindrome.
    i: integer
    start: where in the string to start
    length: length of the palindrome to check for
    """
    s = str(i)[start:start+length]
    return s[::-1] == s

def check(i):
    """Checks if the integer (i) has the desired properties.
    i: int
    """
    return (has_palindrome(i, 2, 4) and
            has_palindrome(i+1, 1, 5) and
            has_palindrome(i+2, 1, 4) and
            has_palindrome(i+3, 0, 6))

def check_all():
    """Enumerate the six-digit numbers and print any winners.
    """
    i = 100000
    while i <= 999996:
        if check(i):
            print(i)
        i = i + 1

# print('The following are the possible odometer readings:')
# check_all()


import os

from flask import Flask, request, abort
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, QuickReplyButton, MessageAction, QuickReply, TextSendMessage)

app = Flask(__name__)

LINE_CHANNEL_ACCESS_TOKEN = os.environ["LINE_CHANNEL_ACCESS_TOKEN"]
LINE_CHANNEL_SECRET = os.environ["LINE_CHANNEL_SECRET"]

line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(LINE_CHANNEL_SECRET)

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def diagnosis_message(event):
    answer_list = [1, 2, 3, 4, 5]

    items = [QuickReplyButton(action=MessageAction(label=f"{language}", text=f"{language}")) for language in answer_list]

    messages = TextSendMessage(text="診断①(1〜5で答えてください。)",
                               quick_reply=QuickReply(items=items))

    line_bot_api.reply_message(event.reply_token, messages=messages)

if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)


'''Goals are a collection of phases which are automatically added to a build, then executed.

The idea here is for the calling code to not need to know about the right phases to include
when getting a higher-level "goal" done; e.g. running or generating an app.
'''
import platform
import sys
import os
import tempfile

from lib import BASE_EXCEPTION

def log_build(build, action):
	'''
	Bundle together some stats and send it to the server for tracking
	This is called by every other function in this module, just before running
	the build.
	'''
	from forge import build_config
	import forge
	from forge.remote import Remote

	log = {}
	log['action']	     = action
	log['platform']	     = platform.platform()
	log['version']	     = sys.version
	log['uuid']	     = build.config['uuid']
	log['tools_version'] = forge.VERSION
	config = build_config.load()
	remote = Remote(config)
	remote._authenticate()
	remote._api_post('track/', data=log)

def generate_app_from_template(generate_module, build_to_run):
	'''Inject code into a previously built template.

	:param generate_module: the :mod:`generate.generate` module
	:param build_to_run: a :class:`build.Build` instance
	'''
	add_check_settings_steps(generate_module, build_to_run)
	build_to_run.add_steps(generate_module.customer_phases.resolve_urls())
	if os.path.isdir('hooks/prebuild'):
		# Create a temp dir
		tempdir = tempfile.mkdtemp()
		os.rmdir(tempdir)
		build_to_run.add_steps(generate_module.customer_phases.copy_user_source_to_tempdir(ignore_patterns=build_to_run.ignore_patterns, tempdir=tempdir))
		build_to_run.add_steps(generate_module.customer_phases.run_hook(hook='prebuild', dir=tempdir))
		build_to_run.add_steps(generate_module.customer_phases.validate_user_source(src=tempdir))
		build_to_run.add_steps(generate_module.customer_phases.copy_user_source_to_template(ignore_patterns=build_to_run.ignore_patterns, src=tempdir))
		# Delete temp dir
		build_to_run.add_steps(generate_module.customer_phases.delete_tempdir(tempdir=tempdir))
	else:
		build_to_run.add_steps(generate_module.customer_phases.validate_user_source())
		build_to_run.add_steps(generate_module.customer_phases.copy_user_source_to_template(ignore_patterns=build_to_run.ignore_patterns))
	build_to_run.add_steps(generate_module.customer_phases.include_platform_in_html())
	build_to_run.add_steps(generate_module.customer_phases.include_name())
	build_to_run.add_steps(generate_module.customer_phases.include_uuid())
	build_to_run.add_steps(generate_module.customer_phases.include_author())
	build_to_run.add_steps(generate_module.customer_phases.include_description())
	build_to_run.add_steps(generate_module.customer_phases.include_version())
	build_to_run.add_steps(generate_module.customer_phases.include_reload())
	build_to_run.add_module_steps("customer_phase")
	build_to_run.add_steps(generate_module.customer_phases.make_installers())
	if os.path.isdir('hooks/postbuild'):
		build_to_run.add_steps(generate_module.customer_phases.run_hook(hook='postbuild', dir='development'))

	log_build(build_to_run, "generate")
	build_to_run.run()

def run_app(generate_module, build_to_run):
	'''Run a generated app on a device or emulator.

	:param generate_module: the :mod:`generate.generate` module
	:param build_to_run: a :class:`build.Build` instance
	'''
	add_check_settings_steps(generate_module, build_to_run)

	if len(build_to_run.enabled_platforms) != 1:
		raise BASE_EXCEPTION("Expected to run exactly one platform at a time")

	target = list(build_to_run.enabled_platforms)[0]
	if target == 'android':
		interactive = build_to_run.tool_config.get('general.interactive', True)
		sdk = build_to_run.tool_config.get('android.sdk')
		device = build_to_run.tool_config.get('android.device')
		purge = build_to_run.tool_config.get('android.purge')

		build_to_run.add_steps(
			generate_module.customer_phases.run_android_phase(
				build_to_run.output_dir,
				sdk=sdk,
				device=device,
				interactive=interactive,
				purge=purge,
			)
		)
	elif target == 'ios':
		device = build_to_run.tool_config.get('ios.device')

		build_to_run.add_steps(
			generate_module.customer_phases.run_ios_phase(device)
		)
	elif target == 'firefox':
		build_to_run.add_steps(
			generate_module.customer_phases.run_firefox_phase(build_to_run.output_dir)
		)
	elif target == 'web':
		build_to_run.add_steps(
			generate_module.customer_phases.run_web_phase()
		)
	elif target == 'wp':
		device = build_to_run.tool_config.get('wp.device')

		build_to_run.add_steps(
			generate_module.customer_phases.run_wp_phase(device)
		)

	elif target == 'chrome':
		build_to_run.add_steps(
			generate_module.customer_phases.run_chrome_phase()
		)

	log_build(build_to_run, "run")
	build_to_run.run()

def package_app(generate_module, build_to_run):
	add_check_settings_steps(generate_module, build_to_run)

	if len(build_to_run.enabled_platforms) != 1:
		raise BASE_EXCEPTION("Expected to package exactly one platform at a time")

	build_to_run.add_steps(
		generate_module.customer_phases.package(build_to_run.output_dir)
	)
	log_build(build_to_run, "package")
	build_to_run.run()

def add_check_settings_steps(generate_module, build_to_run):
	"""
	Required steps to sniff test the JavaScript and local configuration
	"""
	build_to_run.add_steps(generate_module.customer_phases.check_javascript())
	build_to_run.add_steps(generate_module.customer_phases.check_local_config_schema())

def check_settings(generate_module, build_to_run):
	"""
	Check the validity of locally configured settings.
	"""
	add_check_settings_steps(generate_module, build_to_run)

	build_to_run.run()

def add_migrate_app_steps(generate_module, build_to_run):
	"""
	Required steps to sniff test the JavaScript and local configuration
	"""
	build_to_run.add_steps(generate_module.customer_phases.migrate_config())

def migrate_app(generate_module, build_to_run):
	"""
	Migrate the app to the next major platform (if possible)
	"""
	add_migrate_app_steps(generate_module, build_to_run)

	build_to_run.run()

def cleanup_after_interrupted_run(generate_module, build_to_run):
	"""
	Cleanup after a run operation that was interrupted forcefully.

	This is exposed so the Trigger Toolkit can cleanup anything lingering from a run operation,
	e.g. node, adb, and any locks they have on files in the development folder
	"""
	build_to_run.add_steps(generate_module.customer_phases.clean_phase())
	build_to_run.run()


from tkinter import *
from PIL import *
from PIL import Image, ImageTk
import imageio
from threading import Thread
from playsound import playsound

class MessageScreen(Frame):
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.pack_propagate(False)
        self.bg = kwargs['bg']

    def display_text(self, text):
        Label(self, text=text, bg=self.bg, justify=LEFT, anchor='w').pack(fill=X)

    def clear(self):
        for child in self.winfo_children():
            child.destroy()

    def display_image(self, filename):
        img = Image.open(filename)
        img = img.resize((100, 100))
        self.photo = ImageTk.PhotoImage(img)
        Label(self,
              image=self.photo,
              bg=self.bg,
              justify=LEFT,
              anchor='w'
          ).pack(fill=X)

    def stream(self):
        for image in self.video.iter_data():
            frame_image = Image.fromarray(image)
            frame_image = frame_image.resize((100, 100))
            frame_image = ImageTk.PhotoImage(frame_image)
            self.l.config(image=frame_image)
            self.l.image = frame_image

    def display_video(self, filename):
        self.video = imageio.get_reader(filename)
        delay = int(1000 / self.video.get_meta_data()['fps'])
        self.l = Label(self,
              bg=self.bg,
              justify=LEFT,
              anchor='w'
        )
        self.l.pack(fill=X)
        Thread(target=self.stream, daemon=True).start()

    def display_audio(self, filename):
        play_label = Label(self,
                           bg=self.bg,
                           justify=LEFT,
                           anchor='w'
        )
        play_label.pack(fill=X)
        Button(play_label,
               text='Play',
               command=lambda: playsound(filename)
        ).pack()


import os
import csv

class LibraryManagementSystem:

    def __init__(self):
        self.book_list = []
        self.load_books()

    def load_books(self):
        try:
            with open('books.csv', mode='r', newline='') as file:
                reader = csv.reader(file)
                self.book_list = [row for row in reader]
        except FileNotFoundError:
            print("No previous data found.")

    def save_books(self):
        with open('books.csv', mode='w', newline='') as file:
            writer = csv.writer(file)
            writer.writerows(self.book_list)

    def clearScreen(self):
        if os.name == 'posix':
            os.system('clear')
        else:
            os.system('cls')

    def isValidBookDetails(self, book_id, book_title, book_author):
        return book_id and book_title and book_author

    def addBook(self):
        self.clearScreen()
        print("Add Book")

        book_id = input("\nEnter Book ID: ")
        book_title = input("Enter Book Title: ")
        book_author = input("Enter Book Author: ")

        if not self.isValidBookDetails(book_id, book_title, book_author):
            print("Invalid book details.")
        else:
            self.book_list.append([book_id, book_title, book_author])
            print(f"\n{book_title} added successfully ")
            self.save_books()

    def deleteBook(self):
        self.clearScreen()
        print("Remove Book")

        book_id = input("\nEnter Book ID to delete: ")

        for book in self.book_list:
            if book[0] == book_id:
                self.book_list.remove(book)
                print("Book deleted successfully!")
                self.save_books()
                return
        print("\nBook ID does not exist.")

    def searchBook(self):
        self.clearScreen()
        print("Search Book")

        book_id = input("\nEnter Book ID to search: ")
        for book in self.book_list:
            if book[0] == book_id:
                print(f"Book found: ID:{book[0]}, Title:{book[1]}, Author:{book[2]}")
                return
        print("\nBook ID does not exist.")

    def updateBook(self):
        self.clearScreen()
        print("Update Book")
        book_id = input("\nEnter Book ID to update: ")
        for book in self.book_list:
            if book[0] == book_id:
                new_title = input("Enter new title: ")
                new_author = input("Enter new author: ")
                if not self.isValidBookDetails(book_id, new_title, new_author):
                    print("Invalid details. Try again.")
                    return
                book[1] = new_title
                book[2] = new_author
                print("Book updated successfully!")
                self.save_books()
                return
        print("\nBook ID does not exist.")

    def viewAllBooks(self):
        for book in self.book_list:
            print(f"ID: {book[0]}, Title: {book[1]}, Author: {book[2]}")

    def runMenu(self):
        while True:
            print("\nLibrary Management System")
            print("\n1. Add book")
            print("2. Update book")
            print("3. List all book")
            print("4. Search book")
            print("5. Remove book")
            print("6. Exit")

            choice = input("\nEnter your choice: ")

            if choice == '1':
                self.addBook()
            elif choice == '2':
                self.updateBook()
            elif choice == '3':
                self.viewAllBooks()
            elif choice == '4':
                self.searchBook()
            elif choice == '5':
                self.deleteBook()
            elif choice == '6':
                print("\nExiting system.")
                break
            else:
                print("\nInvalid choice. Try again.")

            input("\nPress Enter to continue...")
            self.clearScreen()

if __name__ == "__main__":
    system = LibraryManagementSystem()
    system.runMenu()