import wandb
from src.models.segmentation_unet import UNet
import time
import shutil
from pytorch_lightning.loggers import WandbLogger
from torchvision.utils import make_grid
import pytorch_lightning as pl
import os
from torch.utils.data import DataLoader
from tqdm import tqdm
import torch
from log import logger
from utils import get_compute_mask_args, make_exp_config, collate_batch, get_train_segmentation_refined
from utils import resize_long_edge
from utils import DatasetSplit
from sklearn.metrics import accuracy_score, jaccard_score
from datetime import datetime
import torchvision
from einops import rearrange
from scipy.ndimage import binary_fill_holes, binary_closing
from torch import optim
from pytorch_lightning.callbacks import ModelCheckpoint
from src.foreground_masks import GMMMaskSuggestor
from src.datasets.dataset import FOBADataset
from src.datasets.utils import path_to_tensor
from src.datasets import get_dataset

class FOBAUnetDataset(FOBADataset):
    def __init__(self, opt, H, W, mask_dir=None):
        super().__init__(opt, H, W, mask_dir)
        self.refined_mask_base_dir = os.path.join(self.opt.base_dir, "refined_mask_tmp")
        logger.info(f"Expecting refined masks in: {self.refined_mask_base_dir}")

        self.load_segmentations = opt.dataset_args.get("load_segmentations", False)
        self._build_dataset()

    def _load_images(self, index):
        assert len(index) == 1
        entry = self.data[index[0]].copy()
        img = path_to_tensor(entry["img_path"])

        entry["img"] = img
        entry["refined_mask"] = torch.load(os.path.join(self.refined_mask_base_dir, entry["rel_path"] + ".pt"))

        if self._preliminary_masks_path is not None:
            entry["preliminary_mask"] = torch.load(os.path.join(self._preliminary_masks_path, entry["rel_path"] + ".pt"))

        if self.load_segmentations:
            seg = (path_to_tensor(entry["seg_path"]) + 1) / 2
            entry["segmentation"] = seg
            y = torch.full((1, 3, self.H, self.W), 0.)
            y[0, :, :seg.size()[2], :seg.size()[3]] = seg
            entry["segmentation_x"] = y
        return entry

    def check_all_masks_computed(self):
        for i in range(len(self)):
            entry = self.data[i]
            if not os.path.isfile(os.path.join(self.refined_mask_base_dir, entry["rel_path"] + ".pt")):
                logger.warn(f"File number {i} not found: {os.path.join(self.refined_mask_base_dir, entry['rel_path'] + '.pt')}")
                return False
        logger.info("All refined masks successfully computed")
        return True

    def calculate_refined_masks(self, exp_name):
        self.add_inpaintings(exp_name)
        self.add_preliminary_masks()

        mask_suggestor = GMMMaskSuggestor(self.opt)

        assert self._preliminary_masks_path is not None, "Define preliminary mask path"
        assert self._inpainted_images_path is not None, "Define inpainting image path"
        for i in tqdm(range(len(self)), "computing refined inpainting"):
            sample = self.data[i].copy()
            img = path_to_tensor(sample["img_path"])
            sample["img"] = img
            sample["preliminary_mask"] = torch.load(os.path.join(self._preliminary_masks_path, sample["rel_path"] + ".pt"))
            sample["inpainted_image"] = torch.load(os.path.join(self._inpainted_images_path, sample["rel_path"] + ".pt"))

            tmp_mask_path = os.path.join(self.opt.base_dir, "refined_mask_tmp", sample["rel_path"] + ".pt")
            if os.path.isfile(tmp_mask_path):
                continue

            # get original image
            original_image = sample["img"]
            original_image = (original_image + 1) / 2
            y_resized = resize_long_edge(original_image, 512)  # resized s.t. long edge has lenght 512
            y = torch.zeros((1, 3, 512, 512))
            y[:, :, :y_resized.size()[-2], :y_resized.size()[-1]] = y_resized

            # get preliminary mask
            resize_to_img_space = torchvision.transforms.Resize(512)
            prelim_mask = resize_to_img_space(mask_suggestor(sample, "preliminary_mask").unsqueeze(dim=0))

            # get inpainted image
            inpainted = sample["inpainted_image"]

            # get gmm diff mask
            diff = (abs(y[0, :, :inpainted.size()[-2], :inpainted.size()[-1]] - inpainted))
            diff = rearrange(diff, "c h w -> 1 h w c")
            diff_mask = mask_suggestor(diff.mean(dim=3), key=None).unsqueeze(dim=0)

            prelim_mask = prelim_mask[:, :diff_mask.size()[-2], :diff_mask.size()[-1]]
            refined_mask = prelim_mask * diff_mask
            refined_mask = refined_mask.unsqueeze(dim=0)
            os.makedirs(os.path.dirname(tmp_mask_path), exist_ok=True)
            torch.save(refined_mask, tmp_mask_path)

def get_train_preprocessing_function(postprocess_function):
    transforms = []
    transforms.append(torchvision.transforms.Resize(128))
    transforms.append(torchvision.transforms.RandomCrop(128))
    transforms.append(torchvision.transforms.RandomHorizontalFlip())
    tform = torchvision.transforms.Compose(transforms)

    def preprocess(batch):
        samples = []
        for i in range(len(batch["img"])):
            sample = {k: batch[k][i] for k in batch}
            samples.append(sample)

        batch["x128"] = []
        batch["y128"] = []
        for i in range(len(samples)):
            x_resized = resize_long_edge(samples[i]["img"], 512)
            y = samples[i]["refined_mask"]
            y = postprocess_function(y)
            if x_resized.ndim == 3 and x_resized.size()[0] == 3:
                x_resized = x_resized.unsqueeze(dim=0)
            if y.ndim == 3 and y.size()[0] == 1:
                y = y.unsqueeze(dim=0)
            xcaty = torch.cat([x_resized, y[:, :, :x_resized.size()[-2], :x_resized.size()[-1]]], dim=1)
            xcaty = tform(xcaty)
            batch["x128"].append(xcaty[:, :3])
            batch["y128"].append(xcaty[:, 3:])

        batch["x128"] = torch.cat(batch["x128"]).to("cuda")
        batch["y128"] = torch.cat(batch["y128"]).to("cuda")
        return batch
    return preprocess

def get_val_preprocessing_function(postprocess_function):
    transforms = []
    transforms.append(torchvision.transforms.Resize(128))
    transforms.append(torchvision.transforms.CenterCrop(128))
    tform = torchvision.transforms.Compose(transforms)

    def preprocess(batch):
        samples = []
        for i in range(len(batch["img"])):
            sample = {k: batch[k][i] for k in batch}
            samples.append(sample)

        batch["x128"] = []
        batch["y128"] = []
        for i in range(len(samples)):
            x_resized = resize_long_edge(samples[i]["img"], 512)
            y = samples[i]["refined_mask"]
            y = postprocess_function(y)
            if x_resized.ndim == 3 and x_resized.size()[0] == 3:
                x_resized = x_resized.unsqueeze(dim=0)
            if y.ndim == 3 and y.size()[0] == 1:
                y = y.unsqueeze(dim=0)
            xcaty = torch.cat([x_resized, y[:, :, :x_resized.size()[-2], :x_resized.size()[-1]]], dim=1)
            xcaty = tform(xcaty)
            batch["x128"].append(xcaty[:, :3])
            batch["y128"].append(xcaty[:, 3:])

        batch["x128"] = torch.cat(batch["x128"])
        batch["y128"] = torch.cat(batch["y128"])
        return batch
    return preprocess

def get_test_preprocessing_function():
    transforms = []
    transforms.append(torchvision.transforms.Resize(128))
    transforms.append(torchvision.transforms.CenterCrop(128))
    tform = torchvision.transforms.Compose(transforms)

    def preprocess(sample):
        sample["x128"] = []
        sample["y128"] = []

        for i in range(len(sample["x"])):
            x = sample["img"][i]
            y = sample["segmentation"][i]
            xcaty = torch.cat([x, y], dim=1)
            xcaty = tform(xcaty)
            sample["x128"].append(xcaty[:, :3])
            sample["y128"].append(xcaty[:, 3:])

        sample["x128"] = torch.cat(sample["x128"])
        sample["y128"] = torch.cat(sample["y128"]).mean(dim=1, keepdims=True)
        return sample
    return preprocess

def get_test_bbox_preprocessing_function():
    transforms = []
    transforms.append(torchvision.transforms.Resize(128))
    transforms.append(torchvision.transforms.CenterCrop(128))
    tform = torchvision.transforms.Compose(transforms)

    def preprocess(sample):
        sample["x128"] = []
        sample["y128"] = []

        for i in range(len(sample["x"])):
            x = sample["img"][i]
            y = sample["segmentation"][i]
            xcaty = torch.cat([x, y], dim=1)
            xcaty = tform(xcaty)
            sample["x128"].append(xcaty[:, :3])
            sample["y128"].append(xcaty[:, 3:])

        sample["x128"] = torch.cat(sample["x128"])
        sample["y128"] = torch.cat(sample["y128"]).mean(dim=1, keepdims=True)
        return sample
    return preprocess

class SegmentationUnet(pl.LightningModule):
    def __init__(self, model, train_transform, val_transform, test_transform, bbox_mode):
        super().__init__()
        self.model = model
        self.train_transform = train_transform
        self.val_transform = val_transform
        self.test_transform = test_transform
        self.step = 0
        self.save_hyperparameters()
        self.bbox_mode = bbox_mode
        #self.compute_

    def training_step(self, train_batch, batch_idx):
        x = self.train_transform(train_batch)
        x = train_batch["x128"]
        y = train_batch["y128"]

        x_out = self.model(x)

        self.step += 1
        loss = torch.nn.functional.binary_cross_entropy(x_out, y)

        self.log('global_step', self.step)
        self.log('train/loss', loss)

        if batch_idx == 0:
            self.log_prediction(x, y, x_out)
        return loss

    def validation_step(self, val_batch, batch_idx):
        self.val_transform(val_batch)

        x = val_batch["x128"]
        y = val_batch["y128"]

        x_out = self.model(x)

        loss = torch.nn.functional.binary_cross_entropy(x_out, y)
        self.log("val/loss", loss)

        if batch_idx == 0:
            self.log_prediction(x, y, x_out)
        return {"loss": loss, "x_out": x_out.flatten().cpu(), "y": y.flatten().cpu().to(bool)}

    def log_prediction(self, x, y, x_out, test=False):
        inputs = make_grid(x, nrows=8)
        gt = make_grid(y, nrows=8)
        predictions = make_grid(x_out, nrows=8)
        appendix = "val" if not test else "test"
        wandb.log({"images" + appendix: wandb.Image(inputs.cpu()),
                   "gt_seg" + appendix: wandb.Image(gt.mean(dim=0).cpu().numpy()),
                   "predictions" + appendix: wandb.Image(self.threshold_prediction(predictions.cpu(), th=0.5).to(torch.float32).mean(dim=0).numpy()),
                   })

    def threshold_prediction(self, pred, th):
        pred[pred >= th] = 1
        pred[pred < th] = 0
        pred = pred.to(bool)
        return pred

    def validation_epoch_end(self, validation_step_outputs):
        losses = []
        y_pred = []
        y_true = []
        for step_outputs in validation_step_outputs:
            losses.append(step_outputs["loss"])
            y_pred.extend(step_outputs["x_out"])
            y_true.extend(step_outputs["y"])
        y_true = torch.tensor(y_true)
        y_pred = torch.tensor(y_pred)

        y_pred = self.threshold_prediction(y_pred, 0.5) # sigmoid

        accuracy = accuracy_score(y_true, y_pred)
        iou = jaccard_score(y_true, y_pred)
        miou = (iou + jaccard_score(torch.logical_not(y_true), torch.logical_not(y_pred))) /2

        self.log("val/batch_loss", torch.tensor(losses).mean())
        self.log("val/acc", accuracy)
        self.log("val/iou", iou)
        self.log("val/miou", miou)
        return {"accuracy":accuracy, "iou":iou, "miou":miou}

    def configure_optimizers(self):
        optimizer = optim.Adam(self.parameters(), lr=0.002)
        return optimizer

    def test_step(self, test_batch, batch_idx):
        self.test_transform(test_batch)

        x = test_batch["x128"]
        y = test_batch["y128"]

        x_out = self.model(x)
        y_pred = self.threshold_prediction(x_out, 0.5) # sigmoid
        for i in range(len(x_out)):
            y_pred[i] = torch.tensor(binary_fill_holes(binary_closing(y_pred[i].cpu())))

        loss = torch.nn.functional.binary_cross_entropy(x_out, y)
        self.log("test/loss", loss)

        if batch_idx == 0:
            self.log_prediction(x, y, x_out, test=True)
        return {"loss": loss, "x_out": x_out.cpu(), "y": y.cpu().to(bool)}

    def test_epoch_end(self, validation_step_outputs):
        losses = []
        y_pred = []
        y_true = []
        for step_outputs in validation_step_outputs:
            losses.append(step_outputs["loss"])
            y_pred.extend(step_outputs["x_out"].flatten())
            y_true.extend(step_outputs["y"].flatten())
        y_true = torch.tensor(y_true)
        y_pred = torch.tensor(y_pred)

        y_pred = self.threshold_prediction(y_pred, 0.5) # sigmoid
        if self.bbox_mode:
            pass

        accuracy = accuracy_score(y_true, y_pred)
        iou = jaccard_score(y_true, y_pred)
        miou = (iou + jaccard_score(torch.logical_not(y_true), torch.logical_not(y_pred))) /2

        self.log("test/batch_loss", torch.tensor(losses).mean())
        self.log("test/acc", accuracy)
        self.log("test/iou", iou)
        self.log("test/miou", miou)
        return {"accuracy":accuracy, "iou":iou, "miou":miou}

