# Variable section
MENU = {
    "espresso": {
        "ingredients": {
            "water": 50,
            "coffee": 18,
        },
        "cost": 1.5,
    },
    "latte": {
        "ingredients": {
            "water": 200,
            "milk": 150,
            "coffee": 24,
        },
        "cost": 2.5,
    },
    "cappuccino": {
        "ingredients": {
            "water": 250,
            "milk": 100,
            "coffee": 24,
        },
        "cost": 3.0,
    }
}

resources = {
    "water": 300,
    "milk": 200,
    "coffee": 100,
}

# Function section
def report(res, profit):
    """Prints the Resources left in the Coffee machine """
    print(
        f"Resources Left : \nWater : {res['water']}ml\tMilf : {res['milk']}ml\tCoffee : {res['coffee']}gm\t Money : {profit}")

def is_order_drink_available(order_ingredients, res):
    """Checks the ingredients availability in the machine and returns that if we can make the drink or not with the
    available resource """
    is_enough = True
    for items in order_ingredients:
        if order_ingredients[items] >= res[items]:
            print(f"Sorry, There is not enough {items}")
            is_enough = False
    return is_enough

def process_coins():
    """Calculate the total value to the coins inserted by the user and gives the total amount"""
    print("Enter the Coins : ")
    total = int(input("Enter the number of quarters (1 quarters = 0.25$) : ")) * 0.25
    total += int(input("Enter the number of dimes (1 dimes = 0.10$) : ")) * 0.10
    total += int(input("Enter the number of nickles (1 nickles = 0.05$) : ")) * 0.05
    total += int(input("Enter the number of pennies (1 pennies = 0.01$) : ")) * 0.01
    return total

def is_transaction_successful(money_received, drink_cost):
    """Returns True if the transaction is successful and return False if the transaction is false"""
    if money_received > drink_cost:
        change = round(money_received - drink_cost, 2)
        print(f"Here is your change {change}$")
        global profit
        profit += drink_cost
        return True
    else:
        print("“Sorry that's not enough money. The Money will be refunded back.")
        return False

def making_the_drink(drink_name, order_ingredients):
    """Deducts the amount of resources for making the coffee"""
    for items in order_ingredients:
        resources[items] -= order_ingredients[items]
    print(f"Here is your {drink_name}")

# Main Code
profit = 0
is_on = True
while is_on:
    choice = input("What would you like to get (espresso/latte/cappuccino) : ").lower()
    if choice == "report":
        report(resources, profit)
    elif choice == "off":
        is_on = False
    else:
        drink = MENU[choice]
        if is_order_drink_available(drink["ingredients"], resources):
            payment = process_coins()
            if is_transaction_successful(payment, drink["cost"]):
                making_the_drink(choice, drink["ingredients"])


from typing import TypeVar, Union

# For tensor functions
import torch
import torch.nn.functional as F
from torch import Tensor

# Custom types for method binding at typecheck
NodeType = TypeVar("NodeType", bound="Node")
AtomType = TypeVar("AtomType", bound="Atom")
EqualType = TypeVar("EqualType", bound="Equal")
NotType = TypeVar("NotType", bound="Not")
AndType = TypeVar("AndType", bound="And")
OrType = TypeVar("OrType", bound="Or")
GloballyType = TypeVar("GloballyType", bound="Globally")
EventuallyType = TypeVar("EventuallyType", bound="Eventually")

def eventually(x: Tensor, time_span: Union[float, int]) -> Tensor:
    """
    STL operator 'eventually' in 1D.

    Parameters
    ----------
    x: torch.Tensor
        Signal
    time_span: any numeric type
        Timespan duration

    Returns
    -------
    torch.Tensor
    A tensor containing the result of the operation.
    """
    return F.max_pool1d(x, kernel_size=time_span, stride=1)

class Node:
    """Abstract node class for STL semantics tree."""

    def __init__(self: NodeType) -> None:
        # Must be overloaded.
        pass

    def __str__(self: NodeType) -> str:
        # Must be overloaded.
        pass

    def boolean(
        self: NodeType, x: Tensor, evaluate_at_all_times: bool = False
    ) -> Tensor:
        """
        Evaluates the boolean semantics at the node.

        Parameters
        ----------
        x : torch.Tensor, of size N_samples x N_vars x N_sampling_points
            The input signals, stored as a batch tensor with trhee dimensions.
        evaluate_at_all_times: bool
            Whether to evaluate the semantics at all times (True) or
            just at t=0 (False).

        Returns
        -------
        torch.Tensor
        A tensor with the boolean semantics for the node.
        """
        z: Tensor = self._boolean(x)
        if evaluate_at_all_times:
            return z
        else:
            return self._extract_semantics_at_time_zero(z)

    def quantitative(
        self: NodeType,
        x: Tensor,
        normalize: bool = False,
        evaluate_at_all_times: bool = False,
    ) -> Tensor:
        """
        Evaluates the quantitative semantics at the node.

        Parameters
        ----------
        x : torch.Tensor, of size N_samples x N_vars x N_sampling_points
            The input signals, stored as a batch tensor with three dimensions.
        normalize: bool
            Whether the measure of robustness if normalized (True) or
            not (False). Currently not in use.
        evaluate_at_all_times: bool
            Whether to evaluate the semantics at all times (True) or
            just at t=0 (False).

        Returns
        -------
        torch.Tensor
        A tensor with the quantitative semantics for the node.
        """
        z: Tensor = self._quantitative(x, normalize)
        if evaluate_at_all_times:
            return z
        else:
            return self._extract_semantics_at_time_zero(z)

    def set_normalizing_flag(self: NodeType, value: bool = True) -> None:
        """
        Setter for the 'normalization of robustness of the formula' flag.
        Currently not in use.
        """

    def time_depth(self: NodeType) -> Union[float, int]:
        """Returns time depth of bounded temporal operators only."""
        # Must be overloaded.

    def _quantitative(self: NodeType, x: Tensor, normalize: bool = False) -> Tensor:
        """Private method equivalent to public one for inner call."""
        # Must be overloaded.

    def _boolean(self: NodeType, x: Tensor) -> Tensor:
        """Private method equivalent to public one for inner call."""
        # Must be overloaded.

    @staticmethod
    def _extract_semantics_at_time_zero(x: Tensor) -> Tensor:
        """Extrapolates the vector of truth values at time zero"""
        return torch.reshape(x[:, 0, 0], (-1,))

class Atom(Node):
    """Atomic formula node; for now of the form X<=t or X>=t"""

    def __init__(
        self: AtomType, var_index: int, threshold: Union[float, int], lte: bool = False
    ) -> None:
        super().__init__()
        self.var_index: int = var_index
        self.threshold: Union[float, int] = threshold
        self.lte: bool = lte

    def __str__(self: AtomType) -> str:
        s: str = (
            "x_"
            + str(self.var_index)
            + (" <= " if self.lte else " >= ")
            + str(self.threshold)
        )
        return s

    def time_depth(self: AtomType) -> Union[float, int]:
        return 0

    def _boolean(self: AtomType, x: Tensor) -> Tensor:
        # extract tensor of the same dimension as data, but with only one variable
        xj: Tensor = x[:, self.var_index, :]
        xj: Tensor = xj.view(xj.size()[0], 1, -1)
        if self.lte:
            z: Tensor = torch.le(xj, self.threshold)
        else:
            z: Tensor = torch.ge(xj, self.threshold)
        return z

    def _quantitative(self: AtomType, x: Tensor, normalize: bool = False) -> Tensor:
        # extract tensor of the same dimension as data, but with only one variable
        xj: Tensor = x[:, self.var_index, :]
        xj: Tensor = xj.view(xj.size()[0], 1, -1)
        if self.lte:
            z: Tensor = -xj + self.threshold
        else:
            z: Tensor = xj - self.threshold
        if normalize:
            z: Tensor = torch.tanh(z)
        return z

class Equal(Node):
    """Atomic formula node; for now of the form X==t"""

    def __init__(self: EqualType, var_index: int, rhs: Union[float, int]) -> None:
        super().__init__()
        self.var_index: int = var_index
        self.rhs: Union[float, int] = rhs

    def __str__(self: EqualType) -> str:
        s: str = "x_" + str(self.var_index) + (" ==") + str(self.rhs)
        return s

    def time_depth(self: EqualType) -> Union[float, int]:
        return 0

    def _boolean(self: EqualType, x: Tensor) -> Tensor:
        # extract tensor of the same dimension as data, but with only one variable
        xj: Tensor = x[:, self.var_index, :]
        xj: Tensor = xj.view(xj.size()[0], 1, -1)
        z: Tensor = torch.eq(xj, self.rhs)
        return z

    def _quantitative(self: EqualType, x: Tensor, normalize: bool = False) -> Tensor:
        # extract tensor of the same dimension as data, but with only one variable
        xj: Tensor = x[:, self.var_index, :]
        xj: Tensor = xj.view(xj.size()[0], 1, -1)
        z: Tensor = -torch.absolute(xj - self.rhs)
        if normalize:
            z: Tensor = torch.tanh(z)
        return z