def get_datasets_splits_human(opt):
    opt.dataset_args["load_segmentations"] = False

    opt.dataset_args["split"] = DatasetSplit("train")
    opt.dataset_args["limit_dataset"] = [0, 5000]
    train_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not train_dataset.check_all_masks_computed():
        train_dataset.calculate_refined_masks(opt.exp_name)

    opt.dataset_args["split"] = DatasetSplit("val")
    opt.dataset_args["limit_dataset"] = [0, 100]
    val_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not val_dataset.check_all_masks_computed():
        val_dataset.calculate_refined_masks(opt.exp_name)

    return train_dataset, val_dataset, val_dataset#2nd one unused

def get_datasets_splits_bbox(opt):
    opt.dataset_args["load_segmentations"] = False
    opt.dataset_args["load_bboxes"] = False
    #opt.dataset_args["limit_dataset"] = [0, 100]

    opt.dataset_args["split"] = DatasetSplit("train")
    train_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not False: # train_dataset.check_all_masks_computed():
        train_dataset.calculate_refined_masks(opt.exp_name)

    opt.dataset_args["split"] = DatasetSplit("train")
    opt.dataset_args["limit_dataset"] = [0, 1] # ununsed as I train for fixed number of steps
    val_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not val_dataset.check_all_masks_computed():
        val_dataset.calculate_refined_masks(opt.exp_name)

    opt.dataset_args["split"] = DatasetSplit("test")
    opt.dataset_args["load_bboxes"] = True
    test_dataset = get_dataset(opt)
    return train_dataset, val_dataset, test_dataset#2nd one unused

def get_datasets_splits(opt):
    opt.dataset_args["split"] = DatasetSplit("train")
    #opt.dataset_args["limit_dataset"] =[0, 10]
    train_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not train_dataset.check_all_masks_computed():
        train_dataset.calculate_refined_masks(opt.exp_name)

    opt.dataset_args["split"] = DatasetSplit("val")
    val_dataset = FOBAUnetDataset(opt, opt.H, opt.W, mask_dir=opt.out_dir)
    if not val_dataset.check_all_masks_computed():
        val_dataset.calculate_refined_masks(opt.exp_name)

    opt.dataset_args["split"] = DatasetSplit("test")
    opt.dataset_args["load_segmentations"] = True
    test_dataset = get_dataset(opt)
    return train_dataset, val_dataset, test_dataset

def train_segmentation_network(opt):
    opt.exp_name = args.exp_name if args.exp_name is not None else opt.exp_name
    logger.info(f"Experiment name:{opt.exp_name}")

    model = UNet(
        n_channels=3,
        n_classes=1,
    )

    if opt.dataset == "human36":
        train_dataset, val_dataset, test_dataset = get_datasets_splits_human(opt)
        # assert test_dataset is val_dataset
    elif opt.dataset == "bird":
        train_dataset, val_dataset, test_dataset = get_datasets_splits(opt)
    elif opt.dataset == "dog":
        train_dataset, val_dataset, test_dataset = get_datasets_splits_bbox(opt)
    elif opt.dataset == "car":
        train_dataset, val_dataset, test_dataset = get_datasets_splits_bbox(opt)
    else:
        raise ValueError("No dataset selected in exp file")

    logger.info(f"Splits: Train {len(train_dataset)}, Val {len(val_dataset)}, Test {len(test_dataset)}")

    device = torch.device("cuda")
    model = model.to(device)

    train_dataloader = DataLoader(train_dataset,
                            batch_size=32,
                            shuffle=True,
                            num_workers=0,#opt.num_workers,
                            collate_fn=collate_batch,
                            )
    val_dataloader = DataLoader(val_dataset,
                            batch_size=32,
                            shuffle=False,
                            num_workers=0,#opt.num_workers,
                            collate_fn=collate_batch,
                            )
    test_dataloader = DataLoader(test_dataset,
                            batch_size=32,
                            shuffle=False,
                            num_workers=0,#opt.num_workers,
                            collate_fn=collate_batch,
                            )

    if not args.postprocess:
        logger.info(f"No postprocessing")
        postprocess_function = lambda x: x
    else:
        logger.info(f"Postprocessing masks with filling and closing")
        mask_suggestor = GMMMaskSuggestor(opt)
        postprocess_function = mask_suggestor._compute_post_processed

    bbox_mode = args.bbox_mode
    logger.info(f"Running test in with BBox Mode set: {bbox_mode}")
    test_transform = get_test_preprocessing_function() if not bbox_mode else get_test_bbox_preprocessing_function()

    module = SegmentationUnet(model,
                        train_transform=get_train_preprocessing_function(postprocess_function),
                        val_transform=get_val_preprocessing_function(postprocess_function),
                        test_transform=test_transform,
                        bbox_mode=bbox_mode,
                    )

    steps_per_epoch =(len(train_dataset) // 32) + 1 - (len(train_dataset) % 32 == 0)
    n_epochs = int(12000 / steps_per_epoch)
    #n_epochs = 3

    logger.info(f"Training for: {n_epochs} epochs")
    wandb_logger = WandbLogger(project="foba-unet")
    checkpoint_callback = ModelCheckpoint(dirpath=os.path.join(opt.log_dir, "checkpoints"),
                                          save_last=True,
                                          save_top_k=2,
                                          monitor="val/acc",
                                          mode="max",
                                          )
    logger.info(f"Logging checkpoints to {os.path.join(opt.log_dir, 'checkpoints')}")

    trainer = pl.Trainer(
        accelerator="gpu",
        strategy="ddp",
        devices=torch.cuda.device_count(),
        max_epochs=n_epochs,
        callbacks=[checkpoint_callback],
        logger=wandb_logger,
    )

    if args.test_only:
        assert args.ckpt_path is not None
        logger.info(f"Start testing with latest model: {args.ckpt_path}")
        test_trainer = pl.Trainer(
            accelerator="gpu",
            devices=1,
            logger=wandb_logger,
        )
        test_trainer.test(
            module,
            dataloaders=[test_dataloader],
            ckpt_path=args.ckpt_path,
        )
        exit(0)

    trainer.fit(module,
                train_dataloaders=train_dataloader,
                val_dataloaders=[val_dataloader],
                )

    if trainer.global_rank == 0:
        save_path = os.path.join(opt.log_dir, 'checkpoints', "last.ckpt")
        logger.info(f"Saving latest model to: {save_path}")
        trainer.save_checkpoint(filepath=save_path)

        logger.info(f"Start testing with latest model: {save_path}")
        test_trainer = pl.Trainer(
            accelerator="gpu",
            devices=1,
            logger=wandb_logger,
        )
        test_trainer.test(
            module,
            dataloaders=[test_dataloader],
            ckpt_path=save_path,
        )

if __name__ == "__main__":
    args = get_train_segmentation_refined()
    exp_config = make_exp_config(args.EXP_PATH)

    # make log dir
    if not os.path.exists(exp_config.log_dir): os.mkdir(exp_config.log_dir)
    log_dir = os.path.join(exp_config.log_dir, exp_config.name, datetime.now().strftime("%Y-%m-%dT%H-%M-%S"))
    os.makedirs(log_dir)
    os.makedirs(os.path.join(log_dir, "checkpoints"))
    shutil.copy(exp_config.__file__, log_dir)
    exp_config.log_dir = log_dir

    wandb.init(project="foba-unet", entity="hr-biomedia")
    exp_config.args = args
    train_segmentation_network(exp_config)


from dataset.imagenet import make_testloader
from mrfi import MRFI, EasyConfig
from mrfi.experiment import BER_Acc_experiment, logspace_density, Acc_golden, benchmark_range
import matplotlib.pyplot as plt
from torchvision.models import resnet18
import torch

econfig = EasyConfig.load_file('easyconfigs/fxp_fi.yaml')
fi_model = MRFI(resnet18(pretrained = True).cuda().eval(), econfig)

selector_cfg = fi_model.get_activation_configs('selector')
quantization_cfg = fi_model.get_activation_configs('quantization.args')
errormode_cfg = fi_model.get_activation_configs('error_mode.args')

batch_size = 128
n_images = 1000

quantization_cfg.integer_bit = 2
quantization_cfg.decimal_bit = 12
errormode_cfg.bit_width = 14

Acc_golden(fi_model, make_testloader(n_images, batch_size = batch_size))
BER, Acc = BER_Acc_experiment(fi_model, selector_cfg, make_testloader(n_images, batch_size = batch_size))

print(Acc)

quantization_cfg.integer_bit = 3
quantization_cfg.decimal_bit = 13
errormode_cfg.bit_width = 16

Acc_golden(fi_model, make_testloader(n_images, batch_size = batch_size))
BER, Acc = BER_Acc_experiment(fi_model, selector_cfg, make_testloader(n_images, batch_size = batch_size))


import pygame
import random
import pygame_menu

class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y, size):
        super().__init__()

        self.direction = "left"
        self.size = size

        self.image = pygame.Surface((size, size), pygame.SRCALPHA, 32).convert_alpha()
        self.rect = pygame.draw.circle(
                self.image,
                "yellow",
                (size/2, size/2),
                self.size/2,
            )
        self.rect.x = x
        self.rect.y = y

    def update(self):
        if self.direction == "left":
            self.rect.x -= 3
        else:
            self.rect.x += 3