class Not(Node):
    """Negation node."""

    def __init__(self: NotType, child: Node) -> None:
        super().__init__()
        self.child: Node = child

    def __str__(self: NotType) -> str:
        s: str = "not ( " + self.child.__str__() + " )"
        return s

    def time_depth(self: NotType) -> Union[float, int]:
        return self.child.time_depth()

    def _boolean(self: NotType, x: Tensor) -> Tensor:
        z: Tensor = ~self.child._boolean(x)
        return z

    def _quantitative(self: NotType, x: Tensor, normalize: bool = False) -> Tensor:
        z: Tensor = -self.child._quantitative(x, normalize)
        return z

class And(Node):
    """Conjunction node."""

    def __init__(self: AndType, left_child: Node, right_child: Node) -> None:
        super().__init__()
        self.left_child: Node = left_child
        self.right_child: Node = right_child

    def __str__(self: AndType) -> str:
        s: str = (
            "( "
            + self.left_child.__str__()
            + " and "
            + self.right_child.__str__()
            + " )"
        )
        return s

    def time_depth(self: AndType) -> Union[float, int]:
        return max(self.left_child.time_depth(), self.right_child.time_depth())

    def _boolean(self: AndType, x: Tensor) -> Tensor:
        z1: Tensor = self.left_child._boolean(x)
        z2: Tensor = self.right_child._boolean(x)
        size: int = min(z1.size()[2], z2.size()[2])
        z1: Tensor = z1[:, :, :size]
        z2: Tensor = z2[:, :, :size]
        z: Tensor = torch.logical_and(z1, z2)
        return z

    def _quantitative(self: AndType, x: Tensor, normalize: bool = False) -> Tensor:
        z1: Tensor = self.left_child._quantitative(x, normalize)
        z2: Tensor = self.right_child._quantitative(x, normalize)
        size: int = min(z1.size()[2], z2.size()[2])
        z1: Tensor = z1[:, :, :size]
        z2: Tensor = z2[:, :, :size]
        z: Tensor = torch.min(z1, z2)
        return z

class Or(Node):
    """Disjunction node."""

    def __init__(self: OrType, left_child: Node, right_child: Node) -> None:
        super().__init__()
        self.left_child: Node = left_child
        self.right_child: Node = right_child

    def __str__(self: OrType) -> str:
        s: str = (
            "( "
            + self.left_child.__str__()
            + " or "
            + self.right_child.__str__()
            + " )"
        )
        return s

    def time_depth(self: OrType) -> Union[float, int]:
        return max(self.left_child.time_depth(), self.right_child.time_depth())

    def _boolean(self: OrType, x: Tensor) -> Tensor:
        z1: Tensor = self.left_child._boolean(x)
        z2: Tensor = self.right_child._boolean(x)
        size: int = min(z1.size()[2], z2.size()[2])
        z1: Tensor = z1[:, :, :size]
        z2: Tensor = z2[:, :, :size]
        z: Tensor = torch.logical_or(z1, z2)
        return z

    def _quantitative(self: OrType, x: Tensor, normalize: bool = False) -> Tensor:
        z1: Tensor = self.left_child._quantitative(x, normalize)
        z2: Tensor = self.right_child._quantitative(x, normalize)
        size: int = min(z1.size()[2], z2.size()[2])
        z1: Tensor = z1[:, :, :size]
        z2: Tensor = z2[:, :, :size]
        z: Tensor = torch.max(z1, z2)
        return z

class Globally(Node):
    """Globally node."""

    def __init__(
        self: GloballyType,
        child: Node,
        unbound: bool = True,
        time_bound: Union[float, int] = 0.0,
        adapt_unbound: bool = True,
    ) -> None:
        super().__init__()
        self.child: Node = child
        self.unbound: bool = unbound
        self.time_bound: Union[float, int] = time_bound + 1
        self.adapt_unbound: bool = adapt_unbound

    def __str__(self: GloballyType) -> str:
        s0: str = "[0," + str(self.time_bound) + "]" if not self.unbound else ""
        s: str = "always" + s0 + " ( " + self.child.__str__() + " )"
        return s

    def time_depth(self: GloballyType) -> Union[float, int]:
        if self.unbound:
            return self.child.time_depth()
        else:
            return self.child.time_depth() + self.time_bound - 1

    def _boolean(self: GloballyType, x: Tensor) -> Tensor:
        z1: Tensor = self.child._boolean(x)
        if self.unbound:
            if self.adapt_unbound:
                z: Tensor
                _: Tensor
                z, _ = torch.cummin(torch.flip(z1, [2]), dim=2)
                z: Tensor = torch.flip(z, [2])
            else:
                z: Tensor
                _: Tensor
                z, _ = torch.min(z1, 2, keepdim=True)
        else:
            z: Tensor = torch.ge(1.0 - eventually((~z1).double(), self.time_bound), 0.5)
        return z

    def _quantitative(self: GloballyType, x: Tensor, normalize: bool = False) -> Tensor:
        z1: Tensor = self.child._quantitative(x, normalize)
        if self.unbound:
            if self.adapt_unbound:
                z: Tensor
                _: Tensor
                z, _ = torch.cummin(torch.flip(z1, [2]), dim=2)
                z: Tensor = torch.flip(z, [2])
            else:
                z: Tensor
                _: Tensor
                z, _ = torch.min(z1, 2, keepdim=True)
        else:
            z: Tensor = -eventually(-z1, self.time_bound)
        return z

class Eventually(Node):
    """Eventually node."""

    def __init__(
        self: EventuallyType,
        child: Node,
        unbound: bool = True,
        time_bound: Union[float, int] = 0.0,
        adapt_unbound: bool = True,
    ) -> None:
        super().__init__()
        self.child: Node = child
        self.unbound: bool = unbound
        self.time_bound: Union[float, int] = time_bound + 1
        self.adapt_unbound: bool = adapt_unbound

    def __str__(self: EventuallyType) -> str:
        s0: str = "[0," + str(self.time_bound) + "]" if not self.unbound else ""
        s: str = "eventually" + s0 + " ( " + self.child.__str__() + " )"
        return s

    def time_depth(self: EventuallyType) -> Union[float, int]:
        if self.unbound:
            return self.child.time_depth()
        else:
            return self.child.time_depth() + self.time_bound - 1

    def _boolean(self: EventuallyType, x: Tensor) -> Tensor:
        z1: Tensor = self.child._boolean(x)
        if self.unbound:
            if self.adapt_unbound:
                z: Tensor
                _: Tensor
                z, _ = torch.cummax(torch.flip(z1, [2]), dim=2)
                z: Tensor = torch.flip(z, [2])
            else:
                z: Tensor
                _: Tensor
                z, _ = torch.max(z1, 2, keepdim=True)
        else:
            z: Tensor = torch.ge(eventually(z1.double(), self.time_bound), 0.5)
        return z

    def _quantitative(
        self: EventuallyType, x: Tensor, normalize: bool = False
    ) -> Tensor:
        z1: Tensor = self.child._quantitative(x, normalize)
        if self.unbound:
            if self.adapt_unbound:
                z: Tensor
                _: Tensor
                z, _ = torch.cummax(torch.flip(z1, [2]), dim=2)
                z: Tensor = torch.flip(z, [2])
            else:
                z: Tensor
                _: Tensor
                z, _ = torch.max(z1, 2, keepdim=True)
        else:
            z: Tensor = eventually(z1, self.time_bound)
        return z


"""
binary dilation in numba cuda
>> python cuda_binary_dilate_3d.py 512 512 512 1
>>> runtime: 0.001s on a Nvidia TitanXP.
"""
import numba
import numpy as np
import time
from numba import cuda
import pickle

# Whether to check output - reference version output's pickle not stored in git
# repo due to size.
CHECK_OUTPUT = True

# Hardcode dimension in kernel to allow for better compiler optimization
NKERN = 3

@cuda.jit
def cuda_binary_dilate_u8(vol, out, kern):
    z, y, x = cuda.grid(3)
    d, h, w = vol.shape
    pa, pb, pc = NKERN // 2, NKERN // 2, NKERN // 2

    tx, ty, tz = cuda.threadIdx.x, cuda.threadIdx.y, cuda.threadIdx.z

    if z < d and y < h and x < w:
        out[z, y, x] = False

        # put kern in shared memory?
        shared = cuda.shared.array((NKERN, NKERN, NKERN), dtype=numba.boolean)
        if tx < NKERN and ty < NKERN and tz < NKERN:
            shared[tx, ty, tz] = kern[tx, ty, tz]

        cuda.syncthreads()

        for i in range(NKERN):
            for j in range(NKERN):
                for k in range(NKERN):
                    zz = z + i - pa
                    yy = y + j - pb
                    xx = x + k - pc
                    if (zz < 0 or zz >= d or yy < 0 or yy >= h or xx < 0
                            or xx >= w):
                        continue
                    if vol[zz, yy, xx] and shared[i, j, k]:
                        out[z, y, x] = True

def binary_dilate_u8_cuda(vol, kern, iterations=1):
    sizes = vol.shape
    block_dim = (4, 4, 4)
    # stride
    sizes = [v for v in sizes]
    grid_dim = tuple(int(np.ceil(a / b)) for a, b in zip(sizes, block_dim))

    # non inplace
    ycu = cuda.device_array(shape=vol.shape, dtype=np.uint8)
    a, b = ycu, vol
    for i in range(iterations):
        a, b = b, a
        cuda_binary_dilate_u8[grid_dim, block_dim](a, b, kern)
    return b

def test(d=512, h=512, w=512, niter=1):
    np.random.seed(1)
    vol = np.random.randn(d, h, w) > 1
    kern = np.random.randn(3, 3, 3) > 1

    vol = cuda.to_device(vol)
    kern = cuda.to_device(kern)

    # Warm up / compile
    res = binary_dilate_u8_cuda(vol, kern, niter)

    if CHECK_OUTPUT:
        with open('reference.pickle', 'rb') as f:
            ref = pickle.load(f)

        np.testing.assert_equal(res.copy_to_host(), ref)

    t1 = time.time()
    for i in range(10):
        binary_dilate_u8_cuda(vol, kern, niter)
    cuda.synchronize()
    t2 = time.time()

    print(f" runtime: {(t2-t1)/10}")  # i measure 1ms

if __name__ == "__main__":
    import sys
    args = [int(a) for a in sys.argv[1:]]
    test(*args)


from typing import List

from util import clean_html, strip_weird_t

class FDASubstanceListInfo:

    def __init__(self):
        self.names: List[str] = []
        self.cas_id: str = ""
        self.fema_num: str = ""
        self.technical_effs: List[str] = []

    def to_dict(self):
        return {
            "names": self.names,
            "cas_id": self.cas_id,
            "fema_num": self.fema_num,
            "technical_effects": self.technical_effs
        }

    @classmethod
    def from_dict(cls, data):
        item = FDASubstanceListInfo()
        item.names = data["names"]
        item.cas_id = data["cas_id"]
        item.fema_num = data["fema_num"]
        item.technical_effs = data["technical_effects"]
        return item

def get_names(row) -> List[str]:
    primary_name = row["Substance"].strip()
    alternate_names_txt = row["Other Names"]
    names = [primary_name]
    if not alternate_names_txt:
        return names
    text = clean_html(alternate_names_txt)
    split_names = text.split("♦")
    for n in split_names:
        if n.strip():
            names.append(n.strip())
    return list(set(names))

def get_cas_id(row) -> str:
    return row["CAS Reg No (or other ID)"].strip()

def get_fema_num(row) -> str:
    fema_text = row["FEMA No"]
    if fema_text:
        return strip_weird_t(fema_text)
    return ""

def get_technical_effs(row) -> List[str]:
    tech_effs_str = row["Used for (Technical Effect)"]
    tech_effs_str = clean_html(tech_effs_str)
    split_tech_effs = tech_effs_str.split(",")
    tech_effs = []
    for s in split_tech_effs:
        if s.strip():
            tech_effs.append(s.strip())
    return tech_effs

def process():
    import pandas as pd
    import json
    df = pd.read_csv("./data/fda_food_substances.csv", keep_default_na=False, dtype="str")
    results: List[FDASubstanceListInfo] = []
    for _, row in df.iterrows():
        info = FDASubstanceListInfo()
        info.names = get_names(row)
        info.fema_num = get_fema_num(row)
        info.technical_effs = get_technical_effs(row)
        info.cas_id = get_cas_id(row)
        results.append(info)

    with open("./export/fda_food_substances.json", "w") as f:
        json.dump([r.to_dict() for r in results], f, indent=4, sort_keys=True)

process()


import tkinter
from tkinter import *

expression = ""

def press(num):
	global expression

	expression = expression + str(num)

	equation.set(expression)

# Function to evaluate the final expression.
def equalpress():
	try:
		global expression
		total = str(eval(expression))
		equation.set(total)
		expression = ""

	except:
		equation.set(" error ")
		expression = ""

# Functions of text entry box.
def clear():
	global expression
	expression = ""
	equation.set("")

# Driver/GUI code
if __name__ == "__main__":
	# creating a GUI window.
	gui = Tk()

	# setting the background colour of GUI window.
	gui.configure(background="light blue")

	# setting the title of GUI window.
	gui.title("Calculator")

	# setting the configuration of GUI window.
	gui.geometry("280x150")

	# StringVar() is the variable class then we create an instance of this class.
	equation = StringVar()

	# create the text entry box for showing the expression.
	expression_field = Entry(gui, textvariable=equation)

	# grid method is used for placing the widgets at respective position in table like structure.
	expression_field.grid(columnspan=4, ipadx=80, ipady=10)

	# create a Buttons and place at a particularlocation inside the root window when user press the button, the command or function affiliated to that button is executed.
	button1 = Button(gui, text=' 1 ', fg='black', bg='white',
					command=lambda: press(1), height=1, width=7)
	button1.grid(row=3, column=0)

	button2 = Button(gui, text=' 2 ', fg='black', bg='white',
					command=lambda: press(2), height=1, width=7)
	button2.grid(row=3, column=1)

	button3 = Button(gui, text=' 3 ', fg='black', bg='white',
					command=lambda: press(3), height=1, width=7)
	button3.grid(row=3, column=2)

	button4 = Button(gui, text=' 4 ', fg='black', bg='white',
					command=lambda: press(4), height=1, width=7)
	button4.grid(row=4, column=0)

	button5 = Button(gui, text=' 5 ', fg='black', bg='white',
					command=lambda: press(5), height=1, width=7)
	button5.grid(row=4, column=1)

	button6 = Button(gui, text=' 6 ', fg='black', bg='white',
					command=lambda: press(6), height=1, width=7)
	button6.grid(row=4, column=2)

	button7 = Button(gui, text=' 7 ', fg='black', bg='white',
					command=lambda: press(7), height=1, width=7)
	button7.grid(row=5, column=0)

	button8 = Button(gui, text=' 8 ', fg='black', bg='white',
					command=lambda: press(8), height=1, width=7)
	button8.grid(row=5, column=1)

	button9 = Button(gui, text=' 9 ', fg='black', bg='white',
					command=lambda: press(9), height=1, width=7)
	button9.grid(row=5, column=2)

	button0 = Button(gui, text=' 0 ', fg='black', bg='white',
					command=lambda: press(0), height=1, width=7)
	button0.grid(row=6, column=0)

	plus = Button(gui, text=' + ', fg='black', bg='white',
				command=lambda: press("+"), height=1, width=7)
	plus.grid(row=3, column=3)

	minus = Button(gui, text=' - ', fg='black', bg='white',
				command=lambda: press("-"), height=1, width=7)
	minus.grid(row=4, column=3)

	multiply = Button(gui, text=' * ', fg='black', bg='white',
					command=lambda: press("*"), height=1, width=7)
	multiply.grid(row=5, column=3)

	divide = Button(gui, text=' / ', fg='black', bg='white',
					command=lambda: press("/"), height=1, width=7)
	divide.grid(row=6, column=3)

	equal = Button(gui, text=' = ', fg='black', bg='white',
				command=equalpress, height=1, width=7)
	equal.grid(row=6, column=2)

	clear = Button(gui, text='Clear', fg='black', bg='white',
				command=clear, height=1, width=7)
	clear.grid(row=6, column='1')

	Decimal= Button(gui, text='.', fg='black', bg='white',
					command=lambda: press('.'), height=1, width=7)
	Decimal.grid(row=7, column=0)

	Reminder= Button(gui, text='WARNING', fg='black', bg='white',
					command=lambda: press('THIS IS NOT A SCIENTIFIC CALCULATOR'), height=1, width=7)
	Reminder.grid(row=7, column=1)

	#HEHEHE
	Present= Button(gui, text='GIFT', fg='red', bg='black',
					command=lambda: press('https://www.youtube.com/watch?v=dQw4w9WgXcQ'), height=1, width=7)
	Present.grid(row=7, column=2)

	import tkinter as hehe
import webbrowser

def callback(event):
    webbrowser.open_new(event.widget.cget("text"))

root = hehe.Tk()
lbl = hehe.Label(root, text=r"https://www.youtube.com/watch?v=yPYZpwSpKmA", fg="blue", cursor="hand2")
lbl.pack()
lbl.bind("<Button-1>", callback)
root.mainloop()

# start the GUI
gui.mainloop()


"""Collection of classes to parse instantiate MLCube configuration from various sources.

- `MLCubeInstance`: Base class for various MLCube flavours (directory, archive ...).
- `MLCubeDirectory`: Class to work with directory-based MLCubes.
- `CliParser`: Helper utilities to parse command linea arguments.
"""
import abc
import os
import typing as t

from omegaconf import (DictConfig, OmegaConf)

class MLCubeInstance(abc.ABC):
    """Base class for different instantiations of MLCube (local, remote, directory, archive, container ...)."""

    @abc.abstractmethod
    def uri(self) -> str:
        """Return uniform resource identifier of this MLCube."""
        raise NotImplementedError

MLCubeInstanceType = t.TypeVar('MLCubeInstanceType', bound=MLCubeInstance)

class MLCubeDirectory(MLCubeInstance):
    """An MLCube instantiation as a local directory."""

    def __init__(self, path: t.Optional[str] = None) -> None:
        """Instantiate MLCube from mlcube.yaml file.

        Args:
            path: MLCube directory or path to mlcube.yaml. If None, current directory is used.
        """
        if path is None:
            path = os.getcwd()
        path = os.path.abspath(path)
        if os.path.isfile(path):
            self.path, self.file = os.path.split(path)
        else:
            self.path, self.file = os.path.abspath(path), 'mlcube.yaml'

    def uri(self) -> str:
        return os.path.join(self.path, self.file)