class Controller:
    def __init__(self):
        pygame.init()

        self.screen = pygame.display.set_mode()
        self.width, self.height = pygame.display.get_window_size()

        ## Bouncing Ball Model
        self.ball = Ball(self.width / 2, self.height / 2, 100)
        self.sprites = pygame.sprite.Group((self.ball,))

        self.state = "START"

    def mainloop(self):
        while True:
            if self.state == "START":
                self.startloop()
            elif self.state == "GAME":
                self.gameloop()
            elif self.state =="END":
                self.endloop()

    def startloop(self):

        self.menu = pygame_menu.Menu("Start Screen", self.width-20, self.height/2)
        # a label allows you to add text on the page
        self.menu.add.label("Click anywhere to start the program", max_char=-1, font_size=14)
        #a button creates a button you can link to a method
        self.menu.add.button(
            'Start',
            self.start_game,
            align=pygame_menu.locals.ALIGN_CENTER
        )

        while self.state == "START":
            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONDOWN:
                    self.state = "GAME"

            # pass events to the menu object
            self.menu.update(pygame.event.get())
            # You can draw the menu onto any surface
            # so if you want the menu in a specific place,
            # create a surface in the location to draw the menu on
            self.menu.draw(self.screen)
            pygame.display.flip()

    def endloop(self):
        font_obj = pygame.font.SysFont(None, 48)
        msg = font_obj.render("You win! You may quit any time by closing the program", True, "yellow")

        while self.state == "END":

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    exit()

            self.screen.blit(msg, (10, 10))
            pygame.display.flip()

    def gameloop(self):

        while self.state == "GAME":

            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONDOWN:
                    if self.ball.rect.collidepoint(event.pos):
                        self.state = "END"

            self.sprites.update()
            # adjust the direction of the ball based on position
            if self.ball.rect.x < 0:
                self.ball.direction = "right"
            elif self.ball.rect.x > (self.width - self.ball.rect.width):
                self.ball.direction = "left"

            self.screen.fill("purple")

            self.sprites.draw(self.screen)

            pygame.display.flip()

    def start_game(self):
        self.state = "GAME"

def main():
    game = Controller()
    game.mainloop()

if __name__ == "__main__":
    main()


# -*- coding: UTF-8 -*-
import tensorflow as tf
from tensorflow.keras import layers