class CliParser(object):
    """Helper utilities to parse command linea arguments."""

    @staticmethod
    def parse_mlcube_arg(mlcube: t.Optional[str]) -> MLCubeInstanceType:
        """Parse value of the `--mlcube` command line argument.

        Args:
            mlcube: Path to a MLCube directory or `mlcube.yaml` file. If it's a directory, standard name
                `mlcube.yaml` is assumed for MLCube definition file.
        Returns:
            One of child classes of MLCubeInstance that represents this MLCube.
        """
        return MLCubeDirectory(mlcube)

    @staticmethod
    def parse_list_arg(arg: t.Optional[str], default: t.Optional[str] = None) -> t.List[str]:
        """Parse a string into list of strings using `,` as a separator.

        Args:
            arg: String if elements separated with `,`.
            default: Default value for `arg` if `arg` is None or empty.
        Returns:
            List of items.
        """
        arg = arg or default
        if not arg:
            return []
        return arg.split(',')

    @staticmethod
    def parse_extra_arg(*args: str) -> t.Tuple[DictConfig, t.Dict]:
        """Parse extra arguments on a command line.

        These arguments correspond to:
            - MLCube runtime arguments. These start with `-P` prefix and are translated to a nested dictionaries
                structure using `.` as a separator. For instance, `-Pdocker.image=mlcommons/mnist:0.0.1` translates to
                python dictionary {'docker': {'image': 'mlcommons/mnist:0.0.1'}}.
            - Task arguments are all other arguments that do not star with `-P`. These arguments are input/output
                arguments of tasks.
        Args:
            args: List of arguments that have not been parsed before.
        Returns:
            Tuple of two dictionaries: (mlcube_arguments, task_arguments).
        """
        mlcube_args = OmegaConf.from_dotlist([arg[2:] for arg in args if arg.startswith('-P')])

        task_args = [arg.split('=') for arg in args if not arg.startswith('-P')]
        task_args = {arg[0]: arg[1] for arg in task_args}

        return mlcube_args, task_args


"""
The hidden tunnel community.

Author(s): Egbert Bouman
"""
from __future__ import annotations

import binascii
import os
import random
import socket
import struct
from asyncio import CancelledError, gather, iscoroutine
from typing import TYPE_CHECKING, Any, Coroutine, Set, Tuple, cast

from ...bootstrapping.dispersy.bootstrapper import DispersyBootstrapper
from ...configuration import DISPERSY_BOOTSTRAPPER
from ...keyvault.private.libnaclkey import LibNaCLSK
from ...keyvault.public.libnaclkey import LibNaCLPK
from ...messaging.anonymization.pex import PexCommunity, PexSettings
from ...peer import Peer
from ...peerdiscovery.churn import RandomChurn
from ...peerdiscovery.discovery import RandomWalk
from ...peerdiscovery.network import Network
from ...taskmanager import task
from ...util import fail
from .caches import *
from .community import TunnelCommunity, TunnelSettings, unpack_cell
from .payload import *
from .tunnel import (
    CIRCUIT_ID_PORT,
    CIRCUIT_STATE_READY,
    CIRCUIT_TYPE_IP_SEEDER,
    CIRCUIT_TYPE_RP_DOWNLOADER,
    CIRCUIT_TYPE_RP_SEEDER,
    DESTROY_REASON_UNNEEDED,
    FORWARD,
    PEER_SOURCE_DHT,
    PEER_SOURCE_PEX,
    Hop,
    IntroductionPoint,
    RelayRoute,
    RendezvousPoint,
    Swarm,
    TunnelExitSocket,
)

if TYPE_CHECKING:
    from ...types import IPv8

class HiddenTunnelSettings(TunnelSettings):
    """
    Settings for the hidden tunnel community.
    """

    ipv8: IPv8 | None = None

    e2e_callbacks: dict[bytes, Callable[[Address], None] | None] | None = None

class HiddenTunnelCommunity(TunnelCommunity):
    """
    Extension of TunnelCommunity logic to link up circuits and create closed-loop e2e circuits.
    """

    settings_class = HiddenTunnelSettings

    def __init__(self, settings: HiddenTunnelSettings) -> None:
        """
        Create a new e2e-capable tunnel community.
        """
        self.ipv8 = settings.ipv8
        self.e2e_callbacks: dict[bytes, Callable[[Address], None] | None] = ({} if settings.e2e_callbacks is None
                                                                             else settings.e2e_callbacks)

        self.swarms: dict[bytes, Swarm] = {}
        self.pex: dict[bytes, PexCommunity] = {}

        super().__init__(settings)

        self.intro_point_for: dict[bytes, tuple[TunnelExitSocket,
                                                bytes]] = {}  # {seeder_pk: (TunnelExitSocket, info_hash)}
        self.rendezvous_point_for: dict[bytes, TunnelExitSocket] = {}  # {cookie: TunnelExitSocket}

        # Messages that can arrive from the socket
        # The circuit id is optional, so we can safely cast these handlers.
        self.add_message_handler(CreateE2EPayload, cast(Callable[[Tuple[str, int], bytes], None],
                                                        self.on_create_e2e))
        self.add_message_handler(PeersRequestPayload, cast(Callable[[Tuple[str, int], bytes], None],
                                                           self.on_peers_request))
        self.add_message_handler(PeersResponsePayload, cast(Callable[[Tuple[str, int], bytes], None],
                                                            self.on_peers_response))

        # Messages that can arrive from a circuit (i.e., they are wrapped in a cell)
        self.add_cell_handler(EstablishIntroPayload, self.on_establish_intro)
        self.add_cell_handler(IntroEstablishedPayload, self.on_intro_established)
        self.add_cell_handler(EstablishRendezvousPayload, self.on_establish_rendezvous)
        self.add_cell_handler(RendezvousEstablishedPayload, self.on_rendezvous_established)
        self.add_cell_handler(CreateE2EPayload, self.on_create_e2e)
        self.add_cell_handler(CreatedE2EPayload, self.on_created_e2e)
        self.add_cell_handler(LinkE2EPayload, self.on_link_e2e)
        self.add_cell_handler(LinkedE2EPayload, self.on_linked_e2e)
        self.add_cell_handler(PeersRequestPayload, self.on_peers_request)
        self.add_cell_handler(PeersResponsePayload, self.on_peers_response)

        self.register_task("do_peer_discovery", self.do_peer_discovery, interval=10)

    def join_swarm(self, info_hash: bytes, hops: int, callback: Callable[[Address], None] | None = None,
                   seeding: bool = True) -> None:
        """
        Join a hidden swarm. This should be called by both the downloader and the seeder. Calling this method while
        already part of the swarm will cause the community to drop all pre-existing connections.
        Note that the seeder should also create introduction points by calling create_introduction_point().

        :param info_hash: the swarm identifier
        :param hops: the amount of hops for our introduction/rendezvous circuits
        :param callback: the callback function to call when we have established an e2e circuit
        :param seeding: whether the swarm should be joined as seeder
        """
        if info_hash in self.swarms:
            self.logger.warning('Already part of hidden swarm %s, leaving existing', binascii.hexlify(info_hash))
            self.leave_swarm(info_hash)

        self.swarms[info_hash] = Swarm(info_hash, hops, self.send_peers_request,
                                       cast(LibNaCLSK, self.crypto.generate_key("curve25519")) if seeding else None)
        self.e2e_callbacks[info_hash] = callback

    def leave_swarm(self, info_hash: bytes) -> None:
        """
        Leave a hidden swarm. Can be called by both the downloader and the seeder.

        :param info_hash: the swarm identifier
        """
        # Remove all introduction points and e2e circuits for this swarm
        for circuit in self.circuits.values():
            if circuit.info_hash == info_hash and circuit.ctype in [CIRCUIT_TYPE_IP_SEEDER,
                                                                    CIRCUIT_TYPE_RP_SEEDER,
                                                                    CIRCUIT_TYPE_RP_DOWNLOADER]:
                _ = self.remove_circuit(circuit.circuit_id, 'leaving hidden swarm', destroy=DESTROY_REASON_UNNEEDED)
        # Remove swarm and callback
        swarm = self.swarms.pop(info_hash, None)
        self.e2e_callbacks.pop(info_hash, None)
        # If there are no other swarms with the same hop count, remove the data circuits
        if swarm and not [s for s in self.swarms.values() if s != swarm and s.hops == swarm.hops]:
            for circuit in self.find_circuits(hops=swarm.hops, state=None):
                _ = self.remove_circuit(circuit.circuit_id, 'not needed', destroy=DESTROY_REASON_UNNEEDED)

    async def estimate_swarm_size(self, info_hash: bytes, hops: int = 1, max_requests: int = 10) -> int:
        """
        Estimate the number of unique seeders that are part of a hidden swarm.

        :param info_hash: the swarm identifier
        :param hops: the amount of hops to use for contacting introduction points
        :param max_requests: the number of introduction points we should send a get-peers message to
        :return: number of unique seeders
        """
        swarm = Swarm(info_hash, hops, self.send_peers_request)

        # None represents a DHT request
        all_: list[IntroductionPoint | None] = [None]
        tried: Set[IntroductionPoint | None] = set()

        while len(tried) < max_requests:
            not_tried = set(all_) - tried
            if not not_tried:
                break

            # Issue as many requests as possible in parallel
            ips = random.sample(list(not_tried), min(len(not_tried), max_requests - len(tried)))
            responses = await gather(*[swarm.lookup(ip) for ip in ips], return_exceptions=True)

            # Collect responses
            all_ += [result for result in responses if not isinstance(result, (CancelledError, Exception))]
            tried |= set(ips)

        return len({ip.seeder_pk for ip in all_ if ip is not None and ip.source == PEER_SOURCE_PEX})

    def select_circuit_for_infohash(self, info_hash: bytes) -> Circuit | None:
        """
        Get a circuit that connectes to the swarm of the given SHA-1 hash.
        """
        swarm = self.swarms.get(info_hash)
        if not swarm or not swarm.hops:
            self.logger.info('Can\'t get hop count, download cancelled?')
            return None

        return self.select_circuit(None, swarm.hops)

    def create_circuit_for_infohash(self, info_hash: bytes, ctype: str, exit_flags: Set[int] | None = None,
                                    required_exit: Peer | None = None) -> Circuit | None:
        """
        Create a circuit that connects to the swarm given by the SHA-1 (info) hash.
        """
        swarm = self.swarms.get(info_hash)
        if not swarm or not swarm.hops:
            self.logger.info('Can\'t get hop count, download cancelled?')
            return None

        # Introduction point circuits need an additional hop, or we will talk directly to the the introduction point.
        # Also, rendezvous circuits need an additional hop, since the seeder chooses the rendezvous_point.
        hops = swarm.hops
        if ctype in [CIRCUIT_TYPE_IP_SEEDER, CIRCUIT_TYPE_RP_DOWNLOADER]:
            hops += 1

        return self.create_circuit(hops, ctype, exit_flags, required_exit, info_hash)

    def ip_to_circuit_id(self, ip_str: str) -> int:
        """
        Convert an IP to a (special) circuit id.
        """
        return struct.unpack("!I", socket.inet_aton(ip_str))[0]

    def circuit_id_to_ip(self, circuit_id: int) -> str:
        """
        Convert an IP-embedded circuit id back to the original IP.
        """
        return socket.inet_ntoa(struct.pack("!I", circuit_id))

    async def do_peer_discovery(self) -> None:
        """
        Find peers in the swarms that we are a part of.
        """
        now = time.time()
        for info_hash, swarm in list(self.swarms.items()):
            if not swarm.seeding and swarm.last_lookup + self.settings.swarm_lookup_interval <= now \
               and swarm.get_num_connections() < self.settings.swarm_connection_limit:
                try:
                    ips = await swarm.lookup()
                except (IndexError, RuntimeError):
                    self.logger.info('Failed to do peer discovery for swarm %s', binascii.hexlify(info_hash))
                    continue

                if ips is None:
                    self.logger.info('Skipping peer discovery for swarm %s', binascii.hexlify(info_hash))
                    continue
                self.logger.info('Found %d/%d peer(s) for swarm %s',
                                 len([ip for ip in ips if ip.source == PEER_SOURCE_DHT]),
                                 len([ip for ip in ips if ip.source == PEER_SOURCE_PEX]),
                                 binascii.hexlify(info_hash))
                for ip in set(ips):
                    swarm.add_intro_point(ip)
                for ip in swarm.intro_points:
                    if not swarm.has_connection(ip.seeder_pk):
                        self.create_e2e(info_hash, ip)

    def do_circuits(self) -> None:
        """
        Beyond normal circuit creation, make sure we have circuits for introduction points.
        """
        super().do_circuits()

        # Make sure we have at least 1 data circuit for every hop count. This circuit will be used for
        # communication with introduction points.
        for hop_count in {swarm.hops for swarm in self.swarms.values()}:
            if not self.find_circuits(state=None, hops=hop_count):
                self.create_circuit(hop_count)

    def do_ping(self, exclude: list[int] | None = None) -> None:
        """
        Ping all circuits, except pending e2e circuits.
        """
        exclude = [] if exclude is None else exclude
        exclude += [c.circuit_id for c in self.circuits.values()
                    if (c.ctype == CIRCUIT_TYPE_RP_SEEDER) or (c.ctype == CIRCUIT_TYPE_RP_DOWNLOADER and not c.e2e)]
        super().do_ping(exclude=exclude)

    def remove_circuit(self, circuit_id: int, additional_info: str = '', remove_now: bool = False,
                       destroy: bool | int = False) -> Coroutine[Any, Any, None]:
        """
        Remove the given circuit and update any swarms it may be part of.
        """
        circuit = self.circuits.get(circuit_id, None)
        if circuit and circuit.ctype == CIRCUIT_TYPE_RP_DOWNLOADER:
            swarm = self.swarms.get(cast(bytes, circuit.info_hash))
            if swarm:
                swarm.remove_connection(circuit)
        return super().remove_circuit(circuit_id, additional_info, remove_now, destroy)

    def remove_exit_socket(self, circuit_id: int, additional_info: str = '', remove_now: bool = False,
                           destroy: bool = False) -> Coroutine[Any, Any, TunnelExitSocket | None]:
        """
        Remove the given exit circuit, remove associated rendezvous points and update any PEX communities
        it may be part of.
        """
        for seeder_pk, (intro_circuit, info_hash) in list(self.intro_point_for.items()):
            if intro_circuit.circuit_id == circuit_id:
                self.intro_point_for.pop(seeder_pk)

                # Stop announcing in PEX community
                pex = self.pex.get(info_hash)
                if pex:
                    pex.stop_announce(seeder_pk)

                    # Unload PEX community
                    if pex.done:
                        self.pex.pop(info_hash, None)
                        if self.ipv8 is not None:
                            self.ipv8.overlays.remove(pex)
                            self.ipv8.strategies = [t for t in self.ipv8.strategies if t[0].overlay != pex]
                        self.register_anonymous_task('unload_pex', pex.unload)

        for cookie, rendezvous_circuit in list(self.rendezvous_point_for.items()):
            if rendezvous_circuit.circuit_id == circuit_id:
                self.rendezvous_point_for.pop(cookie)

        return super().remove_exit_socket(circuit_id, additional_info, remove_now, destroy)

    def get_max_time(self, circuit_id: int) -> float:
        """
        Get the maximum time that a given circuit is allowed to exist.
        """
        if circuit_id in self.circuits and self.circuits[circuit_id].ctype == CIRCUIT_TYPE_IP_SEEDER:
            return self.settings.max_time_ip
        if circuit_id in self.exit_sockets and circuit_id in [c.circuit_id for c, _ in self.intro_point_for.values()]:
            return self.settings.max_time_ip
        return super().get_max_time(circuit_id)

    def tunnel_data(self, circuit: Circuit | TunnelExitSocket, destination: Address,
                    payload: VariablePayloadWID) -> None:
        """
        Send any serializable payload to the next hop in the circuit.
        """
        packet = self.ezr_pack(payload.msg_id, payload, sig=False)
        pre = ('0.0.0.0', 0)
        post = ('0.0.0.0', 0)
        if isinstance(circuit, TunnelExitSocket):
            post = destination
        else:
            pre = destination
        self.send_data(cast(Peer, circuit.peer), circuit.circuit_id, pre, post, packet)

    def select_circuit(self, destination: Address | None, hops: int) -> Circuit | None:
        """
        Make sure that we select the right circuit when dealing with an e2e connection.
        """
        if destination is not None and destination[1] == CIRCUIT_ID_PORT:
            circuit_id = self.ip_to_circuit_id(destination[0])
            circuit = self.circuits.get(circuit_id, None)

            if circuit and circuit.state == CIRCUIT_STATE_READY and \
               circuit.ctype == CIRCUIT_TYPE_RP_DOWNLOADER:
                return circuit

        circuits = self.find_circuits(hops=hops)
        return random.choice(circuits) if circuits else None

    def send_peers_request(self, info_hash: bytes, target: IntroductionPoint | None,
                           hops: int) -> Future[list[IntroductionPoint]]:
        """
        Attempt to find peers for a given SHA-1 (info) hash.
        """
        circuit = self.select_circuit(None, hops)
        if not circuit:
            self.logger.info("No circuit for peers-request")
            return fail(RuntimeError("No circuit for peers-request"))

        # Send a peers-request message over this circuit
        cache = PeersRequestCache(self, circuit, info_hash, target)
        self.request_cache.add(cache)
        payload = PeersRequestPayload(circuit.circuit_id, cache.number, info_hash)

        # Ask an introduction point if available (in which case we'll use PEX), otherwise let
        # the exit node do a DHT request.
        if target is not None and target.peer.public_key != circuit.hops[-1].public_key:
            self.tunnel_data(circuit, target.peer.address, payload)
            self.logger.info("Sending peers request (intro point %s)", target.peer)
        else:
            self.send_cell(cast(Peer, circuit.peer), payload)
            self.logger.info("Sending peers request as cell")
        return cache.future

    @unpack_cell(PeersRequestPayload)
    async def on_peers_request(self, source_address: Address, payload: PeersRequestPayload,
                               circuit_id: int | None = None) -> None:
        """
        Callback for when someone wants us to find peers for their circuit.
        """
        info_hash = payload.info_hash
        self.logger.info("Doing hidden seeders lookup for info_hash %s", binascii.hexlify(info_hash))
        if info_hash in self.pex:
            # Get peers from PEX community
            intro_points = self.pex[info_hash].get_intro_points()
            self.send_peers_response(source_address, payload, intro_points, circuit_id)
        elif circuit_id in self.exit_sockets:
            # Get peers from DHT community
            results = await self.dht_lookup(info_hash)
            if results:
                _, intro_points = results
                self.send_peers_response(source_address, payload, intro_points, circuit_id)
        elif circuit_id is not None:
            self.logger.warning("Received a peers-request over circuit %d, but unable to do a DHT lookup", circuit_id)
        else:
            self.logger.warning("Received a peers-request over the socket, but unable to do a PEX lookup")

    def send_peers_response(self, target_addr: Address, request: PeersRequestPayload,
                            intro_points: list[IntroductionPoint], circuit_id: int | None) -> None:
        """
        Send a response with the peers that we found through the DHT.
        """
        peers = [IntroductionInfo(ip.peer.address, ip.peer.public_key.key_to_bin(), ip.seeder_pk, ip.source)
                 for ip in random.sample(intro_points, min(len(intro_points), 7))]
        payload = PeersResponsePayload(request.circuit_id, request.identifier, request.info_hash, peers)

        if circuit_id is not None:
            # Send back to origin
            self.send_cell(target_addr, payload)
        else:
            # Send back to exit node
            packet = self.ezr_pack(payload.msg_id, payload, sig=False)
            self.send_packet(target_addr, packet)

    @unpack_cell(PeersResponsePayload)
    def on_peers_response(self, source_address: Address, payload: PeersResponsePayload, circuit_id: int | None) -> None:
        """
        Callback for when someone performed a DHT lookup for us.
        """
        if not self.request_cache.has(PeersRequestCache, payload.identifier):
            self.logger.warning('Got a peers-response with an unknown identifier')
            return
        cache = self.request_cache.pop(PeersRequestCache, payload.identifier)

        self.logger.info("Received peers-response containing %d peers", len(payload.peers))
        ips = [IntroductionPoint(Peer(peer.key, address=peer.address), peer.seeder_pk, peer.source)
               for peer in payload.peers if peer.address != ('0.0.0.0', 0)]
        cache.future.set_result(ips)

    def create_e2e(self, info_hash: bytes, intro_point: IntroductionPoint) -> None:
        """
        Create an e2e bridge for the given SHA-1 (info) hash over the given introduction point.
        """
        circuit = self.select_circuit_for_infohash(info_hash)
        if not circuit:
            self.logger.error("No circuit for contacting the introduction point")
            return

        hop = Hop(Peer(LibNaCLPK(intro_point.seeder_pk[10:])))
        hop.dh_secret, hop.dh_first_part = self.crypto.generate_diffie_secret()
        self.logger.info('Creating e2e circuit for introduction point %s', intro_point.peer)
        cache = E2ERequestCache(self, info_hash, hop, intro_point)
        self.request_cache.add(cache)
        self.tunnel_data(circuit, intro_point.peer.address,
                         CreateE2EPayload(cache.number, info_hash, hop.public_key_bin, hop.dh_first_part))

    @unpack_cell(CreateE2EPayload)
    async def on_create_e2e(self, source_address: Address, payload: CreateE2EPayload,
                            circuit_id: int | None = None) -> None:
        """
        Callback for when we receive a creation message for an e2e circuit.
        """
        # If we have received this message over a socket, we need to forward it
        if circuit_id is None:
            if payload.node_public_key in self.intro_point_for:
                self.logger.info('On create-e2e: forwarding message because received over socket')
                relay_circuit, _ = self.intro_point_for[payload.node_public_key]
                self.tunnel_data(relay_circuit, source_address, payload)
            else:
                self.logger.info('On create-e2e: dropping message for unknown seeder key %s',
                                 binascii.hexlify(payload.node_public_key))
        else:
            self.logger.info('On create-e2e: creating rendezvous point')
            swarm = self.swarms.get(payload.info_hash)
            if swarm and swarm.seeding:
                rp = await self.create_rendezvous_point(payload.info_hash)
                if rp and await rp.ready:
                    self.create_created_e2e(rp, source_address, payload, circuit_id)

    def create_created_e2e(self, rp: RendezvousPoint, source_address: Address,
                           payload: CreateE2EPayload, circuit_id: int | None) -> None:
        """
        Callback for when we receive "create" or "created" payloads.
        """
        key = self.swarms[payload.info_hash].seeder_sk
        shared_secret, y, auth = self.crypto.generate_diffie_shared_secret(payload.key, key)
        rp.circuit.hs_session_keys = self.crypto.generate_session_keys(shared_secret)

        rp_info = RendezvousInfo(rp.address, rp.circuit.hops[-1].public_key.key_to_bin(), rp.cookie)
        rp_info_bin = self.serializer.pack('payload', rp_info)
        rp_info_enc = self.crypto.encrypt_str(rp_info_bin, rp.circuit.hs_session_keys, FORWARD)

        circuit = self.circuits[cast(int, circuit_id)]
        self.tunnel_data(circuit, source_address, CreatedE2EPayload(payload.identifier, y, auth, rp_info_enc))

    @unpack_cell(CreatedE2EPayload)
    async def on_created_e2e(self, source_address: Address, payload: CreatedE2EPayload, circuit_id: int | None) -> None:
        """
        Callback for when peers signal that they have created an e2e circuit.
        """
        if not self.request_cache.has(E2ERequestCache, payload.identifier):
            self.logger.warning("Invalid created-e2e identifier")
            return

        cache = self.request_cache.pop(E2ERequestCache, payload.identifier)
        shared_secret = self.crypto.verify_and_generate_shared_secret(cast(LibNaCLSK, cache.hop.dh_secret),
                                                                      payload.key,
                                                                      payload.auth,
                                                                      cache.hop.public_key.key.pk)
        session_keys = self.crypto.generate_session_keys(shared_secret)

        rp_info_enc = payload.rp_info_enc
        rp_info_bin = self.crypto.decrypt_str(rp_info_enc, session_keys, FORWARD)
        rp_info, _ = cast(Tuple[RendezvousInfo, int], self.serializer.unpack(RendezvousInfo, rp_info_bin))

        required_exit = Peer(rp_info.key, rp_info.address)
        circuit = self.create_circuit_for_infohash(cache.info_hash, CIRCUIT_TYPE_RP_DOWNLOADER,
                                                   required_exit=required_exit)
        if circuit:
            self.swarms[cache.info_hash].add_connection(circuit, cache.intro_point)
            if await circuit.ready:
                link_cache = LinkRequestCache(self, circuit, cache.info_hash, session_keys)
                self.request_cache.add(link_cache)
                self.send_cell(cast(Peer, circuit.peer),
                               LinkE2EPayload(circuit.circuit_id, link_cache.number, rp_info.cookie))

    @unpack_cell(LinkE2EPayload)
    def on_link_e2e(self, source_address: Address, payload: LinkE2EPayload, circuit_id: int | None) -> None:
        """
        Callback for when an e2e circuit attempts to link up sender and receiver.
        """
        if payload.cookie not in self.rendezvous_point_for:
            self.logger.warning("Not a rendezvous point for this cookie")
            return

        if circuit_id is None:
            self.logger.warning("Attempted link without circuit id")
            return

        if self.exit_sockets[circuit_id].enabled:
            self.logger.warning("Exit socket for circuit is enabled, cannot link")
            return

        relay_circuit = self.rendezvous_point_for[payload.cookie]
        if self.exit_sockets[relay_circuit.circuit_id].enabled:
            self.logger.warning("Exit socket for relay_circuit is enabled, cannot link")
            return

        circuit = self.exit_sockets[circuit_id]

        _ = self.remove_exit_socket(circuit.circuit_id, 'linking circuit')
        _ = self.remove_exit_socket(relay_circuit.circuit_id, 'linking circuit')

        self.relay_from_to[circuit.circuit_id] = RelayRoute(relay_circuit.circuit_id, relay_circuit.peer, FORWARD, True)
        self.relay_from_to[relay_circuit.circuit_id] = RelayRoute(circuit.circuit_id, circuit.peer, FORWARD, True)

        self.send_cell(source_address, LinkedE2EPayload(circuit.circuit_id, payload.identifier))

    @unpack_cell(LinkedE2EPayload)
    def on_linked_e2e(self, source_address: Address, payload: LinkedE2EPayload, circuit_id: int | None) -> None:
        """
        Callback for when a sender and receiver circuit have been linked up.
        """
        if not self.request_cache.has(LinkRequestCache, payload.identifier):
            self.logger.warning("Invalid linked-e2e identifier")
            return

        cache = self.request_cache.pop(LinkRequestCache, payload.identifier)
        circuit = cache.circuit
        circuit.e2e = True
        circuit.hs_session_keys = cache.hs_session_keys
        callback = self.e2e_callbacks.get(cache.info_hash, None)
        if callback:
            result = callback((self.circuit_id_to_ip(circuit.circuit_id), CIRCUIT_ID_PORT))
            if iscoroutine(result):
                self.register_anonymous_task('e2e_callback', result)
        else:
            self.logger.error('On linked e2e: could not find download for %s!', cache.info_hash)

    async def create_introduction_point(self, info_hash: bytes, required_ip: Peer | None = None) -> None:
        """
        Create an introduction point for the given SHA-1 (info) hash.
        """
        self.logger.info("Creating introduction point")

        if info_hash not in self.swarms:
            self.logger.warning('Cannot create introduction point for unknown swarm')
            return
        if not self.swarms[info_hash].seeding:
            self.logger.warning('Cannot create introduction point for swarm that is not seeding')
            return

        circuit = self.create_circuit_for_infohash(info_hash, CIRCUIT_TYPE_IP_SEEDER, required_exit=required_ip)

        if circuit and await circuit.ready:
            # We got a circuit, now let's create an introduction point
            seed_pk = cast(LibNaCLSK, self.swarms[info_hash].seeder_sk).pub().key_to_bin()
            circuit_id = circuit.circuit_id
            cache = IPRequestCache(self, circuit)
            self.request_cache.add(cache)
            self.send_cell(cast(Peer, circuit.peer), EstablishIntroPayload(circuit_id, cache.number, info_hash,
                                                                           seed_pk))
            self.logger.info("Established introduction tunnel %s", circuit_id)

    @unpack_cell(EstablishIntroPayload)
    def on_establish_intro(self, source_address: Address, payload: EstablishIntroPayload,
                           circuit_id: int | None) -> None:
        """
        Callback for when we are asked to be an introduction point.
        """
        if payload.public_key in self.intro_point_for:
            self.logger.warning('Already have an introduction point for %s', binascii.hexlify(payload.public_key))
            return

        if circuit_id is None:
            self.logger.warning('Trying to establish an introduction point without a circuit id')
            return

        self.logger.info('Established introduction point for %s', binascii.hexlify(payload.public_key))

        circuit = self.exit_sockets[circuit_id]
        self.intro_point_for[payload.public_key] = circuit, payload.info_hash

        if self.ipv8 is None:
            self.logger.error('No IPv8 service object available, cannot start PEXCommunity')
        elif payload.info_hash not in self.pex:
            community = PexCommunity(PexSettings(my_peer=self.my_peer, endpoint=self.endpoint, network=Network(),
                                                 info_hash=payload.info_hash))
            community.bootstrappers = [DispersyBootstrapper(**DISPERSY_BOOTSTRAPPER['init'])]
            # Since IPv8 takes a step every .5s until we have 10 peers, the PexCommunity will generate
            # a lot of traffic in case there are <10 peers in existence. Therefore, we slow the walk down to a 5s/step.
            self.ipv8.add_strategy(community, RandomWalk(community, target_interval=5), 10)
            self.ipv8.add_strategy(community, RandomChurn(community), -1)
            self.pex[payload.info_hash] = community

        # PEX announce
        if payload.info_hash in self.pex:
            self.pex[payload.info_hash].start_announce(payload.public_key)

        # DHT announce
        self.dht_announce(payload.info_hash, IntroductionPoint(Peer(self.my_peer.key, self.my_estimated_wan),
                                                               payload.public_key))

        self.send_cell(source_address, IntroEstablishedPayload(circuit.circuit_id, payload.identifier))

    @unpack_cell(IntroEstablishedPayload)
    def on_intro_established(self, source_address: Address, payload: IntroEstablishedPayload,
                             circuit_id: int | None) -> None:
        """
        Callback for when a peer signals that an introduction point has been created for us.
        """
        if not self.request_cache.has(IPRequestCache, payload.identifier):
            self.logger.warning("Invalid intro-established request identifier")
            return

        self.request_cache.pop(IPRequestCache, payload.identifier)
        self.logger.info("Got intro-established from %s", source_address)

    async def create_rendezvous_point(self, info_hash: bytes) -> RendezvousPoint | None:
        """
        Create a new rendezvous point for the given SHA-1 (info) hash.
        """
        # Create a new circuit to be used for transferring data
        circuit = self.create_circuit_for_infohash(info_hash, CIRCUIT_TYPE_RP_SEEDER)

        if circuit and await circuit.ready:
            # We got a circuit, now let's create a rendezvous point
            rp = RendezvousPoint(circuit, os.urandom(20))
            cache = RPRequestCache(self, rp)
            self.request_cache.add(cache)
            self.send_cell(cast(Peer, circuit.peer), EstablishRendezvousPayload(circuit.circuit_id, cache.number,
                                                                                rp.cookie))
            return rp
        return None

    @unpack_cell(EstablishRendezvousPayload)
    def on_establish_rendezvous(self, source_address: Address, payload: EstablishRendezvousPayload,
                                circuit_id: int | None) -> None:
        """
        Callback for when we are requested to be a rendezvous point.
        """
        if circuit_id is None:
            self.logger.warning('Trying to establish a rendezvous point without a circuit id')
            return

        circuit = self.exit_sockets[circuit_id]
        self.rendezvous_point_for[payload.cookie] = circuit

        self.send_cell(source_address,
                       RendezvousEstablishedPayload(circuit.circuit_id, payload.identifier, self.my_estimated_wan))

    @unpack_cell(RendezvousEstablishedPayload)
    def on_rendezvous_established(self, source_address: Address, payload: RendezvousEstablishedPayload,
                                  circuit_id: int | None) -> None:
        """
        Callback for when a peer signals that they act as a rendezvous point for us.
        """
        if not self.request_cache.has(RPRequestCache, payload.identifier):
            self.logger.warning("Invalid rendezvous-established request identifier")
            return

        rp = self.request_cache.pop(RPRequestCache, payload.identifier).rp
        rp.address = payload.rendezvous_point_addr
        rp.ready.set_result(rp)

    async def dht_lookup(self, info_hash: bytes) -> tuple[bytes, list[IntroductionPoint]] | None:
        """
        Attempt to find introduction points for the given SHA-1 (info) hash.
        """
        if self.dht_provider:
            return await self.dht_provider.lookup(info_hash)

        self.logger.error("Need a DHT provider to lookup on the DHT")
        return None

    @task
    async def dht_announce(self, info_hash: bytes, intro_point: IntroductionPoint) -> None:
        """
        Announce ourselves as a candidate for the given SHA-1 (info) hash.
        """
        if self.dht_provider:
            return await self.dht_provider.announce(info_hash, intro_point)

        self.logger.error("Need a DHT provider to announce to the DHT")
        return None