class Generator:
    def __init__(self, in_channels, optimizer, loss_fn, height, width):
        self.in_channels = in_channels
        self.height = height
        self.width = width

        self.g_optimizer = optimizer
        self.loss_fn = loss_fn
        self.g_loss = None
        self.gen_loss_tracker = tf.keras.metrics.Mean(name="generator_loss")

        self.model = tf.keras.Sequential(
            [
                layers.InputLayer((self.in_channels,)),
                # We want to generate 128 + num_classes coefficients to reshape into a
                # 7x7x(128 + num_classes) map.
                layers.Dense((self.height // 4) * (self.width // 4) * self.in_channels),
                layers.LeakyReLU(alpha=0.2),
                layers.Reshape(((self.height // 4), (self.width // 4), self.in_channels)),

                layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
                layers.BatchNormalization(),
                layers.LeakyReLU(alpha=0.2),

                layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
                layers.BatchNormalization(),
                layers.LeakyReLU(alpha=0.2),

                # layers.Conv2DTranspose(128, (4, 4), strides=(1, 1), padding="same"),
                # layers.BatchNormalization(),
                # layers.LeakyReLU(alpha=0.2),

                layers.Conv2D(1, (7, 7), padding="same", activation="sigmoid"),
            ],
            name="generator",
        )

    def generate(self, captcha):
        return self.model(captcha)

    def train(self, random_vector, image_hot_labels, misleading_labels, discriminator):
        # Train the generator (note that we should *not* update the weights
        # of the discriminator)!
        with tf.GradientTape() as tape:
            fake_images = self.generate(random_vector)
            fake_image_and_labels = tf.concat([fake_images, image_hot_labels], -1)
            predictions = discriminator.predict(fake_image_and_labels)
            self.g_loss = self.loss_fn(misleading_labels, predictions)
        grads = tape.gradient(self.g_loss, self.model.trainable_weights)
        self.g_optimizer.apply_gradients(zip(grads, self.model.trainable_weights))

    def update_weights(self):
        self.gen_loss_tracker.update_state(self.g_loss)

    def get_loss_track(self):
        return self.gen_loss_tracker.result()

if __name__ == "__main__":
    generator = Generator(in_channels=134,
                          optimizer=None,
                          loss_fn=tf.keras.losses.BinaryCrossentropy(),
                          height=40,
                          width=24)
    MODEL_IMG = 'generator.png'
    tf.keras.utils.plot_model(generator.model, to_file=MODEL_IMG,
                              show_shapes=True,
                              show_layer_activations=True,
                              show_dtype=True,
                              show_layer_names=True)
    print(generator.model.summary())


import pickle
from skimage.io import imread
from skimage.transform import resize
import matplotlib.pyplot as plt

# Đọc mô hình từ tệp "model.sav"
with open('model.sav', 'rb') as model_file:
    model = pickle.load(model_file)
Categories = ['cats', 'dogs']
# Đường dẫn tới ảnh mới bạn muốn dự đoán nhãn
path = 'test/test_1.jpg'
img = imread(path)

# Chuẩn bị ảnh mới và dự đoán nhãn
img_resize = resize(img, (150, 150, 3))
l = [img_resize.flatten()]
probability = model.predict_proba(l)

# In kết quả
for ind, val in enumerate(Categories):
    print(f'{val} = {probability[0][ind]*100:.2f}%')

predicted_label = model.predict(l)[0]
print("The predicted image is:", Categories[predicted_label])
plt.imshow(img)
plt.show()


#%%
import os
import json
import matplotlib.pyplot as plt
import matplotlib as mpl
import tikzplotlib

import matplotlib.font_manager as fm

mpl.rcParams['font.family'] = 'serif'
mpl.rcParams['font.serif'] = 'Times New Roman'
mpl.rcParams['mathtext.fontset'] = 'custom'
mpl.rcParams['mathtext.rm'] = 'Times New Roman'
mpl.rcParams['mathtext.it'] = 'Times New Roman:italic'
mpl.rcParams['mathtext.bf'] = 'Times New Roman:bold'

def copy_keys(dict_orig):
    dict_new = {}
    for k, v in dict_orig.items():
        if isinstance(dict_orig[k], dict):
            dict_new[k] = copy_keys(dict_orig[k])
        else:
            dict_new[k] = str(type(dict_orig[k]))
    return dict_new

path = 'output/json/'
json_dicts = []
color_labels = {
    'G' : '#D0D0D0',
    'P' : '#720069',
    'SQ' : 'green'
}
custom_labels = [
    'Non-augmented, $f_{CNN}$ 4 Hz',
    'Non-augmented, $f_{CNN}$ 1 Hz',
    'Augmented, $f_{CNN}$ 4 Hz',
    'Augmented, $f_{CNN}$ 1 Hz'
    ]
model_idx_mod = {
    0 : 2,
    1 : 3,
    2 : 0,
    3 : 1
}
model_mod = {
    0 : 1.95,
    1 : 2.5,
    2 : 0.5,
    3 : 1.05
}

for filename in os.listdir(path):
    if filename.endswith('.json'):
        with open(os.path.join(path, filename)) as f:
            json_dicts.append(json.load(f))

model_list = []
max_index = 0
for j in json_dicts:
    model_list.append(f"{j['model']}_{j['shift']}")
    for d in j['dict']:
        max_index = max(max(j['dict'][d]['y']), max_index)

data = [None] * (max_index + 1)

for j in json_dicts:
    model_idx = model_list.index(f"{j['model']}_{j['shift']}")
    for d in j['dict']:
        for x, y in zip(j['dict'][d]['x'], j['dict'][d]['y']):
            if data[y] is None:
                data[y] = {
                    'G' : {'x': [], 'y': []},
                    'P' : {'x': [], 'y': []},
                    'SQ' : {'x': [], 'y': []}
                }
            # if d not in data[y]:
            #     data[y][d] = {'x': [], 'y': []}
            data[y][d]['x'].append(x * j['shift'])
            data[y][d]['y'].append(model_mod[model_idx])

# %%
vertical_lines = [
    [1.53,  4.83,   8.97, 13.04, 16.67, 20.40, 24.56],
    [0.6, 5.16, 9.07, 13.57, 17.07, 22.56, 26.91],
    [0.42, 4.29, 6.97, 9.79, 13.24, 16.2, 19.51, 22.7, 25.6],
    [2.16, 6.02, 9.52, 13.25, 17.23, 21.5, 25.3],
    [0.03, 4.71, 9.52, 13.61, 17.59, 21.92, 25.12],
    [0.86, 3.71, 6.32, 9.7, 12.96, 15.75, 19.01, 23.1]
]
miss_line = [
    [[1], [], [], [], [], [1], [1]],
    [[], [], [], [1], [], [1], [1]],
    [[1], [], [], [], [], [1], [1], [], [1]],
    [[1], [], [], [], [], [], []],
    [[], [], [], [], [], [1], []],
    [[1], [], [], [], [], [], [], []]
]

figure_w = 16
figure_h = 4
fig = plt.figure(figsize=(figure_w, figure_h))

for i in range(len(data)):
    for d in data[i]:
        plt.scatter(data[i][d]['x'], data[i][d]['y'], marker="s", s = 30, c = color_labels[d])

    miss_w = 0.75
    miss_h = 0.2
    ylim_min = 0.15
    ylim_max = 2.85

    xmin, xmax = plt.xlim()  # Ottieni i limiti dell'asse x
    for v_line, m_line in zip(vertical_lines[i], miss_line[i]):
        plt.axvline(x=v_line, color='k', linestyle='--', alpha=0.5)
        for m in m_line:
            relative_xmin = (v_line - miss_w - xmin) / (xmax - xmin)
            relative_xmax = (v_line + miss_w - xmin) / (xmax - xmin)
            plt.axhspan(model_mod[m] - miss_h, model_mod[m] + miss_h, xmin=relative_xmin, xmax=relative_xmax, facecolor='red', alpha=0.25) #, zorder = 0)

    plt.ylim(ylim_min, ylim_max)
    plt.yticks([model_mod[k] for k in model_mod], custom_labels)
    # plt.yticks(['Squat', 'Squat', 'Squat', 'Push-up', 'Push-up', 'Push-up'])

    if i == 0:
        labels = ["Other", "Push-up", "Squat", "Ground truth", "Missed classification"]
        plt.legend(labels, loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=len(labels), fancybox=True, shadow=False)
    if i == len(data) - 1:
        plt.xlabel("Time [s]") # ,fontsize=14

    # plt.savefig("output/plot/raster.png")
    plt.savefig(f"output/plot/raster_{i}.pdf", format="pdf")
    tikzplotlib.save(f"output/plot/raster_{i}.tex")
    # plt.show()

    plt.clf()


#Creating windows and labels with Tkinter
import tkinter

window=tkinter.Tk()
window.title("My first GUI program")
window.minsize(width=500,height=300)

#Label
my_label=tkinter.Label(text="I am a Label",font=("Arial",24,"bold"))
my_label.pack() #place it in screen and center it else you won't see label
# my_label.pack(side="left") #label is displayed to the left
# my_label.pack(side="bottom") #label displayed on bottom
# my_label.pack(expand=True) #will take up full height and width of available screen size
my_label["text"]="New text" #changing intiial text
# my_label.config(text="New text")  #another way to write

#Entry
# input=tkinter.Entry(width=10)
# input.pack()
# ans=input.get()

input=tkinter.Entry(width=10)  #create text field
input.pack()

#event listener
def button_clicked():
    # print("Button got clicked!")
    # my_label["text"]="Button got clicked!"
    ans=input.get()       #type in text field and click button,text will be displayed in place of "New text"
    my_label["text"]=ans

#Button
button=tkinter.Button(text="Click here",command=button_clicked)
button.pack()

# while True:
#     listening
window.mainloop()


from flask import Flask
import requests
import random

app = Flask(__name__)
high = 300
close = 500
latestPrice = 240

def _get_fake_data():
    data = {
            'high':high,
            'close':close,
            'latestPrice':latestPrice+random.randint(1,10)
    }
    return data

@app.route('/stock/<ticker>')
def show_stock(ticker: str):
    print("Fetching data for {}".format(ticker))
    return _get_fake_data()

@app.route('/stock/<ticker>/<property>')
def show_stock_property(ticker: str, property: str):
    data = _get_fake_data()
    return str(data[property])

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)


import pandas as pd
import numpy as np
import math
import unicodedata
from datetime import datetime

#§ or ° are not included but is a standard so its fine, maybe include 255 ascii signs
#malformed so copied
#chars=string.printable
#len is 95 as DEL is excluded
printables='''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ '''
dict_char_classes={
        'lower':'abcdefghijklmnopqrstuvwxyz',
        'upper':'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
        'number':'0123456789',
        'special':'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ '''
        }

single_value_feats=['n_chars', 'n_numbers', 'n_letters', 'n_spaces',
                    'n_uppers', 'n_specials', 'is_date', 'perc_numbers',
                    'perc_letters', 'perc_specials', 'first_letter', 'digits_before_comma',
                    'digits_after_comma', 'negativ_number', 'first_number',
                    'unit_place', 'tenth_place']

cell_feats=['char_dist', 'word_shape', 'dict_lookup']

global_stats=['n_none','frac_none', 'entropy','frac_unique','frac_num',
     'frac_alpha','frac_sym','mean_num_count', 'std_num_count',
     'mean_alpha_count', 'std_alpha_count', 'mean_sym_count',
     'std_sym_count', 'sum_chars', 'min_chars','max_chars',
     'median_chars', 'mode_chars','kurt_chars', 'skew_chars',
     'any_chars', 'all_chars','dates','min_num', 'max_num', 'median_num',
     'mean_num', 'mode_num', 'std_num', 'kurt_num', 'skew_num']

endpoint_dict =	{
  'uniprot':'http://sparql.uniprot.org/sparql', #works great
  'ebi':'https://www.ebi.ac.uk/rdf/services/sparql', #certification error
  'disgenet':'http://rdf.disgenet.org/sparql/', #no results for found datatype properties
  'monarch':'http://rdf.monarchinitiative.org/sparql', #doesnt work --> returns some html stuff
  'lld':'http://linkedlifedata.com/sparql', #includes many databases
  'old':'http://sparql.openlifedata.org/', #includes many databases
  'wikipathways':'http://sparql.wikipathways.org/', #doesnt work --> returns some html stuff
  'tcga':'http://tcga.deri.ie/', #doesnt work --> returns some html stuff
  'pubchem':'https://pubchemdocs.ncbi.nlm.nih.gov/rdf', #doesnt work --> returns some html stuff
  'nbdc':'http://integbio.jp/rdf/sparql', #very much dt_properties
  'dbpedia':'http://dbpedia.org/sparql',
  'wikidata': 'https://query.wikidata.org/sparql'
}
#http://drugbank.bio2rdf.org/sparql old endpoint?
#'https://opensparql.sbgenomics.com/blazegraph/namespace/tcga_metadata_kb/sparql' doesnt work --> urlerror
#drugbank=lld

# from https://stackoverflow.com/a/518232/2809427
#normalizing the data to only contain ascii printables
def unicodeToAscii(s):
    chars=printables
    return ''.join(
        c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn'
        and c in chars)

#https://stackoverflow.com/questions/1265665/how-can-i-check-if-a-string-represents-an-int-without-using-try-except
def representsInt(s):
    try:
        int(s)
        return True
    except:
        return False

def representsFloat(s):
    try:
        float(s)
        return True
    except:
        return False

 #from https://stackoverflow.com/questions/33204500/pandas-automatically-detect-date-columns-at-run-time
def representsDate(s):
    try:
        pd.to_datetime(s, utc=True)
        return True
    except:
        return False

def drop_nan_inf_none(col):
    col=pd.Series(list(filter(None, col)))
    for i in range(len(col)):
        try:
            cell=float(col[i])
            if np.isnan(cell):
                col=col.drop(i)
                continue
            if np.isinf(cell):
                col=col.drop(i)
                continue
            if cell>np.finfo(np.float32).max:
                col=col.drop(i)
                continue
        except:
            continue
    return col.reset_index(drop=True)

def check_float_min_max(X):
    for i in range(len(X)):
        mask=X[i]>np.finfo(np.float64).max
        X[i][mask]=np.finfo(np.float64).max
        mask=X[i]<np.finfo(np.float64).min
        X[i][mask]=np.finfo(np.float64).min
    return X

def shape_for_scale(X, X_shape, n_features):
    X_helper=np.zeros(shape=(X_shape[0]*X_shape[1], n_features))
    idx=0
    for i in range(X_shape[0]):
        for j in range(n_features):
            X_helper[idx:idx+X_shape[1],j]=X[i][j,:]
        idx=idx+X_shape[1]
    return X_helper

def shape_for_training(X, X_shape, n_features):
    shaped=np.zeros(shape=(X_shape[0], n_features*X_shape[1]))
    idxrow=0
    for i in range(X_shape[0]):
        idxcolumn=0
        for j in range(n_features):
            shaped[i,idxcolumn:idxcolumn+X_shape[1]]=X[idxrow:idxrow+X_shape[1],j]
            idxcolumn=idxcolumn+X_shape[1]
        idxrow=idxrow+X_shape[1]
    return shaped

def get_single_value_feat_rows(X, n_features):
    X_single_feat=np.zeros(shape=(X.shape[0], X[0].shape[1]))
    for i in range(len(X)):
        X_single_feat[i,:]=X[i][n_features-1,:]
    return X_single_feat

def add_scaled_single_value_feat_rows(X_single_feat_scaled, X_scaled, n_chars, n_features):
    idx_row=0
    for i in range(X_single_feat_scaled.shape[0]):
        X_scaled[idx_row:idx_row+n_chars, n_features-1]=X_single_feat_scaled[i]
        idx_row+=n_chars
    return X_scaled

def make_feat_importance_file(key, classifier, samples, n_chars, n_features, duration, ser_metrics, ser_feature_importances, ser_feature_importances_summed):
    ser_metrics['duration']=duration
    ser_metrics['samples']=samples
    ser_metrics['n_chars']=n_chars
    ser_metrics['n_features']=n_features
    ser_metrics['database']=key
    now = datetime.now()
    dt_string = now.strftime('%Y_%m_%d_%H_%M_%S')
    #path='C:/Users/jgtha/Dropbox/Default Python Directory/data/measurements/'
    file_name='data/feature_importances/Scores_{}_{}_classifier_{}_samples_{}_nchars_{}_features_{}.xlsx'.format(key, classifier, samples, n_chars, n_features, dt_string)
    metrics_sheet='params_and_scores'
    feature_importance_sheet='feature_importances'
    feature_importance_summed_sheet='feature_importances_summed'
    with pd.ExcelWriter(file_name) as writer:
        ser_metrics.to_excel(writer, sheet_name=metrics_sheet, header=['params and scores'])
        ser_feature_importances.to_excel(writer, sheet_name=feature_importance_sheet, header=['feature importances'])
        ser_feature_importances_summed.to_excel(writer, sheet_name=feature_importance_summed_sheet, header=['summed feature importances'])


#Thea M Factorial of a positive no
def func_factorial(n):
    # factorial is n * by all the '+' no less than it
    #n= 7
    #if n == 1:
        # if n is 1 return 1
        #return n
    #else:
        #return n * (n-1)
        #

# Python program to find the factorial of a number provided by the user.

# change the value for a different result
#n= 7
# uncomment to take input from the user
#num = int(input("Enter a number: "))

    factorial = 1

# check if the number is negative, positive or zero
    if n < 0:
        print("not doing factorial for zero")
    elif n == 0:
        print("The factorial of 0 is 1")
    else:
        for i in range(1,n + 1):
            factorial = factorial*i
            print("The factorial of",n,"is",factorial)

func_factorial (7)


def armn(x):
	sum=0
	t=x
	while(t>0):
		d=t%10
		sum+=d**3
		t=t//10
	if sum ==x:
		return'armstrong number'
	else:
		return'not an armstrong number'

x=int(input("enter the number"))
print(armn(x))


import numpy as np
import torch
from torch import nn
import torch.nn.functional as F

class Bottleneck(nn.Module):
    def __init__(self, input_size, bottleneck_size, bottleneck_activation):
        """
        The function takes in the input size, the bottleneck size, and the bottleneck activation function.

        The function then creates a linear layer with the input size and the bottleneck size.

        The function then creates a linear layer with the bottleneck size and the input size.

        The function then returns the input size, the output size, the bottleneck size, and the bottleneck activation
        function.

        :param input_size: the size of the input to the bottleneck layer
        :param bottleneck_size: The size of the bottleneck layer
        :param bottleneck_activation: The activation function to use for the bottleneck layer
        """
        super(Bottleneck, self).__init__()
        self.input = int(np.prod(input_size))
        self.output = input_size
        self.bottleneck_size = bottleneck_size
        self.bottleneck_activation = bottleneck_activation
        self.fc1 = nn.Linear(self.input, bottleneck_size)
        self.fc2 = nn.Linear(bottleneck_size, self.input)

    def forward(self, x):
        """
        The function takes in an input, reshapes it, passes it through a linear layer, reshapes it again, and returns the
        output

        :param x: the input to the model
        :return: The output of the forward pass, and the encoded representation of the input.
        """
        x = x.view(-1, self.input)
        if self.bottleneck_activation is not None:
            encoded = self.bottleneck_activation(self.fc1(x))
        else:
            encoded = self.fc1(x)
        x = self.fc2(encoded)
        x = x.view(-1, self.output[0], self.output[1], self.output[2])
        return x, encoded


import time

import pytest

import zntrack

class SleepClassNoMetadata(zntrack.Node):
    sleep_for = zntrack.zn.params(1)

    @zntrack.tools.timeit("metadata")
    def run(self):
        time.sleep(self.sleep_for)

class SleepClass(zntrack.Node):
    sleep_for = zntrack.zn.params(1)
    timeit_metrics = zntrack.zn.metrics()

    @zntrack.tools.timeit("timeit_metrics")
    def run(self):
        time.sleep(self.sleep_for)

class SleepClassLoop(zntrack.Node):
    sleep_for = zntrack.zn.params(0.1)
    timeit_metrics = zntrack.zn.metrics()
    iterations = zntrack.zn.params(30)

    def run(self):
        for _ in range(30):
            self.sleep()

    @zntrack.tools.timeit("timeit_metrics")
    def sleep(self):
        time.sleep(self.sleep_for)

def test_timeit_no_metadata_err(proj_path):
    with pytest.raises(AttributeError):
        SleepClassNoMetadata().run()

@pytest.mark.parametrize("eager", [True, False])
def test_timeit(proj_path, eager):
    with zntrack.Project() as project:
        sleep_class = SleepClass()

    project.run(eager=eager)
    if not eager:
        sleep_class.load()
    assert pytest.approx(sleep_class.timeit_metrics["run"], 0.1) == 1.0

@pytest.mark.parametrize("eager", [True, False])
def test_timeit_loop(proj_path, eager):
    with zntrack.Project() as project:
        sleep_class = SleepClassLoop()
    project.run(eager=eager)
    if not eager:
        sleep_class.load()

    assert pytest.approx(sleep_class.timeit_metrics["sleep"]["mean"], 0.1) == 0.1
    assert sleep_class.timeit_metrics["sleep"]["std"] < 1e-2
    assert len(sleep_class.timeit_metrics["sleep"]["values"]) == 30


#!/usr/bin/env python
class TreeNode(object):
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

    def invertTree(self, root):
        """
        :type root: TreeNode
        :rtype: TreeNode
        """
        if root:
            root.right, root.left = self.invertTree(root.left), self.invertTree(root.right)
        return root

if __name__ == '__main__':
    root = TreeNode(1)
    root.left = TreeNode(2)
    root.right = TreeNode(3)
    root.left.left = TreeNode(4)
    root.left.right = TreeNode(5)
    root.right.left = TreeNode(6)
    root.right.right = TreeNode(7)
    print ("before:" ,root.val, root.left.val, root.right.val, root.left.left.val, root.left.right.val, root.right.left.val, root.right.right.val)
    root.invertTree(root)
    print ("after :" ,root.val,root.left.val,root.right.val,root.left.left.val,root.left.right.val,root.right.left.val,root.right.right.val)


from game.definitions import Tag
from game.operation.operations.modify_seat import ModifySeat
from game.scripting.api.sandbox_api import SandboxApi

class StudentApi(SandboxApi):
    def get_students(self):
        return self._sort_by_id(self.repo.students())

    def get_controlled_students(self):
        results = filter(lambda s: s.controlled, self.repo.students())
        return self._sort_by_id(results)

    def get_all_but_requestor(self):
        results = filter(lambda s: not s.controlled or s.actor.id != self.repo.requestor_id, self.repo.students())
        return self._sort_by_id(results)

    def get_adjacent_students(self, target_student):
        students = self.repo.students()
        results = []
        for s in students:
            proximate_column = abs(s.seat.column - target_student.seat.column) <= 1
            proximate_row = abs(s.seat.row - target_student.seat.row) <= 1
            if proximate_column and proximate_row and (proximate_column + proximate_row) > 0:
                results.append(s)
        return self._sort_by_id(results)

    def get_immediate_students(self, target_student):
        students = self.repo.students()
        results = []
        for s in students:
            diff_col = abs(s.seat.column - target_student.seat.column)
            diff_row = abs(s.seat.row - target_student.seat.row)
            if diff_col + diff_row == 1:
                results.append(s)
        return self._sort_by_id(results)

    def move_to_empty_seat(self, student, seat):
        default_tags = self.repo.default_tags
        operation = ModifySeat(
            destination_seat_id=seat.id,
            targeted_student_id=student.id,
            tags=default_tags.union({Tag.Seat})
        )
        self.program_api.students.move_student_to_empty_seat(operation=operation)

    def swap_seat(self, student, seat):
        default_tags = self.repo.default_tags
        operation = ModifySeat(
            destination_seat_id=seat.id,
            targeted_student_id=student.id,
            tags=default_tags.union({Tag.Seat})
        )
        self.program_api.students.swap_seat(operation=operation)

    def _sort_by_id(self, students):
        return list(sorted(students, key=lambda student: student.id))


import glob, os, mistune, pprint, subprocess, argparse, praw, getpass
from mako.template import Template
from multiprocessing import Pool
import uuid

class media:
    src = ''
    alt = ''
    opts = ''

class doc_section:
    level = 0
    title = ''
    media = ''
    text  = ''

def get_title(ast):
    for a in ast:
        if a['type'] == 'heading':
            return a['children'][0]['text']

    raise Exception('no title in ast')

def get_level(ast):
    for a in ast:
        if a['type'] == 'heading':
            return a['level']

    raise Exception('no title in ast')

def get_media_(ast):
    res = []
    for a in ast:
        if 'children' in a and a['type'] == 'paragraph':
            res.append(get_media(a['children']))
        elif a['type'] == 'image':
            m = media()
            m.src = a['src']
            m.alt = a['alt'].split(';')[0]
            m.opts = ';'.join(a['alt'].split(';')[1:])
            res.append(m)

    return res

def get_text_(ast):
    res = []
    for a in ast:
        if 'children' in a and a['type'] == 'paragraph':
            res.append(get_text(a['children']))
        elif a['type'] == 'text':
            res.append(a['text'])

    return res

def get_media(ast):
    res = get_media_(ast)
    if len(res) > 1:
        raise Exception('section can only have one media', ast)
    if len(res) == 0:
        raise Exception('section is missing media', ast)

    return res[0]

def get_text(ast):
    res = get_text_(ast)
    if len(res) > 1:
        raise Exception('section can only have one text', res)
    if len(res) == 0:
        raise Exception('section is missing text')

    return res[0]

def get_sections(ast):
    sections = []

    for a in ast:
        if a['type'] == 'heading':
            sections.append([])
        sections[-1].append(a)

    return sections

def make_doc_section(ast_section):
    section = doc_section()

    section.level = get_level(ast_section)
    section.title = get_title(ast_section)
    section.media = get_media(ast_section)
    section.text  = get_text(ast_section)

    return section

class Script:

    @staticmethod
    def get_script():
        doc = open('script.md', 'r').read()

        markdown = mistune.create_markdown(renderer=mistune.AstRenderer())
        markdown_ast = markdown(doc)

        doc_ast = {}

        sections = [make_doc_section(s) for s in get_sections(markdown_ast)]

        return sections


# encoding: utf-8
"""
@author:  sherlock
@contact: sherlockliao01@gmail.com
"""

import copy
import numpy as np
import os.path as osp
import tarfile
import zipfile
import torch
# from .dataset_loader import ImageDataset_new
from utils.iotools import mkdir_if_missing
class BaseDataset(object):
    """
    Base class of reid dataset
    """

    def get_imagedata_info(self, data):
        pids, cams = [], []
        for _, pid, camid in data:
            pids += [pid]
            cams += [camid]
        pids = set(pids)
        cams = set(cams)
        num_pids = len(pids)
        num_cams = len(cams)
        num_imgs = len(data)
        return num_pids, num_imgs, num_cams

    def get_videodata_info(self, data, return_tracklet_stats=False):
        pids, cams, tracklet_stats = [], [], []
        for img_paths, pid, camid in data:
            pids += [pid]
            cams += [camid]
            tracklet_stats += [len(img_paths)]
        pids = set(pids)
        cams = set(cams)
        num_pids = len(pids)
        num_cams = len(cams)
        num_tracklets = len(data)
        if return_tracklet_stats:
            return num_pids, num_tracklets, num_cams, tracklet_stats
        return num_pids, num_tracklets, num_cams

    def print_dataset_statistics(self):
        raise NotImplementedError

class BaseImageDataset(BaseDataset):
    """
    Base class of image reid dataset
    """

    def print_dataset_statistics(self, train, query, gallery):
        num_train_pids, num_train_imgs, num_train_cams = self.get_imagedata_info(train)
        num_query_pids, num_query_imgs, num_query_cams = self.get_imagedata_info(query)
        num_gallery_pids, num_gallery_imgs, num_gallery_cams = self.get_imagedata_info(gallery)

        print("Dataset statistics:")
        print("  ----------------------------------------")
        print("  subset   | # ids | # images | # cameras")
        print("  ----------------------------------------")
        print("  train    | {:5d} | {:8d} | {:9d}".format(num_train_pids, num_train_imgs, num_train_cams))
        print("  query    | {:5d} | {:8d} | {:9d}".format(num_query_pids, num_query_imgs, num_query_cams))
        print("  gallery  | {:5d} | {:8d} | {:9d}".format(num_gallery_pids, num_gallery_imgs, num_gallery_cams))
        print("  ----------------------------------------")

class BaseVideoDataset(BaseDataset):
    """
    Base class of video reid dataset
    """

    def print_dataset_statistics(self, train, query, gallery):
        num_train_pids, num_train_tracklets, num_train_cams, train_tracklet_stats = \
            self.get_videodata_info(train, return_tracklet_stats=True)

        num_query_pids, num_query_tracklets, num_query_cams, query_tracklet_stats = \
            self.get_videodata_info(query, return_tracklet_stats=True)

        num_gallery_pids, num_gallery_tracklets, num_gallery_cams, gallery_tracklet_stats = \
            self.get_videodata_info(gallery, return_tracklet_stats=True)

        tracklet_stats = train_tracklet_stats + query_tracklet_stats + gallery_tracklet_stats
        min_num = np.min(tracklet_stats)
        max_num = np.max(tracklet_stats)
        avg_num = np.mean(tracklet_stats)

        print("Dataset statistics:")
        print("  -------------------------------------------")
        print("  subset   | # ids | # tracklets | # cameras")
        print("  -------------------------------------------")
        print("  train    | {:5d} | {:11d} | {:9d}".format(num_train_pids, num_train_tracklets, num_train_cams))
        print("  query    | {:5d} | {:11d} | {:9d}".format(num_query_pids, num_query_tracklets, num_query_cams))
        print("  gallery  | {:5d} | {:11d} | {:9d}".format(num_gallery_pids, num_gallery_tracklets, num_gallery_cams))
        print("  -------------------------------------------")
        print("  number of images per tracklet: {} ~ {}, average {:.2f}".format(min_num, max_num, avg_num))
        print("  -------------------------------------------")

# class Dataset_new(object):
#     """An abstract class representing a Dataset.
#
#     This is the base class for ``ImageDataset`` and ``VideoDataset``.
#
#     Args:
#         train (list): contains tuples of (img_path(s), pid, camid).
#         query (list): contains tuples of (img_path(s), pid, camid).
#         gallery (list): contains tuples of (img_path(s), pid, camid).
#         transform: transform function.
#         mode (str): 'train', 'query' or 'gallery'.
#         combineall (bool): combines train, query and gallery in a
#             dataset for training.
#         verbose (bool): show information.
#     """
#     _junk_pids = [
#     ] # contains useless person IDs, e.g. background, false detections
#
#     def __init__(
#         self,
#         train,
#         query,
#         gallery,
#         transform=None,
#         mode='train',
#         combineall=False,
#         verbose=True,
#         **kwargs
#     ):
#         self.train = train
#         self.query = query
#         self.gallery = gallery
#         self.transform = transform
#         self.mode = mode
#         self.combineall = combineall
#         self.verbose = verbose
#
#         self.num_train_pids = self.get_num_pids(self.train)
#         self.num_train_cams = self.get_num_cams(self.train)
#
#         if self.combineall:
#             self.combine_all()
#
#         if self.mode == 'train':
#             self.data = self.train
#         elif self.mode == 'query':
#             self.data = self.query
#         elif self.mode == 'gallery':
#             self.data = self.gallery
#         else:
#             raise ValueError(
#                 'Invalid mode. Got {}, but expected to be '
#                 'one of [train | query | gallery]'.format(self.mode)
#             )
#
#         if self.verbose:
#             self.show_summary()
#
#     def __getitem__(self, index):
#         raise NotImplementedError
#
#     def __len__(self):
#         return len(self.data)
#
#     def __add__(self, other):
#         """Adds two datasets together (only the train set)."""
#         train = copy.deepcopy(self.train)
#
#         for img_path, pid, camid in other.train:
#             pid += self.num_train_pids
#             camid += self.num_train_cams
#             train.append((img_path, pid, camid))
#
#         ###################################
#         # Things to do beforehand:
#         # 1. set verbose=False to avoid unnecessary print
#         # 2. set combineall=False because combineall would have been applied
#         #    if it was True for a specific dataset, setting it to True will
#         #    create new IDs that should have been included
#         ###################################
#         if isinstance(train[0][0], str):
#             return ImageDataset_new(
#                 train,
#                 self.query,
#                 self.gallery,
#                 transform=self.transform,
#                 mode=self.mode,
#                 combineall=False,
#                 verbose=False
#             )
        # else:
        #     return VideoDataset(
        #         train,
        #         self.query,
        #         self.gallery,
        #         transform=self.transform,
        #         mode=self.mode,
        #         combineall=False,
        #         verbose=False,
        #         seq_len=self.seq_len,
        #         sample_method=self.sample_method
        #     )

    def __radd__(self, other):
        """Supports sum([dataset1, dataset2, dataset3])."""
        if other == 0:
            return self
        else:
            return self.__add__(other)

    def parse_data(self, data):
        """Parses data list and returns the number of person IDs
        and the number of camera views.

        Args:
            data (list): contains tuples of (img_path(s), pid, camid)
        """
        pids = set()
        cams = set()
        for _, pid, camid in data:
            pids.add(pid)
            cams.add(camid)
        return len(pids), len(cams)

    def get_num_pids(self, data):
        """Returns the number of training person identities."""
        return self.parse_data(data)[0]

    def get_num_cams(self, data):
        """Returns the number of training cameras."""
        return self.parse_data(data)[1]

    def show_summary(self):
        """Shows dataset statistics."""
        pass

    def combine_all(self):
        """Combines train, query and gallery in a dataset for training."""
        combined = copy.deepcopy(self.train)

        # relabel pids in gallery (query shares the same scope)
        g_pids = set()
        for _, pid, _ in self.gallery:
            if pid in self._junk_pids:
                continue
            g_pids.add(pid)
        pid2label = {pid: i for i, pid in enumerate(g_pids)}

        def _combine_data(data):
            for img_path, pid, camid in data:
                if pid in self._junk_pids:
                    continue
                pid = pid2label[pid] + self.num_train_pids
                combined.append((img_path, pid, camid))

        _combine_data(self.query)
        _combine_data(self.gallery)

        self.train = combined
        self.num_train_pids = self.get_num_pids(self.train)

    def download_dataset(self, dataset_dir, dataset_url):
        """Downloads and extracts dataset.

        Args:
            dataset_dir (str): dataset directory.
            dataset_url (str): url to download dataset.
        """
        if osp.exists(dataset_dir):
            return

        if dataset_url is None:
            raise RuntimeError(
                '{} dataset needs to be manually '
                'prepared, please follow the '
                'document to prepare this dataset'.format(
                    self.__class__.__name__
                )
            )

        print('Creating directory "{}"'.format(dataset_dir))
        mkdir_if_missing(dataset_dir)
        fpath = osp.join(dataset_dir, osp.basename(dataset_url))

        print(
            'Downloading {} dataset to "{}"'.format(
                self.__class__.__name__, dataset_dir
            )
        )
        # download_url(dataset_url, fpath)

        print('Extracting "{}"'.format(fpath))
        try:
            tar = tarfile.open(fpath)
            tar.extractall(path=dataset_dir)
            tar.close()
        except:
            zip_ref = zipfile.ZipFile(fpath, 'r')
            zip_ref.extractall(dataset_dir)
            zip_ref.close()

        print('{} dataset is ready'.format(self.__class__.__name__))

    def check_before_run(self, required_files):
        """Checks if required files exist before going deeper.

        Args:
            required_files (str or list): string file name(s).
        """
        if isinstance(required_files, str):
            required_files = [required_files]

        for fpath in required_files:
            if not osp.exists(fpath):
                raise RuntimeError('"{}" is not found'.format(fpath))

    def __repr__(self):
        num_train_pids, num_train_cams = self.parse_data(self.train)
        num_query_pids, num_query_cams = self.parse_data(self.query)
        num_gallery_pids, num_gallery_cams = self.parse_data(self.gallery)

        msg = '  ----------------------------------------\n' \
              '  subset   | # ids | # items | # cameras\n' \
              '  ----------------------------------------\n' \
              '  train    | {:5d} | {:7d} | {:9d}\n' \
              '  query    | {:5d} | {:7d} | {:9d}\n' \
              '  gallery  | {:5d} | {:7d} | {:9d}\n' \
              '  ----------------------------------------\n' \
              '  items: images/tracklets for image/video dataset\n'.format(
              num_train_pids, len(self.train), num_train_cams,
              num_query_pids, len(self.query), num_query_cams,
              num_gallery_pids, len(self.gallery), num_gallery_cams
              )

        return msg


from typing import Callable

from client.controllers.base_page_controller import BasePageController
from client.views.home_button_page_view import HomeButtonPageView

class HomeButtonPageController(BasePageController):
    def __init__(
        self,
        go_home_callback: Callable[[], None],
    ) -> None:
        """
        Specific type of page controller with a home button already handled.
        Many pages could have a home button, so this removes duplicate code.

        :param go_home_callback: Callback to call when going to the home screen
        """
        super().__init__()
        self._task_execute_dict["home_button"] = self.__execute_task_home_button
        self._go_home_callback: Callable[[], None] = go_home_callback

    def handle_home_button(self) -> None:
        """
        Handles home button action from the user by queueing task
        """
        self.queue(task_name="home_button")

    def __execute_task_home_button(self) -> None:
        """
        Takes action on the home button by notifying callback
        """
        self._go_home_callback()


import json
import random
from datetime import datetime

from dateutil import relativedelta
from interactions import *
from interactions.api.events import Component

import Data.capsule_characters as chars
import database
from Utilities.bot_icons import loading
from Utilities.fancysend import *

class Nikogotchi:
    def __init__(self, name: str, immortal: bool, rarity: int, status: int, emoji: int, health: float, hunger: float,
                 attention: float, cleanliness: float, pancake_dialogue: list[str], pet_dialogue: list[str],
                 cleaned_dialogue: list[str], last_interacted: datetime):
        self.name = name
        self.immortal = immortal
        self.rarity = rarity
        self.status = status
        self.emoji = emoji
        self.health = health
        self.hunger = hunger
        self.attention = attention
        self.cleanliness = cleanliness
        self.pancake_dialogue = pancake_dialogue
        self.pet_dialogue = pet_dialogue
        self.cleaned_dialogue = cleaned_dialogue
        self.last_interacted = last_interacted

class Command(Extension):

    async def get_nikogotchi(self, uid: int):
        data = database.fetch('nikogotchi_data', 'data', uid)

        if data is None:
            return None

        return Nikogotchi(
            name=data['name'],
            immortal=data['immortal'],
            rarity=data['rarity'],
            status=data['status'],
            emoji=data['emoji'],
            health=data['health'],
            hunger=data['hunger'],
            attention=data['attention'],
            cleanliness=data['cleanliness'],
            pancake_dialogue=data['pancake_dialogue'],
            pet_dialogue=data['pet_dialogue'],
            cleaned_dialogue=data['cleaned_dialogue'],
            last_interacted=datetime.strptime(data['last_interacted'], '%Y-%m-%d %H:%M:%S'),
        )

    async def save_nikogotchi(self, nikogotchi: Nikogotchi, uid: int):
        data = json.dumps({
            'name': nikogotchi.name,
            'immortal': nikogotchi.immortal,
            'rarity': nikogotchi.rarity,
            'status': nikogotchi.status,
            'emoji': nikogotchi.emoji,
            'health': nikogotchi.health,
            'hunger': nikogotchi.hunger,
            'attention': nikogotchi.attention,
            'cleanliness': nikogotchi.cleanliness,
            'pancake_dialogue': nikogotchi.pancake_dialogue,
            'pet_dialogue': nikogotchi.pet_dialogue,
            'cleaned_dialogue': nikogotchi.cleaned_dialogue,
            'last_interacted': nikogotchi.last_interacted.strftime('%Y-%m-%d %H:%M:%S'),
        })

        database.update('nikogotchi_data', 'data', uid, data)

    buttons = [
        Button(
            style=ButtonStyle.SUCCESS,
            label='Feed',
            custom_id=f'feed'
        ),
        Button(
            style=ButtonStyle.SUCCESS,
            label='Pet',
            custom_id=f'pet'
        ),
        Button(
            style=ButtonStyle.SUCCESS,
            label='Clean',
            custom_id=f'clean'
        ),
        Button(
            style=ButtonStyle.PRIMARY,
            label='Find Treasure',
            custom_id=f'find_treasure'
        ),
        Button(
            style=ButtonStyle.GREY,
            emoji=PartialEmoji(id=1147696088250335303),
            custom_id=f'refresh'
        )
    ]

    async def get_nikogotchi_age(self, uid: int):
        date_hatched_str = database.fetch('nikogotchi_data', 'hatched', uid)
        date_hatched = datetime.strptime(date_hatched_str, '%Y-%m-%d %H:%M:%S')

        return relativedelta.relativedelta(datetime.now(), date_hatched)

    async def get_main_nikogotchi_embed(self, uid: int, age: relativedelta.relativedelta, dialogue: str,
                                        found_treasure: list[int], n: Nikogotchi):
        progress_bar = {
            'empty': {
                'start': '<:thebeginningofthesong:1117957176724557824>',
                'middle': '<:themiddleofthesong:1117957179463438387>',
                'end': '<:theendofthesong:1117957159938961598>'
            },
            'filled': {
                'start': '<:thebeginningofthesong:1117957177987051530>',
                'middle': '<:themiddleofthesong:1117957181220864120>',
                'end': '<:theendofthesong:1117957174015041679>'
            }
        }

        progress_bar_length = 5

        health_value = round((n.health / 50) * progress_bar_length)
        hunger_value = round((n.hunger / 50) * progress_bar_length)
        attention_value = round((n.attention / 50) * progress_bar_length)
        cleanliness_value = round((n.cleanliness / 50) * progress_bar_length)

        health_progress_bar = ''
        hunger_progress_bar = ''
        attention_progress_bar = ''
        cleanliness_progress_bar = ''

        values = [health_value, hunger_value, attention_value, cleanliness_value]

        for index, value in enumerate(values):
            progress_bar_l = []
            for i in range(progress_bar_length):
                bar_section = 'middle'
                if i == 0:
                    bar_section = 'start'
                elif i == progress_bar_length - 1:
                    bar_section = 'end'

                if i < value:
                    bar_fill = progress_bar['filled'][bar_section]
                else:
                    bar_fill = progress_bar['empty'][bar_section]

                progress_bar_l.append(bar_fill)

            progress = ''.join(progress_bar_l)

            if index == 0:
                health_progress_bar = progress
            elif index == 1:
                hunger_progress_bar = progress
            elif index == 2:
                attention_progress_bar = progress
            elif index == 3:
                cleanliness_progress_bar = progress

        embed = Embed(
            title=n.name,
            color=0x8b00cc
        )

        nikogotchi_status = 'Your Nikogotchi seems to be doing okay.'

        if n.attention < 20:
            nikogotchi_status = f'ðŸ¤¨ {n.name} wants to be pet!'

        if n.cleanliness < 20:
            nikogotchi_status = f'ðŸ›€ {n.name} wants to be cleaned!'

        if n.hunger < 20:
            nikogotchi_status = f'ðŸ¥ž {n.name} is feeling hungry...'

        if n.status == 3:
            nikogotchi_status = f'{n.name} is currently finding treasures for you!'

        treasure_found = ''

        if len(found_treasure) > 0:

            treasures = ''
            looked_over_treasures = []

            get_treasure = database.get_treasures()

            for index in found_treasure:

                if index in looked_over_treasures:
                    continue

                treasure = get_treasure[index]

                treasures += f'<:any:{treasure["emoji"]}> {treasure["name"]} x{found_treasure.count(index)}\n'
                looked_over_treasures.append(index)

            treasure_found = f'''
            {n.name} found some treasures!\n\n{treasures}
            '''

        if n.health < 20:
            nikogotchi_status = f'ðŸš¨ {n.name} is at low health! Use Golden Pancakes to restore their health! ðŸš¨'

        embed.set_author(name=nikogotchi_status)

        description = f'''
        {treasure_found}\n
        â�¤ï¸�  {health_progress_bar} ({int(n.health)} / 50)\n\nðŸ�´  {hunger_progress_bar} ({n.hunger} / 50)\nðŸ«‚  {attention_progress_bar} ({n.attention} / 50)\nðŸ§½  {cleanliness_progress_bar} ({n.cleanliness} / 50)\n\nâ�°  ***{age.years}*** *years*, ***{age.months}*** *months*, ***{age.days}*** *days*
        '''

        embed.description = description

        embed.set_image(url=f'https://cdn.discordapp.com/emojis/{n.emoji}.png')
        embed.set_footer(text=dialogue)

        return embed

    @slash_command(description="All things about your Nikogotchi!")
    async def nikogotchi(self, ctx: SlashContext):
        pass

    @nikogotchi.subcommand(sub_cmd_description="Check out your Nikogotchi!")
    async def check(self, ctx: SlashContext):

        uid = ctx.author.id

        nikogotchi = await self.get_nikogotchi(uid)

        nikogotchi: Nikogotchi

        if nikogotchi is not None:
            await fancy_message(ctx, f'[ Loading Nikogotchi... {loading()} ]', ephemeral=True)

        else:
            is_available = database.fetch('nikogotchi_data', 'nikogotchi_available', uid)
            rarity = database.fetch('nikogotchi_data', 'rarity', uid)

            if is_available == 0:
                return await fancy_message(ctx,
                                           "[ You don't have a Nikogotchi! You can buy a capsule from the shop to unlock a random one! ]",
                                           ephemeral=True, color=0xff0000)

            viable_nikogotchi = []

            nikogotchi_list = chars.get_characters()

            for character in nikogotchi_list:
                if rarity == character.rarity.value:
                    viable_nikogotchi.append(character)

            selected_nikogotchi: chars.Nikogotchi = random.choice(viable_nikogotchi)

            owned_nikogotchi = database.fetch('user_data', 'unlocked_nikogotchis', ctx.author.id)
            owned_nikogotchi.append(nikogotchi_list.index(selected_nikogotchi))

            database.update('nikogotchi_data', 'data', ctx.author.id, json.dumps({
                'name': selected_nikogotchi.name,
                'emoji': selected_nikogotchi.emoji,
                'rarity': selected_nikogotchi.rarity.value,
                'status': 2,
                'immortal': False,
                'health': 50,
                'hunger': 50,
                'attention': 50,
                'cleanliness': 50,
                'pancake_dialogue': selected_nikogotchi.pancake_dialogue,
                'pet_dialogue': selected_nikogotchi.pet_dialogue,
                'cleaned_dialogue': selected_nikogotchi.cleaned_dialogue,
                'last_interacted': datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')
            }))

            nikogotchi = await self.get_nikogotchi(ctx.author.id)

            hatched_embed = Embed(
                title=f'You found {nikogotchi.name}!',
                color=0x8b00cc,
                description='Do you want to give them a new name?'
            )

            hatched_embed.set_footer(text='Dismiss this message and then rerun the command to keep the current name.')

            hatched_embed.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi.emoji}.png')

            buttons = [
                Button(style=ButtonStyle.GREEN, label='Yes', custom_id=f'yes {ctx.author.id}'),
                Button(style=ButtonStyle.RED, label='No', custom_id=f'no {ctx.author.id}')
            ]

            database.update('nikogotchi_data', 'nikogotchi_available', uid, is_available - 1)
            database.update('nikogotchi_data', 'hatched', uid, datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S'))

            await ctx.send(embed=hatched_embed, components=buttons, ephemeral=True)

            button: Component = await self.bot.wait_for_component(components=buttons)
            button_ctx = button.ctx

            custom_id = button_ctx.custom_id

            if custom_id == f'yes {ctx.author.id}':
                modal = Modal(
                    ShortText(
                        custom_id='name',
                        value=nikogotchi.name,
                        label='Name'
                    ),
                    custom_id='name',
                    title='Give your Nikogotchi a name!',
                )

                await button_ctx.send_modal(modal)

                modal_ctx: ModalContext = await self.bot.wait_for_modal(modal)

                await modal_ctx.defer(edit_origin=True)

                nikogotchi.name = modal_ctx.kwargs['name']
            else:
                await button_ctx.defer(edit_origin=True)

            await self.save_nikogotchi(nikogotchi, ctx.author.id)

        await self.load_nikogotchi(nikogotchi, ctx, uid)

    async def load_nikogotchi(self, nikogotchi: Nikogotchi, ctx: SlashContext, uid: int):

        age = await self.get_nikogotchi_age(int(ctx.author.id))

        last_interacted = nikogotchi.last_interacted

        # Get the current datetime
        current_time = datetime.now()

        # Calculate the time difference in hours
        time_difference = (current_time - last_interacted).total_seconds() / 7200

        nikogotchi.last_interacted = current_time

        modifier = 1

        if nikogotchi.status == 3:
            modifier = 2.5

        nikogotchi.hunger = int(max(0, nikogotchi.hunger - time_difference * modifier))
        nikogotchi.attention = int(max(0, nikogotchi.attention - time_difference * 1.25 * modifier))
        nikogotchi.cleanliness = int(max(0, nikogotchi.cleanliness - time_difference * 1.05 * modifier))

        if nikogotchi.immortal:
            nikogotchi.hunger = 9999
            nikogotchi.attention = 9999
            nikogotchi.cleanliness = 9999

        if nikogotchi.hunger <= 0 or nikogotchi.attention <= 0 or nikogotchi.cleanliness <= 0:
            nikogotchi.health = nikogotchi.health - time_difference

        if nikogotchi.health <= 0:
            embed = Embed(
                title=f'{nikogotchi.name} Passed away...',
                color=0x696969,
                description=f'''
                {nikogotchi.name} lived a full life of {age.years} years, {age.months} months, and {age.days} days.
                Hours since last taken care of: **{int(time_difference)} Hours**

                Nikogotchis rely on your love and attention, so keep trying to give them the best care possible!
                '''
            )

            buttons = []
            database.update('nikogotchi_data', 'data', ctx.author.id, None)
        else:
            embed = await self.get_main_nikogotchi_embed(uid, age, '...', [], nikogotchi)

            self.buttons[0].disabled = False
            self.buttons[1].disabled = False
            self.buttons[2].disabled = False

            self.buttons[3].label = 'Find Treasure'
            self.buttons[3].custom_id = 'find_treasure'

            buttons = self.buttons

            embed.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi.emoji}.png')

            if nikogotchi.status == 3:

                treasures_found = []

                for i in range(int(time_difference)):
                    if i % 4 == 0:
                        value = random.randint(0, 1000)
                        treasure = -1
                        if value > 0:
                            treasure = random.choice([0, 1, 2])
                        if value > 600:
                            treasure = random.choice([3, 4, 5])
                        if value > 900:
                            treasure = random.choice([6, 7, 8])

                        treasures_found.append(treasure)

                        user_treasures = database.fetch('nikogotchi_data', 'treasure', uid)
                        user_treasures[treasure] += 1
                        database.update('nikogotchi_data', 'treasure', uid, user_treasures)

                self.buttons[0].disabled = True
                self.buttons[1].disabled = True
                self.buttons[2].disabled = True

                self.buttons[3].label = 'Call Nikogotchi Back'
                self.buttons[3].custom_id = 'call_back'

                buttons = self.buttons

                embed = await self.get_main_nikogotchi_embed(uid, age, '...', treasures_found, nikogotchi)
                embed.set_image(url='')

        await self.save_nikogotchi(nikogotchi, uid)
        await ctx.edit(embed=embed, components=buttons)

    @component_callback('feed', 'pet', 'clean', 'find_treasure', 'refresh', 'call_back')
    async def nikogotchi_interaction(self, ctx: ComponentContext):

        await ctx.defer(edit_origin=True)

        pancakes = database.fetch('nikogotchi_data', 'pancakes', ctx.author.id)
        golden_pancakes = database.fetch('nikogotchi_data', 'golden_pancakes', ctx.author.id)
        glitched_pancakes = database.fetch('nikogotchi_data', 'glitched_pancakes', ctx.author.id)

        nikogotchi = await self.get_nikogotchi(ctx.author.id)

        custom_id = ctx.custom_id

        nikogotchi.last_interacted = datetime.now()

        dialogue = '...'

        buttons = self.buttons

        age = await self.get_nikogotchi_age(int(ctx.author.id))

        if nikogotchi.status == 2:
            if custom_id == 'feed':
                if pancakes <= 0:
                    dialogue = 'You don\'t have any pancakes! Buy some from the shop to feed your Nikogotchi!'
                else:
                    buttons = await self.feed_nikogotchi(pancakes, golden_pancakes, glitched_pancakes, ctx)

            if custom_id == 'pet':
                # Adjust hunger, attention, and cleanliness
                attention_increase = 20
                nikogotchi.attention = min(50, nikogotchi.attention + attention_increase)

                dialogue = random.choice(nikogotchi.pet_dialogue)

            if custom_id == 'clean':
                cleanliness_increase = 30
                nikogotchi.cleanliness = min(50, nikogotchi.cleanliness + cleanliness_increase)

                dialogue = random.choice(nikogotchi.cleaned_dialogue)

            if custom_id == 'find_treasure':
                dialogue = 'Your Nikogotchi is now finding treasure! Just be aware that their stats will decrease faster than normal, so make sure to call them back when they\'re done!'
                nikogotchi.status = 3

        if custom_id == 'call_back':
            nikogotchi.status = 2

        embed = await self.get_main_nikogotchi_embed(ctx.author.id, age, dialogue, [], nikogotchi)

        if not custom_id == 'feed':
            if nikogotchi.status == 2:
                self.buttons[0].disabled = False
                self.buttons[1].disabled = False
                self.buttons[2].disabled = False

                self.buttons[3].label = 'Find Treasure'
                self.buttons[3].custom_id = 'find_treasure'
            else:
                self.buttons[0].disabled = True
                self.buttons[1].disabled = True
                self.buttons[2].disabled = True

                self.buttons[3].label = 'Call Back'
                self.buttons[3].custom_id = 'call_back'

        embed.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi.emoji}.png')

        if nikogotchi.status == 3:

            treasures_found = []

            for i in range(int()):
                value = random.randint(0, 1000)
                treasure = -1
                if value > 0:
                    treasure = random.choice([0, 1, 2])
                if value > 600:
                    treasure = random.choice([3, 4, 5])
                if value > 900:
                    treasure = random.choice([6, 7, 8])

                treasures_found.append(treasure)

                user_treasures = database.fetch('user_data', 'treasures', ctx.author.id)
                user_treasures[treasure] += 1
                database.update('user_data', 'treasures', ctx.author.id, user_treasures)

            self.buttons[0].disabled = True
            self.buttons[1].disabled = True
            self.buttons[2].disabled = True

            self.buttons[3].label = 'Call Back'
            self.buttons[3].custom_id = 'call_back'

            buttons = self.buttons

            embed = await self.get_main_nikogotchi_embed(ctx.author.id, age, dialogue, treasures_found, nikogotchi)
            embed.set_image(url='')

        await self.save_nikogotchi(nikogotchi, ctx.author.id)
        await ctx.edit_origin(embed=embed, components=buttons)

    async def feed_nikogotchi(self, pancakes, golden_pancakes, glitched_pancakes, ctx):
        food_options = []

        nikogotchi = await self.get_nikogotchi(ctx.author.id)

        if glitched_pancakes > 0:
            food_options.append(
                StringSelectOption(
                    label=f'Feed ??? (x{glitched_pancakes})',
                    emoji=PartialEmoji(1152356972423819436),
                    value=f'pancake_glitched'
                )
            )

        if golden_pancakes > 0:
            food_options.append(
                StringSelectOption(
                    label=f'Feed Golden Pancake (x{golden_pancakes})',
                    emoji=PartialEmoji(1152330988022681821),
                    value=f'golden_pancake'
                )
            )

        if pancakes > 0:
            food_options.append(
                StringSelectOption(
                    label=f'Feed Pancake (x{pancakes})',
                    emoji=PartialEmoji(1147281411854839829),
                    value=f'pancake'
                )
            )

        select = StringSelectMenu(
            *food_options,
            custom_id='feed_food',
            placeholder=f'What do you want to feed {nikogotchi.name}?'
        )

        return spread_to_rows(select, Button(label='Cancel', style=ButtonStyle.RED, custom_id='refresh'))

    @component_callback('feed_food')
    async def feed_food(self, ctx: ComponentContext):

        await ctx.defer(edit_origin=True)

        nikogotchi = await self.get_nikogotchi(ctx.author.id)
        value = ctx.values[0]

        golden_pancakes = database.fetch('nikogotchi_data', 'golden_pancakes', ctx.author.id)
        pancakes = database.fetch('nikogotchi_data', 'pancakes', ctx.author.id)
        glitched_pancakes = database.fetch('nikogotchi_data', 'glitched_pancakes', ctx.author.id)

        hunger_increase = 0
        health_increase = 0

        if value == 'golden_pancake':
            if golden_pancakes <= 0:
                dialogue = 'You don\'t have any golden pancakes! Buy some from the shop to feed your Nikogotchi!'
            else:
                hunger_increase = 50
                health_increase = 25

                golden_pancakes = database.update('nikogotchi_data', 'golden_pancakes', ctx.author.id,
                                                  golden_pancakes - 1)
                dialogue = random.choice(nikogotchi.pancake_dialogue)
        elif value == 'pancake_glitched':
            if glitched_pancakes <= 0:
                dialogue = 'You don\'t have any â˜�. You don\'t have any â˜�. You don\'t have any â˜�. You don\'t have any â˜�. You don\'t have any â˜�. You don\'t have any â˜�. Do you?'
            else:
                hunger_increase = 9999
                health_increase = 9999
                glitched_pancakes = database.update('nikogotchi_data', 'glitched_pancakes', ctx.author.id,
                                                    glitched_pancakes - 1)
                nikogotchi.immortal = True
                dialogue = 'Your Nikogotchi is now Immortal.'
        else:
            if pancakes <= 0:
                dialogue = 'You don\'t have any pancakes! Buy some from the shop to feed your Nikogotchi!'
            else:
                hunger_increase = 25
                health_increase = 1

                pancakes = database.update('nikogotchi_data', 'pancakes', ctx.author.id, pancakes - 1)
                dialogue = random.choice(nikogotchi.pancake_dialogue)

        nikogotchi.hunger = min(50, nikogotchi.hunger + hunger_increase)
        nikogotchi.health = min(50, nikogotchi.health + health_increase)

        await self.save_nikogotchi(nikogotchi, ctx.author.id)

        buttons = await self.feed_nikogotchi(pancakes, golden_pancakes, glitched_pancakes, ctx)

        embed = await self.get_main_nikogotchi_embed(ctx.author.id, await self.get_nikogotchi_age(ctx.author.id),
                                                     dialogue, [], nikogotchi)

        await ctx.edit_origin(embed=embed, components=buttons)

    @nikogotchi.subcommand(sub_cmd_description='Send away your Nikogotchi.')
    async def send_away(self, ctx: SlashContext):

        nikogotchi = await self.get_nikogotchi(ctx.author.id)

        if nikogotchi is None:
            return await fancy_message(ctx, "[ You don't have a Nikogotchi! ]", ephemeral=True, color=0xff0000)

        buttons = [
            Button(style=ButtonStyle.RED, label='Yes', custom_id=f'rehome {ctx.author.id}')
        ]

        embed = await fancy_embed(
            f'[ Are you sure you want to send away {nikogotchi.name} so that you can adopt a new one? ]')
        embed.set_footer(text='Dismiss this message if you change your mind.')

        await fancy_message(ctx,
                            f'[ Are you sure you want to send away {nikogotchi.name} so that you can adopt a new one? ]',
                            ephemeral=True, components=buttons)

        button = await self.bot.wait_for_component(components=buttons)
        button_ctx = button.ctx

        custom_id = button_ctx.custom_id

        if custom_id == f'rehome {ctx.author.id}':
            database.update('nikogotchi_data', 'data', ctx.author.id, None)

            embed = await fancy_embed(f'[ Successfully sent away {nikogotchi.name}. Enjoy your future Nikogotchi! ]')

            await ctx.edit(embed=embed, components=[])

    @nikogotchi.subcommand(sub_cmd_description='Rename your Nikogotchi.')
    @slash_option('name', description='Rename your Nikogotchi.', opt_type=OptionType.STRING, required=True)
    async def rename(self, ctx: SlashContext, name):

        nikogotchi = await self.get_nikogotchi(ctx.author.id)

        if nikogotchi is None:
            return await fancy_message(ctx, "[ You don't have a Nikogotchi! ]", ephemeral=True, color=0xff0000)

        old_name = nikogotchi.name
        nikogotchi.name = name

        await self.save_nikogotchi(nikogotchi, ctx.author.id)
        await fancy_message(ctx, f'[ Successfully renamed **{old_name}** to **{nikogotchi.name}**! ]', ephemeral=True)

    @nikogotchi.subcommand(sub_cmd_description='Show off your Nikogotchi, or view someone else\'s.!')
    @slash_option('user', description='The user to view.', opt_type=OptionType.USER, required=True)
    async def show(self, ctx: SlashContext, user: User):

        uid = user.id

        nikogotchi = await self.get_nikogotchi(uid)

        if nikogotchi is None:
            return await fancy_message(ctx, "[ This person doesn't seem to have a Nikogotchi! ]", ephemeral=True,
                                       color=0xff0000)

        age = await self.get_nikogotchi_age(uid)

        embed = Embed(
            title=f'{nikogotchi.name}',
            color=0x8b00cc,
        )

        embed.author = EmbedAuthor(
            name=f'Owned by {user.username}',
            icon_url=user.avatar_url
        )

        embed.description = f'''
        Age: {age.years} years, {age.months} months, and {age.days} days.

        Health: {int(nikogotchi.health)}/50
        '''

        embed.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi.emoji}.png?v=1')

        await ctx.send(embed=embed)

    @nikogotchi.subcommand(sub_cmd_description='Trade your Nikogotchi with someone else!')
    @slash_option('user', description='The user to trade with.', opt_type=OptionType.USER, required=True)
    async def trade(self, ctx: SlashContext, user: User):

        nikogotchi_one = await self.get_nikogotchi(ctx.author.id)
        nikogotchi_two = await self.get_nikogotchi(user.id)

        if nikogotchi_one is None:
            return await fancy_message(ctx, "[ You don't have a Nikogotchi! ]", ephemeral=True, color=0xff0000)
        if nikogotchi_two is None:
            return await fancy_message(ctx, "[ This person doesn't have a Nikogotchi! ]", ephemeral=True,
                                       color=0xff0000)

        await fancy_message(ctx, f'[ Sent {user.mention} a trade offer! Waiting on their response... {loading()} ]',
                            ephemeral=True)

        uid = user.id

        embed = await fancy_embed(f'''
        ### **{ctx.author.mention}** wants to trade their Nikogotchi with you!

        **Name:** {nikogotchi_one.name}
        **Health:** {nikogotchi_one.health}/50

        Do you want to trade <:dfd:{nikogotchi_two.emoji}> **{nikogotchi_two.name}** with them?
        ''')

        embed.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi_one.emoji}.png?v=1')

        buttons = [
            Button(style=ButtonStyle.SUCCESS, label='Trade', custom_id=f'trade {ctx.author.id} {uid}'),
            Button(style=ButtonStyle.DANGER, label='Decline', custom_id=f'decline {ctx.author.id} {uid}')
        ]

        await user.send(embed=embed, components=buttons)

        button = await self.bot.wait_for_component(components=buttons)
        button_ctx = button.ctx

        await button_ctx.defer(edit_origin=True)

        custom_id = button_ctx.custom_id

        if custom_id == f'trade {ctx.author.id} {uid}':
            await self.save_nikogotchi(nikogotchi_two, ctx.author.id)
            await self.save_nikogotchi(nikogotchi_one, uid)

            embed_two = await fancy_embed(
                f'[ Successfully traded with {user.mention}! Say hello to **{nikogotchi_two.name}**! ]')
            embed_two.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi_two.emoji}.png?v=1')

            embed_one = await fancy_embed(
                f'[ Successfully traded with **{ctx.author.username}**! Say hello to **{nikogotchi_one.name}**! ]')
            embed_one.set_image(url=f'https://cdn.discordapp.com/emojis/{nikogotchi_one.emoji}.png?v=1')

            await button_ctx.edit_origin(embed=embed_one, components=[])
            await ctx.edit(embed=embed_two)
        else:
            embed = await fancy_embed(f'[ {user.mention} declined your trade offer. ]')
            await ctx.edit(embed=embed)

            embed = await fancy_embed(f'[ Successfully declined trade offer. ]')

            await button_ctx.edit_origin(embed=embed, components=[])

    @slash_command(description='View the treasure you currently have, or someone else\'s!')
    @slash_option('user', description='The user to view.', opt_type=OptionType.USER, required=True)
    async def treasure(self, ctx: SlashContext, user: User):
        embed = Embed(
            title=f'{user.username}\'s Treasure',
            color=0x8b00cc,
        )

        treasures = database.get_treasures()

        treasure_string = ''

        user_treasure = database.fetch('nikogotchi_data', 'treasure', user.id)

        for i, item in enumerate(treasures):
            treasure_string += f'<:emoji:{item["image"]}> {item["name"]}: **{user_treasure[i]}x**\n\n'

        embed.description = f'''
        Here is {user.mention}'s treasure!

        {treasure_string}
        Earn more treasure through Nikogotchis or purchasing them from the shop!
        '''

        await ctx.send(embed=embed)