from hypothesis import given, strategies as st
from hypothesis.extra.pandas import column, data_frames

import pandas as pd
import numpy as np

from housinglib.cleansing import fill_na_real, drop_precision

@given(data_frames(columns=[
    column(name='first',
           elements=st.floats(allow_nan=True)),
    column(name='second',
           elements=st.floats(allow_nan=True))
])
)
def test_fill_na_real_hypo(frame):
    result = fill_na_real(frame)
    assert isinstance(result, pd.DataFrame)
    assert result.notna().all(axis=None)

@given(data_frames(columns=[
    column(name='first',
           dtype='int64'),
    column(name='second',
           dtype='float64')
])
)
def test_drop_precision_hypo(frame):
    result = drop_precision(frame, 'float64', 'int64')
    assert np.allclose(result.values, frame.values, equal_nan=True)


from argparse import ArgumentParser
import os
# import sys

parser = ArgumentParser(description = "Split a text file into parts and label \
                        their content")
parser.add_argument("directory", help = "Directory containing the files to be split")
parser.add_argument("label", help = "Label to be added at the beginning of each part")
parser.add_argument("trunc_size", type = int, help = "Size (characters) of the \
                    resulting parts")

args = parser.parse_args()

os.chdir(args.directory)

part_len = args.trunc_size # Longueur souhaitée de chaque partie
eos = "." # Fin de phrase

for filename in os.listdir():
    # print("Travail sur ", filename)

    out_len = 0 # Nombre d'octets écrits
    part_idx = 0 # Indice de la partie de fichier obtenue

    with open(filename, 'r') as input:
        # print(" Ouvert", input.name)
        out_name_base = input.name # output_0, output_1, ...
        output = open(out_name_base + "_" + str(part_idx), 'w') # Ouvre une nouvelle partie
        output.write(args.label + "\n") # Labellise la partie

        input.readline() # Ligne du titre
        text = input.read()

        for sent in text.split(sep = eos):
            output.write(sent + eos)
            out_len += len(sent + eos)

            if out_len > part_len:
                output.close() # Partie fermée
                # print("     Partie", part_idx, "terminée")

                part_idx += 1
                out_len = 0
                output = open(out_name_base + "_" + str(part_idx), 'w') # Ouvre une nouvelle partie
                output.write(args.label + "\n")

    if output:
        # print(" Fermé : ", output.name)
        output.close() # Ferme la dernière partie

    if out_len < part_len/2: # Si le dernier fichier est "trop court"
        # print("Fusion pour ", input.name, ";", output.name, "sera copié puis supprimé")
        with open(out_name_base + "_" + str(part_idx - 1), 'a') as target:
            # print(" Ouvert : " + out_name_base + "_" + str(part_idx - 1))
            with open(out_name_base + "_" + str(part_idx), 'r') as lastPart:
                lastPart.readline() # Label précédemment ajouté, à ne pas copier
                target.write(lastPart.read()) # Concatène les deux dernières parties

        os.remove(out_name_base + "_" + str(part_idx))
        # print("     Supprimé : " + out_name_base + "_" + str(part_idx))
    # print(output.name, out_len)
    print("Split and labeled : ", input.name)