from conans import ConanFile, CMake

class JSONSettingsRESTAPITestConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "cmake_find_package"
    options = {"boost": ["1.66.0", "1.67.0", "1.72.0"], "openssl": ["1.0.2n", "1.0.2s", "1.1.1g"], "gtest": ["1.7.0", "1.8.1", "1.10.0"]}
    default_options = {"boost":"1.72.0", "openssl": "1.1.1g", "gtest":"1.10.0"}
    required_conan_version = ">=1.33.1"

    def configure(self):
        self.options["JSONSettingsRESTAPI"].boost = self.options.boost
        self.options["JSONSettingsRESTAPI"].openssl = self.options.openssl
        self.options["JSONSettingsRESTAPI"].gtest = self.options.gtest
        self.options["RapidJSONAdapter"].gtest = self.options.gtest

    def requirements(self):
        self.requires("RapidJSONAdapter/1.1.6@systelab/stable")

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def imports(self):
        self.copy("*.dll", dst=("bin/%s" % self.settings.build_type), src="bin")
        self.copy("*.dll", dst=("bin/%s" % self.settings.build_type), src="lib")
        self.copy("*.dylib*", dst="bin", src="lib")
        self.copy('*.so*', dst='bin', src='lib')

    def test(self):
        cmake = CMake(self)
        cmake.test()