# for data manipulation
import pandas as pd

# importing for scientific and numeric manipulations
import numpy as np

# importing components for visualization
from matplotlib.patches import Rectangle

def calculate_precursor_within_scan_area(
    library: pd.DataFrame,
    mz: tuple,
    im: tuple,
) -> dict:
    """This function calculates the number of precursors within the scan area
    in percent.

    Parameters:
    library (pd.DataFrame): a pre-filtered data frame with unified column names
        containing all required precursor information.
    mz (tuple): lower and upper value of the m/z range.
    im (tuple): lower and upper value of the ion mobility range.

    Returns:
    dict: this dictionary has only one item. The key is the value description,
        and the value is the ratio of precursors [%] within the set scan area.
    """
    filtered_selection = library['mz'] > mz[0]
    filtered_selection &= library['mz'] < mz[1]
    filtered_selection &= library['IM'] > im[0]
    filtered_selection &= library['IM'] < im[1]

    prec_scan_area = (len(library[filtered_selection])/len(library['mz']))*100

    return {'precursors within m/z-range [%]': round(prec_scan_area, 2)}

def calculate_percentage_multiple_charged_precursors(
    library_subset: pd.DataFrame,
) -> dict:
    """This function calculates the ratio of precursors with different charge
        states in percent.

    Parameters:
    library_subset (pd.DataFrame): a pre-filtered data frame with unified
        column names containing all required precursor information.

    Returns:
    dict: The key is the value description, and the value is the ratio of
        precursors [%] with a specific charge state.
    """
    dict_charge_prec = dict()
    for i in range(1, 5):
        dict_charge_prec[str(i)] = round(
            (
                len(
                    library_subset[library_subset['Charge'] == i]
                ) / len(library_subset)
            ) * 100, 2
        )
    return dict_charge_prec

def coverage(
    df: pd.DataFrame,
    library: pd.DataFrame,
) -> dict:
    """This function calculates different evaluation values to estimate the
        optimization potential of the tested dia-PASEF method based on the
        theoretically covered (doubly/ triply charged) precursors.

    Parameters:
    df (pd.DataFrame): data frame that contains the scan type (PASEF), scan
        number, and the corresponding dia-PASEF window coordinates for each
        window per scan.
    library_subset (pd.DataFrame): a pre-filtered data frame with unified
        column names containing all required precursor information.

    Returns:
    dict: This dictionary describes multiple values to evaluate diaPASEF
        methods in respect to the proteomics library.
    """
    _, borders, small_window, big_window, mean_window = boxes(df)

    coverage_list = list()
    for i in borders:
        # subset for precursors within boundaries and append to dataset:
        # mz_min:i[0], mz_max:i[2], im_min:i[1], im_max:i[3]
        filtered_selection = library['mz'] >= i[0]
        filtered_selection &= library['mz'] <= i[2]
        filtered_selection &= library['IM'] >= i[1]
        filtered_selection &= library['IM'] <= i[3]
        coverage_list.append(library[filtered_selection])
    coverage_unique_prec = pd.concat(coverage_list, ignore_index=True)
    coverage_removed_prec_duplicates = coverage_unique_prec.drop_duplicates(['Peptide', 'Charge'])

    dict_prec_coverage = {
        "unique proteins in the library": len(library.drop_duplicates(['Proteins'])),
        "unique precursors in the library": len(library.drop_duplicates(['Peptide', 'Charge'])),
        "smallest diaPASEF window": round(small_window, 2),
        "biggest diaPASEF window": round(big_window, 2),
        "average diaPASEF window size": round(mean_window, 2),
        "No. of covered proteins": len(coverage_removed_prec_duplicates.drop_duplicates(['Proteins'])),
        "No. of covered precursors": len(coverage_removed_prec_duplicates),
        "No. of covered, doubly charged precursors": len(coverage_removed_prec_duplicates[coverage_removed_prec_duplicates['Charge'] == 2]),
        "No. of covered, triply charged precursors": len(coverage_removed_prec_duplicates[coverage_removed_prec_duplicates['Charge'] == 3]),
        "No. of covered, quadruply charged precursors": len(coverage_removed_prec_duplicates[coverage_removed_prec_duplicates['Charge'] == 4])
    }
    dict_prec_coverage["all proteins covered"] = format(np.round(dict_prec_coverage["No. of covered proteins"]/len(library.drop_duplicates(['Proteins']))*100, 1)) + "%"
    dict_prec_coverage["all precursors covered"] = format(np.round(dict_prec_coverage["No. of covered precursors"]/len(library)*100, 1)) + "%"
    dict_prec_coverage["all doubly charged precursors covered"] = format(np.round(dict_prec_coverage["No. of covered, doubly charged precursors"] / len(library[library['Charge'] == 2]) * 100, 1)) + "%"
    dict_prec_coverage["all triply charged precursors covered"] = format(np.round(dict_prec_coverage["No. of covered, triply charged precursors"] / len(library[library['Charge'] == 3]) * 100, 1)) + "%"
    dict_prec_coverage["all quadruply charged precursors covered"] = format(np.round(dict_prec_coverage["No. of covered, quadruply charged precursors"] / len(library[library['Charge'] == 4]) * 100, 1)) + "%"
    return dict_prec_coverage

def boxes(
    df: pd.DataFrame,
) -> int:
    """This function calculates dia-PASEF window specific information regarding
        the window size and creates two lists. One is for plotting the dia-PASEF
        window scheme on top of a kernel density estimation plot. The second is
        to calculate the number of proteins and precursors covered by the
        dia-PASEF window scheme.

    Parameters:
    df (pd.DataFrame): data frame that contains the scan type (PASEF), scan
        number, and the corresponding dia-PASEF window coordinates for each
        window per scan.

    Returns:
    rect (list): This list contains all information to print the dia-PASEF
        windows on top of a kernel density estimation plot.
    border (list(tuples)): The list "borders" contains a list of tuples where
        each tuple represents the edge coordinates for each dia-PASEF window.
    small_window (int): size of the smallest dia-PASEF window [Da].
    big_window (int): size of the biggest dia-PASEF window [Da].
    mean_window (int): average dia-PASEF window size [Da].
    """
    rect = list()
    borders = list()
    window_width = list()
    count = dict()

    for item in df["Cycle Id"].values:
        count[item] = count.get(item, 0)+1

    for i in count.keys():
        df_temp = df[df["Cycle Id"] == i]
        df_temp["xw"] = df_temp["End Mass"] - df_temp["Start Mass"]
        df_temp["yw"] = df_temp["End IM"] - df_temp["Start IM"]

        df_temp["borders"] = df_temp.apply(
            lambda x:
                (x["Start Mass"], x["Start IM"], x["End Mass"], x["End IM"]),
                axis=1
            )
        df_temp["rect"] = df_temp.apply(
            lambda x: Rectangle(
                (x["Start Mass"], x["Start IM"]), x["xw"], x["yw"]),
                axis=1
            )
        rect += list(df_temp["rect"].values)
        borders += list(df_temp["borders"].values)
        window_width += list(df_temp["xw"].values)
        small_window = min(window_width)
        big_window = max(window_width)
        mean_window = np.mean(window_width)
    return rect, borders, small_window, big_window, mean_window


from flask.ext.gears import Gears
from flask.ext.sqlalchemy import SQLAlchemy
from gears.compressors import SlimItCompressor
from gears_stylus import StylusCompiler
from gears_clean_css import CleanCSSCompressor
from gears_coffeescript import CoffeeScriptCompiler

gears = Gears()
db = SQLAlchemy()

_compilers = {
    '.styl': StylusCompiler.as_handler(),
    '.coffee': CoffeeScriptCompiler.as_handler()
}

_compressors = {
    "text/css": CleanCSSCompressor.as_handler(),
    "application/javascript": SlimItCompressor.as_handler()
}

def gears_environment(app):
    return app.extensions['gears']['environment']

def setup_compilers(app):
    env = gears_environment(app)
    for extension, compiler in _compilers.iteritems():
        env.compilers.register(extension, compiler)

def setup_compressors(app):
    env = gears_environment(app)
    if not app.config["DEBUG"] or app.config["TESTING"]:
        for mimetype, compressor in _compressors.iteritems():
            env.compressors.register(mimetype, compressor)

def setup_database(app):
    db = app.extensions["sqlalchemy"].db

    @app.before_first_request
    def create_database_for_development():
        is_sqlite_memory = (db.engine.url.drivername == "sqlite" and
                            db.engine.url.host in ("", ":memory:"))
        if app.config["DEBUG"] and is_sqlite_memory:
            db.create_all()