from __future__ import print_function

import argparse
import os
import sys
import random
import warnings
import ignite

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

from ignite.contrib.handlers import ProgressBar
from ignite.engine import Engine, Events
from ignite.handlers import ModelCheckpoint, Timer
from ignite.metrics import RunningAverage

try:
    import torchvision
    import torchvision.datasets as dset
    import torchvision.transforms as transforms
    import torchvision.utils as vutils

except ImportError:
    raise ImportError("Please install torchvision to run this example, for example "
                      "via conda by running 'conda install -c pytorch torchvision'. ")

PRINT_FREQ = 100
FAKE_IMG_FNAME = 'fake_sample_epoch_{:04d}.png'
REAL_IMG_FNAME = 'real_sample_epoch_{:04d}.png'
LOGS_FNAME = 'logs.tsv'
PLOT_FNAME = 'plot.svg'
SAMPLES_FNAME = 'samples.svg'
CKPT_PREFIX = 'networks'

class Net(nn.Module):
    """ A base class for both generator and the discriminator.
    Provides a common weight initialization scheme.

    """

    def weights_init(self):
        for m in self.modules():
            classname = m.__class__.__name__

            if 'Conv' in classname:
                m.weight.data.normal_(0.0, 0.02)

            elif 'BatchNorm' in classname:
                m.weight.data.normal_(1.0, 0.02)
                m.bias.data.fill_(0)

    def forward(self, x):
        return x

class Generator(Net):
    """ Generator network.

    Args:
        nf (int): Number of filters in the second-to-last deconv layer
    """

    def __init__(self, z_dim, nf):
        super(Generator, self).__init__()

        self.net = nn.Sequential(

            # input is Z, going into a convolution
            nn.ConvTranspose2d(in_channels=z_dim, out_channels=nf * 8, kernel_size=4, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(nf * 8),
            nn.ReLU(inplace=True),

            # state size. (nf*8) x 4 x 4
            nn.ConvTranspose2d(in_channels=nf * 8, out_channels=nf * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf * 4),
            nn.ReLU(inplace=True),

            # state size. (nf*4) x 8 x 8
            nn.ConvTranspose2d(in_channels=nf * 4, out_channels=nf * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf * 2),
            nn.ReLU(inplace=True),

            # state size. (nf*2) x 16 x 16
            nn.ConvTranspose2d(in_channels=nf * 2, out_channels=nf, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf),
            nn.ReLU(inplace=True),

            # state size. (nf) x 32 x 32
            nn.ConvTranspose2d(in_channels=nf, out_channels=3, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh()

            # state size. (nc) x 64 x 64
        )

        self.weights_init()

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

class Discriminator(Net):
    """ Discriminator network.

    Args:
        nf (int): Number of filters in the first conv layer.
    """

    def __init__(self, nf):
        super(Discriminator, self).__init__()

        self.net = nn.Sequential(

            # input is (nc) x 64 x 64
            nn.Conv2d(in_channels=3, out_channels=nf, kernel_size=4, stride=2, padding=1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),

            # state size. (nf) x 32 x 32
            nn.Conv2d(in_channels=nf, out_channels=nf * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf * 2),
            nn.LeakyReLU(0.2, inplace=True),

            # state size. (nf*2) x 16 x 16
            nn.Conv2d(in_channels=nf * 2, out_channels=nf * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf * 4),
            nn.LeakyReLU(0.2, inplace=True),

            # state size. (nf*4) x 8 x 8
            nn.Conv2d(in_channels=nf * 4, out_channels=nf * 8, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(nf * 8),
            nn.LeakyReLU(0.2, inplace=True),

            # state size. (nf*8) x 4 x 4
            nn.Conv2d(in_channels=nf * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),
            nn.Sigmoid()
        )

        self.weights_init()

    def forward(self, x):
        output = self.net(x)
        return output.view(-1, 1).squeeze(1)

def check_manual_seed(seed):
    """ If manual seed is not specified, choose a random one and communicate it to the user.

    """

    seed = seed or random.randint(1, 10000)
    random.seed(seed)
    torch.manual_seed(seed)

    print('Using manual seed: {seed}'.format(seed=seed))

def to_rgb(img):
    return img.convert('RGB')

def check_dataset(dataset, dataroot):
    """

    Args:
        dataset (str): Name of the dataset to use. See CLI help for details
        dataroot (str): root directory where the dataset will be stored.

    Returns:
        dataset (data.Dataset): torchvision Dataset object

    """
    # to_rgb = transforms.Lambda(lambda img: img.convert('RGB'))
    resize = transforms.Resize(64)
    crop = transforms.CenterCrop(64)
    to_tensor = transforms.ToTensor()
    normalize = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

    if dataset in {'imagenet', 'folder', 'lfw'}:
        dataset = dset.ImageFolder(root=dataroot, transform=transforms.Compose([resize,
                                                                                crop,
                                                                                to_tensor,
                                                                                normalize]))

    elif dataset == 'lsun':
        dataset = dset.LSUN(root=dataroot, classes=['bedroom_train'], transform=transforms.Compose([resize,
                                                                                crop,
                                                                                to_tensor,
                                                                                normalize]))

    elif dataset == 'cifar10':
        dataset = dset.CIFAR10(root=dataroot, download=True, transform=transforms.Compose([resize,
                                                                                to_tensor,
                                                                                normalize]))

    elif dataset == 'mnist':
        dataset = dset.MNIST(root=dataroot, download=True, transform=transforms.Compose([
            to_rgb,
            resize,
            to_tensor,
            normalize]))

    elif dataset == 'fake':
        dataset = dset.FakeData(size=256, image_size=(3, 64, 64), transform=to_tensor)

    else:
        raise RuntimeError("Invalid dataset name: {}".format(dataset))

    return dataset

def main(dataset, dataroot,
         z_dim, g_filters, d_filters,
         batch_size, epochs,
         learning_rate, beta_1,
         saved_G, saved_D,
         seed,
         n_workers, device,
         alpha, output_dir):

    # seed
    check_manual_seed(seed)

    # netowrks
    netG = Generator(z_dim, g_filters).to(device)
    netD = Discriminator(d_filters).to(device)

    # criterion
    bce = nn.BCELoss()

    # optimizers
    optimizerG = optim.Adam(netG.parameters(), lr=learning_rate, betas=(beta_1, 0.999))
    optimizerD = optim.Adam(netD.parameters(), lr=learning_rate, betas=(beta_1, 0.999))

    # data
    dataset = check_dataset(dataset, dataroot)
    loader = data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=n_workers, drop_last=True)

    # load pre-trained models
    if saved_G:
        netG.load_state_dict(torch.load(saved_G))

    if saved_D:
        netD.load_state_dict(torch.load(saved_D))

    # misc
    real_labels = torch.ones(batch_size, device=device)
    fake_labels = torch.zeros(batch_size, device=device)
    fixed_noise = torch.randn(batch_size, z_dim, 1, 1, device=device)

    def get_noise():
        return torch.randn(batch_size, z_dim, 1, 1, device=device)

    # The main function, processing a batch of examples
    def step(engine, batch):

        # unpack the batch. It comes from a dataset, so we have <images, labels> pairs. Discard labels.
        real, _ = batch
        real = real.to(device)

        # -----------------------------------------------------------
        # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
        netD.zero_grad()

        # train with real
        output = netD(real)
        errD_real = bce(output, real_labels)
        D_x = output.mean().item()

        errD_real.backward()

        # get fake image from generator
        noise = get_noise()
        fake = netG(noise)

        # train with fake
        output = netD(fake.detach())
        errD_fake = bce(output, fake_labels)
        D_G_z1 = output.mean().item()

        errD_fake.backward()

        # gradient update
        errD = errD_real + errD_fake
        optimizerD.step()

        # -----------------------------------------------------------
        # (2) Update G network: maximize log(D(G(z)))
        netG.zero_grad()

        # Update generator. We want to make a step that will make it more likely that discriminator outputs "real"
        output = netD(fake)
        errG = bce(output, real_labels)
        D_G_z2 = output.mean().item()

        errG.backward()

        # gradient update
        optimizerG.step()

        return {
            'errD': errD.item(),
            'errG': errG.item(),
            'D_x': D_x,
            'D_G_z1': D_G_z1,
            'D_G_z2': D_G_z2
        }

    # ignite objects
    trainer = Engine(step)
    checkpoint_handler = ModelCheckpoint(output_dir, CKPT_PREFIX, save_interval=1, n_saved=10, require_empty=False)
    timer = Timer(average=True)

    # attach running average metrics
    monitoring_metrics = ['errD', 'errG', 'D_x', 'D_G_z1', 'D_G_z2']
    RunningAverage(alpha=alpha, output_transform=lambda x: x['errD']).attach(trainer, 'errD')
    RunningAverage(alpha=alpha, output_transform=lambda x: x['errG']).attach(trainer, 'errG')
    RunningAverage(alpha=alpha, output_transform=lambda x: x['D_x']).attach(trainer, 'D_x')
    RunningAverage(alpha=alpha, output_transform=lambda x: x['D_G_z1']).attach(trainer, 'D_G_z1')
    RunningAverage(alpha=alpha, output_transform=lambda x: x['D_G_z2']).attach(trainer, 'D_G_z2')

    # attach progress bar
    pbar = ProgressBar()
    pbar.attach(trainer, metric_names=monitoring_metrics)

    @trainer.on(Events.ITERATION_COMPLETED)
    def print_logs(engine):
        if (engine.state.iteration - 1) % PRINT_FREQ == 0:
            fname = os.path.join(output_dir, LOGS_FNAME)
            columns = engine.state.metrics.keys()
            values = [str(round(value, 5)) for value in engine.state.metrics.values()]

            with open(fname, 'a') as f:
                if f.tell() == 0:
                    print('\t'.join(columns), file=f)
                print('\t'.join(values), file=f)

            message = '[{epoch}/{max_epoch}][{i}/{max_i}]'.format(epoch=engine.state.epoch,
                                                                  max_epoch=epochs,
                                                                  i=(engine.state.iteration % len(loader)),
                                                                  max_i=len(loader))
            for name, value in zip(columns, values):
                message += ' | {name}: {value}'.format(name=name, value=value)

            pbar.log_message(message)

    # adding handlers using `trainer.on` decorator API
    @trainer.on(Events.EPOCH_COMPLETED)
    def save_fake_example(engine):
        fake = netG(fixed_noise)
        path = os.path.join(output_dir, FAKE_IMG_FNAME.format(engine.state.epoch))
        vutils.save_image(fake.detach(), path, normalize=True)

    # adding handlers using `trainer.on` decorator API
    @trainer.on(Events.EPOCH_COMPLETED)
    def save_real_example(engine):
        img, y = engine.state.batch
        path = os.path.join(output_dir, REAL_IMG_FNAME.format(engine.state.epoch))
        vutils.save_image(img, path, normalize=True)

    # adding handlers using `trainer.add_event_handler` method API
    trainer.add_event_handler(event_name=Events.EPOCH_COMPLETED, handler=checkpoint_handler,
                              to_save={
                                  'netG': netG,
                                  'netD': netD
                              })

    # automatically adding handlers via a special `attach` method of `Timer` handler
    timer.attach(trainer, start=Events.EPOCH_STARTED, resume=Events.ITERATION_STARTED,
                 pause=Events.ITERATION_COMPLETED, step=Events.ITERATION_COMPLETED)

    # adding handlers using `trainer.on` decorator API
    @trainer.on(Events.EPOCH_COMPLETED)
    def print_times(engine):
        pbar.log_message('Epoch {} done. Time per batch: {:.3f}[s]'.format(engine.state.epoch, timer.value()))
        timer.reset()

    # adding handlers using `trainer.on` decorator API
    @trainer.on(Events.EPOCH_COMPLETED)
    def create_plots(engine):
        try:
            import matplotlib as mpl
            mpl.use('agg')

            import numpy as np
            import pandas as pd
            import matplotlib.pyplot as plt

        except ImportError:
            warnings.warn('Loss plots will not be generated -- pandas or matplotlib not found')

        else:
            pass
            # df = pd.read_csv(os.path.join(output_dir, LOGS_FNAME), delimiter='\t')
            # x = np.arange(1, engine.state.iteration + 1, PRINT_FREQ)
            # _ = df.plot(x=x, subplots=True, figsize=(20, 20))
            # _ = plt.xlabel('Iteration number')
            # fig = plt.gcf()
            # path = os.path.join(output_dir, PLOT_FNAME)
            #
            # fig.savefig(path)

    # adding handlers using `trainer.on` decorator API
    @trainer.on(Events.EXCEPTION_RAISED)
    def handle_exception(engine, e):
        if isinstance(e, KeyboardInterrupt) and (engine.state.iteration > 1):
            engine.terminate()
            warnings.warn('KeyboardInterrupt caught. Exiting gracefully.')

            create_plots(engine)
            checkpoint_handler(engine, {
                'netG_exception': netG,
                'netD_exception': netD
            })

        else:
            raise e

    # Setup is done. Now let's run the training
    trainer.run(loader, epochs)

if __name__ == '__main__':
    dev = 'cuda:0'

    output_dir = '../data/dcgan/'

    # os.makedirs(output_dir)
    print('torch version', torch.__version__)
    print('torchvision version', torchvision.__version__)
    print('ignite version, ', ignite.__version__)
    print(sys.version)

    main(dataset='mnist', dataroot='data/dcgan/',
         z_dim=100, g_filters=64, d_filters=64,
         batch_size=64, epochs=2500,
         learning_rate=0.0002, beta_1=0.5,
         saved_D=None, saved_G=None,
         seed=42,
         device=dev, n_workers=4,
         alpha=0.98, output_dir=output_dir)


import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model

import time
from tqdm import tqdm

class MyModel(Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.conv1 = Conv2D(32, 3, activation='relu')
        self.flatten = Flatten()
        self.d1 = Dense(128, activation='relu')
        self.d2 = Dense(10, activation='softmax')

    def call(self, x):
        x = self.conv1(x)
        x = self.flatten(x)
        x = self.d1(x)
        return self.d2(x)

class EagerTrainer(object):
    def __init__(self):
        self.model = MyModel()
        self.loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
        self.optimizer = tf.keras.optimizers.Adam()

        self.train_loss = tf.keras.metrics.Mean(name='train_loss')
        self.train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

        self.test_loss = tf.keras.metrics.Mean(name='test_loss')
        self.test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

    @tf.function
    def train_step(self, image, label):
        with tf.GradientTape() as tape:
            predictions = self.model(image)
            loss = self.loss_object(label, predictions)
        gradients = tape.gradient(loss, self.model.trainable_variables)
        self.optimizer.apply_gradients(zip(gradients, self.model.trainable_variables))

        self.train_loss(loss)
        self.train_accuracy(label, predictions)

    @tf.function
    def test_step(self, image, label):
        predictions = self.model(image)
        t_loss = self.loss_object(label, predictions)

        self.test_loss(t_loss)
        self.test_accuracy(label, predictions)

    def train(self, epochs, training_data, test_data):
        template = 'Epoch {}, Loss: {:.5f}, Accuracy: {:.5f}, Test Loss: {:.5f}, Test Accuracy: {:.5f}, elapsed_time {:.5f}'

        for epoch in range(epochs):
            start = time.time()
            for image, label in tqdm(training_data):
                self.train_step(image, label)
            elapsed_time = time.time() - start

            for test_image, test_label in test_data:
                self.test_step(test_image, test_label)

            print(template.format(epoch + 1,
                                  self.train_loss.result(),
                                  self.train_accuracy.result() * 100,
                                  self.test_loss.result(),
                                  self.test_accuracy.result() * 100,
                                  elapsed_time))

class KerasTrainer(object):
    def __init__(self):
        self.model = MyModel()
        self.model.compile(optimizer=tf.keras.optimizers.Adam(),
                           loss=tf.keras.losses.SparseCategoricalCrossentropy(),
                           metrics=['accuracy'])

    def train(self, epochs, training_data, test_data):
        self.model.fit(training_data, epochs=epochs, validation_data=test_data)


import pandas as pd

from data_preprocessing.data_encoder import DataEncoder
from data_preprocessing.data_eraser import DataEraser

def main():
    """
    main function responsible for running data deletion and data encoding methods before
    using clustering methods and saving the processed data onto designated csv file
    :return: None
    """
    diabetic_data = pd.read_csv('data/raw_data/diabetic_data.csv')
    diabetic_data = DataEraser(diabetic_data).delete_unnecessary_data()
    diabetic_data = DataEncoder(diabetic_data).encode_data()

    diabetic_data.to_csv("data/processed_data/latest_for_clustering_modified_data.csv", index=False)

if __name__ == '__main__':
    main()


"""
Input: nums = [1,3,5,6], target = 5
Output: 2
Example 2:

Input: nums = [1,3,5,6], target = 2
Output: 1
Example 3:

Input: nums = [1,3,5,6], target = 7
Output: 4
Example 4:

Input: nums = [1,3,5,6], target = 0
Output: 0
Example 5:

Input: nums = [1], target = 0
Output: 0
"""

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        ini=0
        last= len (nums)-1
        count = 0
        #for i in range ((last)-1):
        if target < nums[ini]:
            return 0

        while ini <= last:
            if nums[ini] == target:
                return ini

            elif nums[ini] < target:
                ini += 1

            elif nums[ini] > target:
                return ini
        return ini


import requests

# Test creating number of players
url = 'http://127.0.0.1:5000/numberofplayers'
data = {
    'number_of_players': 4
}
response = requests.post(url, json=data)
assert response.status_code == 201
assert response.json()['message'] == 'Player count 4 created successfully.'

# Test creating player
url = 'http://127.0.0.1:5000/players'
data = {
    'name': 'Alice',
    'colour': 'red'
}
response = requests.post(url, json=data)
assert response.status_code == 201
assert response.json()['message'] == 'Player Alice created successfully.'

# Test getting players
url = 'http://127.0.0.1:5000/players'
response = requests.get(url)
assert response.status_code == 200
assert len(response.json()) == 1
assert response.json()[0]['name'] == 'Alice'
assert response.json()[0]['colour'] == 'red'
print(response.json()[0]['name'])


from coffee_machine_menu import MENU, resources

def refill_water():
    """Refills the water tank.
        Prints, it is full, if the water tank is full."""
    if resources["water"] < 300:
        resources["water"] = 300
    else:
        print("Water tank is full!")

def refill_milk():
    """Refills the milk tank.
        Prints, it is full, if the milk tank is full."""
    if resources["milk"] < 200:
        resources["milk"] = 200
    else:
        print("Milk tank is full!")

def refill_coffee():
    """Refills the coffee tank.
        Prints, it is full, if the coffee tank is full."""
    if resources["coffee"] < 100:
        resources["coffee"] = 100
    else:
        print("Coffee tank is full!")

def report():
    """Print the resources left in coffee machine"""
    print(f"Water: {resources['water']}")
    print(f"Milk: {resources['milk']}")
    print(f"Coffee: {resources['coffee']}")
    print(f"Money: {resources['money']}")

def format_input(user_input):
    """Convert the integer input into requested order.
        If input is 1 convert it to 'espresso'
        2 will be 'cappuccino'
        3 is going to be 'latte'."""
    if user_input == 1:
        return "espresso"
    elif user_input == 2:
        return "cappuccino"
    elif user_input == 3:
        return "latte"
    else:
        print("Input is invalid.")

def money_taker():
    print("Please insert coins")
    total_money_inserted = int(input("How many quarters?: ")) * 0.01
    total_money_inserted += int(input("How many dimes?: ")) * 0.1
    total_money_inserted += int(input("How many nickles?: ")) * 0.05
    total_money_inserted += int(input("How many pennies?: ")) * 0.25
    return total_money_inserted

def make_coffee(order):
    """Decreases the amount of ingredients for prepared order.
            At the end prints the order."""
    for ingredient in MENU[order]["ingredients"]:
        resources[ingredient] -= MENU[order]["ingredients"][ingredient]
    print(f"Here is your {order}. Enjoy!")

def money_calculator(order):
    """Take inserted money and order as input.
        If the inserted money >= cost of the order, print the prepared object and change amount.
        If money is not enough prints it's not enough."""
    order = format_input(user_input=order)
    cost = MENU[order]["cost"]
    total_money_inserted = money_taker()
    change = round(total_money_inserted - cost, 2)
    if total_money_inserted < cost:
        print("Sorry that's not enough money. Money refunded.")
    else:
        resources["money"] += cost
        print(f"Here is ${change} in change.")
        make_coffee(order=order)

def is_resource_sufficient(order):
    """Checks if the resources for chosen order is enough.
        Returns True if it's enough.
        Returns False if it's not enough."""
    order = format_input(user_input=order)
    for ingredient in MENU[order]["ingredients"]:
        if resources[ingredient] < MENU[order]["ingredients"][ingredient]:
            print(f"Sorry there is not enough {ingredient} for {order}")
            return False
    return True

def check_wish(user_input):
    """Takes user input and calls the function according to that wish.
        If user wishes to close the machine with integer input 8, returns False.
        Otherwise, do the wished action and returns True."""
    if user_input == 8:
        return False
    elif user_input == 7:
        report()
    elif user_input == 4:
        refill_water()
    elif user_input == 5:
        refill_milk()
    elif user_input == 6:
        refill_coffee()
    elif is_resource_sufficient(order=user_input):
        money_calculator(order=user_input)
    return True

state_on = True

while state_on:
    print("Hello!")
    print("1- Make espresso.")
    print("2- Make cappuccino.")
    print("3- Make latte.")
    print("4- Refill water.")
    print("5- Refill milk.")
    print("6- Refill coffee.")
    print("7- Report the resources.")
    print("8- Close the machine.")
    wish = int(input("What would you like to do?: "))
    state_on = check_wish(user_input=wish)

print("See you later, have a nice day!")


class Student:
    def __init__(self, name, surname, gender):
        self.name = name
        self.surname = surname
        self.gender = gender
        self.finished_courses = []
        self.courses_in_progress = []
        self.grades = {}

    def __str__(self):
        return f"Имя: {self.name} \nФамилия: {self.surname} \nСредняя оценка за лекции {self.avg_rate()} " \
               f"\nКурсы в процессе изучения: {', '.join(self.courses_in_progress)}"

    def avg_rate(self):
        tmp = []
        for i in self.grades:
            tmp += self.grades[i]
        avg = sum(tmp) / len(tmp)
        return avg

    def add_raring_lecturer(self, lecturer, course, grade):
        if isinstance(lecturer, Lecturer):
            if course in lecturer.courses_attached:
                if grade <= 10 and grade > 0:
                    if course in lecturer.grades:
                        lecturer.grades[course] += [grade]
                    else:
                        lecturer.grades[course] = [grade]
                else:
                    return 'Значение оценки должно быть от 0 до 10'
            else:
                return 'Лектор не ведет этот курс'
        else:
            return 'Ошибка'

    def __eq__(self, other):
        if self.avg_rate() > other.avg_rate():
            return f"Имя: {self.name} балл: {self.avg_rate()}"
        elif self.avg_rate() < other.avg_rate():
            return f"Имя: {other.name} балл: {other.avg_rate()}"
        else:
            return f"Баллы одинаковые у {self.name} и {other.name}"

class Mentor:
    def __init__(self, name, surname):
        self.name = name
        self.surname = surname
        self.courses_attached = []
    def __str__(self):
        return f"{self.name}"

class Lecturer(Mentor):
    def __init__(self, name, surname):
        super().__init__(name, surname)
        self.courses_attached = []
        self.grades = {}
    def __str__(self):
        return f"Имя: {self.name} \nФамилия: {self.surname} \nСредняя оценка за лекции {self.avg_rate()}"

    def __eq__(self, other):
        if self.avg_rate() > other.avg_rate():
            return f"Студент: {self.name} {self.surname}.\nСредний балл: {self.avg_rate()}"
        elif self.avg_rate() < other.avg_rate():
            return f"Студент: {other.name} {other.surname}.\nСредний балл: {other.avg_rate()}"
        else:
            return f"Баллы одинаковые у {self.name} {self.surname} и {other.name} {other.surname}.\nСреднее количество баллов: {other.avg_rate()}"
    def avg_rate(self):
        tmp = []
        for i in self.grades:
            tmp += self.grades[i]
        avg = sum(tmp) / len(tmp)
        return avg

class Reviewer(Mentor):
    def __init__(self, name, surname):
        super().__init__(name, surname)

    def __str__(self):
        return f"Имя: {self.name} \nФамилия: {self.surname}"

    def rate_hw(self, student, course, grade):
        if isinstance(student, Student) and course in self.courses_attached and course in student.courses_in_progress:
            if course in student.grades:
                student.grades[course] += [grade]
            else:
                student.grades[course] = [grade]
        else:
            return 'Ошибка'

def all_grade_students(course, student_list):
    tmp_list = []
    for student in student_list:
        if course in student.grades.keys():
            for grade in student.grades[course]:
                tmp_list.append(grade)
        else:
            print(f'У студента {student.name} нет курса {course}')
    avg_grade = sum(tmp_list) / len(tmp_list)
    return f"Средняя оценка за курс {course} у студентов: {avg_grade}"

def all_grade_lectors(course, lectors_list):
    tmp_list = []
    for student in lectors_list:
        if course in student.grades.keys():
            for grade in student.grades[course]:
                tmp_list.append(grade)
        else:
            print(f'У лектора {student.name} нет курса {course}')
    avg_grade = sum(tmp_list) / len(tmp_list)
    return f"Средняя оценка за курс {course} у лектора: {avg_grade}"

best_student = Student('Ruoy', 'Eman', 'Male')
best_student_two = Student('Erman', 'Chekovskiy', 'Male')
best_student.courses_in_progress += ['Python']
best_student_two.courses_in_progress += ['Python']
best_student.courses_in_progress += ['Git']
best_student_two.courses_in_progress += ['Git']
best_student.finished_courses += ['Git']
best_student_two.finished_courses += ['Git']

cool_reviewer = Reviewer('Igor', 'Gatsky')
cool_reviewer.courses_attached += ['Python']
cool_reviewer.courses_attached += ['Git']

cool_lecturer = Lecturer('John', 'Smith')
cool_lecturer_two = Lecturer('Stein', 'Silver')
cool_lecturer_two.courses_attached += ['Python']
cool_lecturer_two.courses_attached += ['Git']
cool_lecturer.courses_attached += ['Python']
cool_lecturer.courses_attached += ['Git']

best_student.add_raring_lecturer(cool_lecturer, 'Python', 5)
best_student_two.add_raring_lecturer(cool_lecturer, 'Python', 2)
best_student.add_raring_lecturer(cool_lecturer, 'Python', 10)
best_student_two.add_raring_lecturer(cool_lecturer, 'Python', 9)
best_student.add_raring_lecturer(cool_lecturer, 'Python', 6)
best_student_two.add_raring_lecturer(cool_lecturer, 'Python', 1)
best_student.add_raring_lecturer(cool_lecturer, 'Git', 6)
best_student_two.add_raring_lecturer(cool_lecturer, 'Git', 2)
best_student.add_raring_lecturer(cool_lecturer, 'Git', 6)
best_student_two.add_raring_lecturer(cool_lecturer, 'Git', 10)

best_student.add_raring_lecturer(cool_lecturer_two, 'Python', 5)
best_student_two.add_raring_lecturer(cool_lecturer_two, 'Python', 10)
best_student.add_raring_lecturer(cool_lecturer_two, 'Python', 4)
best_student_two.add_raring_lecturer(cool_lecturer_two, 'Python', 5)
best_student.add_raring_lecturer(cool_lecturer_two, 'Python', 6)
best_student_two.add_raring_lecturer(cool_lecturer_two, 'Python', 1)
best_student.add_raring_lecturer(cool_lecturer_two, 'Git', 1)
best_student_two.add_raring_lecturer(cool_lecturer_two, 'Git', 6)

cool_reviewer.rate_hw(best_student, 'Python', 10)
cool_reviewer.rate_hw(best_student_two, 'Python', 2)
cool_reviewer.rate_hw(best_student, 'Python', 3)
cool_reviewer.rate_hw(best_student_two, 'Python', 4)
cool_reviewer.rate_hw(best_student, 'Python', 1)
cool_reviewer.rate_hw(best_student_two, 'Python', 2)
cool_reviewer.rate_hw(best_student, 'Git', 3)
cool_reviewer.rate_hw(best_student_two, 'Git', 7)
cool_reviewer.rate_hw(best_student, 'Git', 6)
cool_reviewer.rate_hw(best_student_two, 'Git', 9)
cool_reviewer.rate_hw(best_student, 'Git', 1)
cool_reviewer.rate_hw(best_student_two, 'Git', 1)

# print(best_student.grades)
# print(cool_lecturer == cool_lecturer_two)
# print(cool_reviewer)
# print(cool_lecturer.grades)
# print(best_student_two)

lst_students = [best_student, best_student_two]
print(all_grade_students('Python', lst_students))
print(all_grade_students('Git', lst_students))

lst_lectors = [cool_lecturer, cool_lecturer_two]
print(all_grade_lectors('Python', lst_lectors))
print(all_grade_lectors('Git', lst_lectors))


# -*- coding: utf-8 -*-
"""Draw mesh/model/fields with matplotlib."""

import textwrap

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

import pygimli as pg
from pygimli.misc import streamline

from .colorbar import autolevel, createColorBar, updateColorBar
from .utils import updateAxes as updateAxes_

class CellBrowserCacheSingleton(object):
    __instance = None
    cbCache_ = []

    def __new__(cls):
        if CellBrowserCacheSingleton.__instance is None:
            CellBrowserCacheSingleton.__instance = object.__new__(cls)
        return CellBrowserCacheSingleton.__instance

    def add(self, c):
        self.cbCache_.append(c)

    def remove(self, c):
        self.cbCache_.remove(c)

# We only want one instance of this global cache so its a singleton class
__CBCache__ = CellBrowserCacheSingleton()

class CellBrowser(object):
    """Interactive cell browser on current or specified ax for a given mesh.

    Cell information can be displayed by mouse picking. Arrow keys up and down
    can be used to scroll through the cells, while ESC closes the cell
    information window.

    Parameters
    ----------
    mesh : 2D pygimli.Mesh instance
        The plotted mesh to browse through.
    data : iterable
        Cell data.
    ax : mpl axis instance, optional
        Axis instance where the mesh is plotted (default is current axis).

    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawModel
    >>> from pygimli.mplviewer import CellBrowser
    >>>
    >>> mesh = pg.createGrid(range(5), range(5))
    >>> fig, ax = plt.subplots()
    >>> plc = drawModel(ax, mesh, mesh.cellMarkers())
    >>> browser = CellBrowser(mesh)
    >>> browser.connect()
    """
    def __init__(self, mesh, data=None, ax=None):
        """Construct CellBrowser on a specific `mesh`."""
        if ax:
            self.ax = ax
        else:
            self.ax = mpl.pyplot.gca()

        self._connected = False

        self.fig = self.ax.figure
        self.mesh = None
        self.data = None
        self.highLight = None
        self.text = None

        self.cellID = None
        self.event = None
        self.artist = None
        self.pid = None
        self.kid = None
        self.text = None

        self.setMesh(mesh)
        self.setData(data)
        self.connect()

    def __del__(self):
        """Deregister if the cellBrowser has been deleted."""
        self.disconnect()

    def connect(self):
        """Connect to matplotlib figure canvas."""
        if not self._connected:
            self.pid = self.fig.canvas.mpl_connect('pick_event', self.onPick)
            self.kid = self.fig.canvas.mpl_connect('key_press_event',
                                                   self.onPress)
            __CBCache__.add(self)
            self._connected = True

    def disconnect(self):
        """Disconnect from matplotlib figure canvas."""
        if self._connected:
            __CBCache__.remove(self)
            self.fig.canvas.mpl_disconnect(self.pid)
            self.fig.canvas.mpl_disconnect(self.kid)
            self._connected = False

    def initText(self):
        bbox = dict(boxstyle='round, pad=0.5', fc='w', alpha=0.5)
        arrowprops = dict(arrowstyle='->', connectionstyle='arc3,rad=0.5')
        kwargs = dict(fontproperties='monospace', visible=False,
                      fontsize=mpl.rcParams['font.size'] - 2, weight='bold',
                      xytext=(50, 20), arrowprops=arrowprops,
                      textcoords='offset points', bbox=bbox, va='center')

        self.text = self.ax.annotate(None, xy=(0, 0), **kwargs)

    def setMesh(self, mesh):
        self.mesh = mesh

    def setData(self, data=None):
        """Set data, if not set look for the artist array data."""
        self.hide()
        if data is not None:
            if len(data) == self.mesh.cellCount():
                self.data = data
            elif len(data) == self.mesh.nodeCount():
                self.data = pg.meshtools.nodeDataToCellData(self.mesh, data)
            else:
                pg.warn('Data length mismatch mesh.cellCount(): ' +
                        str(len(data)) + "!=" + str(self.mesh.cellCount()) +
                        ". Mapping data to cellMarkers().")
                self.data = data[self.mesh.cellMarkers()]

    def hide(self):
        """Hide info window."""
        self.cellID = -1

        if self.text is not None:
            self.text.set_visible(False)

        self.removeHighlightCell()

        self.fig.canvas.draw()

    def removeHighlightCell(self):
        """Remove cell highlights."""
        if self.highLight is not None:
            if self.highLight in self.ax.collections:
                self.highLight.remove()
            self.highLight = None

    def highlightCell(self, cell):
        """Highlight selected cell."""
        self.removeHighlightCell()
        self.highLight = mpl.collections.PolyCollection(
                                                    [_createCellPolygon(cell)])
        self.highLight.set_edgecolors('0')
        self.highLight.set_linewidths(1.5)
        self.highLight.set_facecolors([0.9, 0.9, 0.9, 0.4])
        self.ax.add_collection(self.highLight)

    def onPick(self, event):
        """Call `self.update()` on mouse pick event."""
        self.event = event
        self.artist = event.artist

        if self.data is None:
            self.data = self.artist.get_array()
            # self.edgeColors = self.artist.get_edgecolors()

        if 'mouseevent' in event.__dict__.keys():
            # print(event.__dict__.keys())
            # print(event.mouseevent)
            if (event.mouseevent.xdata is not None and
                event.mouseevent.ydata is not None and
                event.mouseevent.button == 1):
                c = self.mesh.findCell((event.mouseevent.xdata,
                                        event.mouseevent.ydata))
                if c and self.cellID != c.id():
                    self.cellID = c.id()
                else:
                    self.cellID = -1

                self.update()
        else:  # variant before (seemed inaccurate)
            self.cellID = event.ind[0]

    def onPress(self, event):
        """Call `self.update()` if up, down, or escape keys are pressed."""
        # print(event, event.key)
        if self.data is None:
            return
        if event.key not in ('up', 'down', 'escape'):
            return
        if event.key is 'up':
            if self.cellID is not None:
                self.cellID += 1
        elif event.key is 'down':
            if self.cellID is not None:
                self.cellID -= 1
        else:
            self.hide()
            return

        if self.cellID is not None:
            self.cellID = int(np.clip(self.cellID, 0,
                                      self.mesh.cellCount() - 1))
            self.update()

    def update(self):
        """Update the information window.
        Hide the information window for self.cellID == -1
        """
        try:
            if self.cellID > -1:
                cell = self.mesh.cell(self.cellID)
                center = cell.center()
                x, y = center.x(), center.y()
                marker = cell.marker()
                data = self.data[self.cellID]
                header = "Cell %d:\n" % self.cellID
                header += "-" * (len(header) - 1)
                istr = "\nx: {:.2f}\n y: {:.2f}\n data: {:.2e}\n marker: {:d}"
                info = istr.format(x, y, data, marker)
                text = header + textwrap.dedent(info)

                if self.text is None or self.text not in self.ax.texts:
                    self.initText()

                self.text.set_text(text)
                self.text.xy = x, y
                self.text.set_visible(True)
                self.highlightCell(cell)
                self.fig.canvas.draw()
            else:
                self.hide()

        except BaseException as e:
            print(e)

def drawMesh(ax, mesh, **kwargs):
    """Draw a 2d mesh into a given ax.

    Set the limits of the ax tor the mesh extent.

    Parameters
    ----------

    mesh : :gimliapi:`GIMLI::Mesh`
        The plotted mesh to browse through.
    ax : mpl axe instance, optional
        Axis instance where the mesh is plotted (default is current axis).
    fitView: bool [True]
        Adjust ax limits to mesh bounding box.

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawMesh
    >>> n = np.linspace(1, 2, 10)
    >>> mesh = pg.createGrid(x=n, y=n)
    >>> fig, ax = plt.subplots()
    >>> drawMesh(ax, mesh)
    >>> plt.show()
    """
    if mesh.cellCount() == 0:
        pg.mplviewer.drawPLC(ax, mesh, **kwargs)
    else:
        pg.mplviewer.drawMeshBoundaries(ax, mesh, **kwargs)

    if kwargs.pop('fitView', True):
        ax.set_xlim(mesh.xmin(), mesh.xmax())
        ax.set_ylim(mesh.ymin(), mesh.ymax())
        ax.set_aspect('equal')

    updateAxes_(ax)

def drawModel(ax, mesh, data=None, logScale=True, cMin=None, cMax=None,
              xlabel=None, ylabel=None, verbose=False,
              tri=False, rasterized=False, **kwargs):
    """Draw a 2d mesh and color the cell by the data.

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        The plotted mesh to browse through.
    ax : mpl axis instance, optional
        Axis instance where the mesh is plotted (default is current axis).
    data : array, optional
        Data to draw. Should either equal numbers of cells or nodes of the
        corresponding `mesh`.
    tri : boolean, optional
        use MPL tripcolor (experimental)
    rasterized : boolean, optional
        Rasterize mesh patches to reduce file size and avoid zooming artifacts
        in some PDF viewers.
    **kwargs : Additional keyword arguments
        Will be forwarded to the draw functions and matplotlib methods,
        respectively.

    Returns
    -------
    gci : matplotlib graphics object

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawModel
    >>> n = np.linspace(0, -2, 11)
    >>> mesh = pg.createGrid(x=n, y=n)
    >>> mx = pg.x(mesh.cellCenter())
    >>> my = pg.y(mesh.cellCenter())
    >>> data = np.cos(1.5 * mx) * np.sin(1.5 * my)
    >>> fig, ax = plt.subplots()
    >>> drawModel(ax, mesh, data)
    <matplotlib.collections.PolyCollection object at ...>
    """
    # deprecated .. remove me
    if 'cMap' in kwargs or 'cmap' in kwargs:
        pg.warn('cMap|cmap argument is deprecated for draw functions. ' +
                'Please use show or customize a colorbar.')
    # deprecated .. remove me

    if mesh.nodeCount() == 0:
        pg.error("drawModel: The mesh is empty.", mesh)

    if tri:
        gci = drawMPLTri(ax, mesh, data,
                         cMin=cMin, cMax=cMax, logScale=logScale,
                         **kwargs)
    else:
        gci = pg.mplviewer.createMeshPatches(ax, mesh, verbose=verbose,
                                             rasterized=rasterized)
        ax.add_collection(gci)

        if data is None:
            data = pg.RVector(mesh.cellCount())

        if len(data) != mesh.cellCount():
            print(data, mesh)
            pg.info("drawModel have wrong data length .. " +
                    " indexing data from cellMarkers()")
            viewdata = data[mesh.cellMarkers()]
        else:
            viewdata = data

        if min(data) <= 0:
            logScale = False

        pg.mplviewer.setMappableData(gci, viewdata, cMin=cMin, cMax=cMax,
                                     logScale=logScale)

    gci.set_antialiased(True)
    gci.set_linewidths(0.1)
    gci.set_edgecolors("face")

    if xlabel is not None:
        ax.set_xlabel(xlabel)

    if ylabel is not None:
        ax.set_ylabel(ylabel)

    if kwargs.pop('fitView', True):
        ax.set_xlim(mesh.xmin(), mesh.xmax())
        ax.set_ylim(mesh.ymin(), mesh.ymax())
        ax.set_aspect('equal')

    updateAxes_(ax)
    return gci

def drawSelectedMeshBoundaries(ax, boundaries, color=None, linewidth=1.0,
                               linestyles="-"):
    """Draw mesh boundaries into a given axes.

    Parameters
    ----------
    ax : matplotlib axes
        axes to plot into
    boundaries : :gimliapi:`GIMLI::Mesh` boundary vector
        collection of boundaries to plot
    color : matplotlib color |str [None]
        matching color or string, else colors are according to markers
    linewidth : float [1.0]
        line width
    linestyles : linestyle for line collection, i.e. solid or dashed

    Returns
    -------
    lco : matplotlib line collection object
    """
    drawAA = True
    lines = []

    if hasattr(boundaries, '__len__'):
        if len(boundaries) == 0:
            return

    for bound in boundaries:
        lines.append(list(zip([bound.node(0).x(), bound.node(1).x()],
                              [bound.node(0).y(), bound.node(1).y()])))

    lineCollection = mpl.collections.LineCollection(lines, antialiaseds=drawAA)

    if color is None:
        viewdata = [b.marker() for b in boundaries]
        pg.mplviewer.setMappableData(lineCollection, viewdata, logScale=False)
    else:
        lineCollection.set_color(color)

    lineCollection.set_linewidth(linewidth)
    lineCollection.set_linestyles(linestyles)
    ax.add_collection(lineCollection)

    updateAxes_(ax)

    return lineCollection

def drawSelectedMeshBoundariesShadow(ax, boundaries, first='x', second='y',
                                     color=(0.3, 0.3, 0.3, 1.0)):
    """Draw mesh boundaries as shadows into a given axes.

    Parameters
    ----------
    ax : matplotlib axes
        axes to plot into
    boundaries : :gimliapi:`GIMLI::Mesh` boundary vector
        collection of boundaries to plot
    first / second : str ['x' / 'y']
        attribute names to retrieve from nodes
    color : matplotlib color |str [None]
        matching color or string, else colors are according to markers
    linewidth : float [1.0]
        line width

    Returns
    -------
    lco : matplotlib line collection object
    """
    polys = []

    for cell in boundaries:
        polys.append(list(zip([getattr(cell.node(0), first)(),
                               getattr(cell.node(1), first)(),
                               getattr(cell.node(2), first)()],
                              [getattr(cell.node(0), second)(),
                               getattr(cell.node(1), second)(),
                               getattr(cell.node(2), second)()])))

    collection = mpl.collections.PolyCollection(polys, antialiaseds=True)

    collection.set_color(color)
    collection.set_edgecolor(color)
    collection.set_linewidth(0.2)
    ax.add_collection(collection)

    updateAxes_(ax)
    return collection

def drawMeshBoundaries(ax, mesh, hideMesh=False, useColorMap=False, **kwargs):
    """Draw mesh on ax with boundary conditions colorized.

    Parameters
    ----------

    hideMesh: bool [False]
        Show only the boundary of the mesh and omit inner edges that
        separate the cells.

    useColorMap: bool[False]
        Apply the default colormap to boundaries with marker values > 0

    **kwargs:
        * fitView : bool [True]
        * linewidth : float [0.3]
            linewidth for edges with marker == 0 if hideMesh is False.

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawMeshBoundaries
    >>> n = np.linspace(0, -2, 11)
    >>> mesh = pg.createGrid(x=n, y=n)
    >>> for bound in mesh.boundaries():
    ...     if not bound.rightCell():
    ...         bound.setMarker(pg.MARKER_BOUND_MIXED)
    ...     if bound.center().y() == 0:
    ...         bound.setMarker(pg.MARKER_BOUND_HOMOGEN_NEUMANN)
    >>> fig, ax = plt.subplots()
    >>> drawMeshBoundaries(ax, mesh)
    """
    if not mesh:
        raise Exception("drawMeshBoundaries(ax, mesh): invalid mesh")

    if not mesh.dimension() == 2:
        raise Exception("No 2d mesh: dim = ", mesh.dimension())

    if mesh.nodeCount() < 2:
        raise Exception("drawMeshBoundaries(ax, mesh): to few nodes",
                        mesh.nodeCount())

    if kwargs.pop('fitView', True):
        ax.set_xlim(mesh.xmin() - 0.05, mesh.xmax() + 0.05)
        ax.set_ylim(mesh.ymin() - 0.05, mesh.ymax() + 0.05)

#    drawAA = True
#    swatch = pg.Stopwatch(True)
    mesh.createNeighbourInfos()

    lw = kwargs.pop('lw', None)
    col = kwargs.pop('color', None)
    if not hideMesh:
        drawSelectedMeshBoundaries(ax,
                                   mesh.findBoundaryByMarker(0),
                                   color=(0.0, 0.0, 0.0, 1.0),
                                   linewidth=lw or 0.3)

    drawSelectedMeshBoundaries(
            ax, mesh.findBoundaryByMarker(pg.MARKER_BOUND_HOMOGEN_NEUMANN),
            color=(0.0, 1.0, 0.0, 1.0),
            linewidth=lw or 1.0)
    drawSelectedMeshBoundaries(
            ax, mesh.findBoundaryByMarker(pg.MARKER_BOUND_MIXED),
            color=(1.0, 0.0, 0.0, 1.0),
            linewidth=lw or 1.0)

    b0 = [b for b in mesh.boundaries() if b.marker() > 0]
    if useColorMap:
        drawSelectedMeshBoundaries(ax, b0, color=None,
                                   linewidth=lw or 1.5)
    else:
        drawSelectedMeshBoundaries(ax, b0,
                                   color=col or (0.0, 0.0, 0.0, 1.0),
                                   linewidth=lw or 1.5)

    b4 = [b for b in mesh.boundaries() if b.marker() < -4]
    drawSelectedMeshBoundaries(ax, b4,
                               color=col or (0.0, 0.0, 0.0, 1.0),
                               linewidth=lw or 1.5)

    updateAxes_(ax)

def drawPLC(ax, mesh, fillRegion=True, regionMarker=True, boundaryMarker=False,
            showNodes=False, **kwargs):
    """Draw 2D PLC into given axes.

    Parameters
    ----------

    fillRegion: bool [True]
        Fill the regions with default colormap.

    regionMarker: bool [True]
        show region marker

    boundaryMarker: bool [False]
        show boundary marker

    showNodes: bool [False]
        draw all nodes

    **kwargs

    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> import pygimli.meshtools as mt
    >>> # Create geometry definition for the modeling domain
    >>> world = mt.createWorld(start=[-20, 0], end=[20, -16],
    ...                        layers=[-2, -8], worldMarker=False)
    >>> # Create a heterogeneous block
    >>> block = mt.createRectangle(start=[-6, -3.5], end=[6, -6.0],
    ...                            marker=10,  boundaryMarker=10, area=0.1)
    >>> fig, ax = plt.subplots()
    >>> geom = mt.mergePLC([world, block])
    >>> pg.mplviewer.drawPLC(ax, geom)
    """
    #    eCircles = []
    if fillRegion and mesh.boundaryCount() > 2:
        tmpMesh = pg.meshtools.createMesh(mesh, quality=20, area=0)
        if tmpMesh.cellCount() == 0:
            pass
        else:
            markers = np.array(tmpMesh.cellMarkers())
            uniquemarkers, uniqueidx = np.unique(markers, return_inverse=True)
            gci = drawModel(ax=ax,
                            data=np.arange(len(uniquemarkers))[uniqueidx],
                            mesh=tmpMesh,
                            alpha=1,
                            linewidth=0.0,
                            tri=True,
                            snap=True,
                            )

            if regionMarker:
                cbar = createColorBar(gci, label="Region markers")
                updateColorBar(
                        cbar, cMap=plt.cm.get_cmap("Set3", len(uniquemarkers)),
                        cMin=-0.5, cMax=len(uniquemarkers) - 0.5)
                ticks = np.arange(len(uniquemarkers))

                cbar.set_ticks(ticks)
                areas = {}
                for reg in mesh.regionMarker():
                    areas[reg.marker()] = reg.area()
                labels = []
                for marker in uniquemarkers:
                    label = "{:d}".format(marker)
                    if marker in areas and areas[marker] > 0:
                        label += "\n$A$={:g}".format(areas[marker])
                        # label += "\n(area: %s)" % areas[marker]
                    labels.append(label)
                cbar.set_ticklabels(labels)

    else:
        if kwargs.pop('showBoundary', True):
            drawMeshBoundaries(ax, mesh, **kwargs)

    if showNodes:
        for n in mesh.nodes():
            col = (0.0, 0.0, 0.0, 0.5)

            if n.marker() == pg.MARKER_NODE_SENSOR:
                col = (0.0, 0.0, 0.0, 1.0)

            # ms = kwargs.pop('markersize', 5)
            ax.plot(n.pos()[0], n.pos()[1], 'bo', color=col, **kwargs)

    #        eCircles.append(mpl.patches.Circle((n.pos()[0], n.pos()[1])))
    #        eCircles.append(mpl.patches.Circle((n.pos()[0], n.pos()[1]), 0.1))
    #        cols.append(col)
    #    p = mpl.collections.PatchCollection(eCircles, color=cols)
    #    ax.add_collection(p)

    if boundaryMarker:
        for b in mesh.boundaries():
            x = b.center()[0]
            y = b.center()[1]
            bbox_props = dict(boxstyle="circle,pad=0.1", fc="w", ec="k")
            ax.text(x, y, str(b.marker()), color="k", va="center", ha="center",
                    zorder=20, bbox=bbox_props, fontsize=9)

    if regionMarker:

        for hole in mesh.holeMarker():
            ax.text(hole[0], hole[1], 'H', color='black',
                    va="center", ha="center")

    if kwargs.pop('fitView', True):
        ax.set_xlim(mesh.xmin(), mesh.xmax())
        ax.set_ylim(mesh.ymin(), mesh.ymax())
        ax.set_aspect('equal')

    updateAxes_(ax)

def _createCellPolygon(cell):
    """Utility function to polygon for cell shape to be used by MPL."""
    if cell.shape().nodeCount() == 3:
        return list(zip([cell.node(0).x(), cell.node(1).x(),
                         cell.node(2).x()],
                        [cell.node(0).y(), cell.node(1).y(),
                         cell.node(2).y()]))
    elif cell.shape().nodeCount() == 4:
        return list(zip([cell.node(0).x(), cell.node(1).x(),
                         cell.node(2).x(), cell.node(3).x()],
                        [cell.node(0).y(), cell.node(1).y(),
                         cell.node(2).y(), cell.node(3).y()]))

    pg.warn("Unknown shape to patch: ", cell)

def createMeshPatches(ax, mesh, verbose=True, rasterized=False):
    """Utility function to create 2d mesh patches within a given ax."""
    if not mesh:
        pg.error("drawMeshBoundaries(ax, mesh): invalid mesh:", mesh)
        return

    if mesh.nodeCount() < 2:
        pg.error("drawMeshBoundaries(ax, mesh): to few nodes:", mesh)
        return

    pg.tic()
    polys = [_createCellPolygon(c) for c in mesh.cells()]
    patches = mpl.collections.PolyCollection(polys, picker=True,
                                             rasterized=rasterized)

    if verbose:
        pg.info("Creation of mesh patches took = ", pg.toc())

    return patches

def createTriangles(mesh, data=None):
    """Generate triangle objects for later drawing.

    Parameters
    ----------
    mesh : :gimliapi:`GIMLI::Mesh`
        pyGimli mesh to plot
    data : iterable [None]
        cell-based values to plot

    Returns
    -------
    x : numpy array
        x position of nodes
    y : numpy array
        x position of nodes
    triangles : numpy array Cx3
        cell indices for each triangle
    z : numpy array
        data for given indices
    dataIdx : list of int
        list of indices into array to plot
    """
    x = pg.x(mesh.positions())
    #    x.round(1e-1)
    y = pg.y(mesh.positions())
    #    y.round(1e-1)

    triCount = 0

    for c in mesh.cells():
        if c.shape().nodeCount() == 4:
            triCount = triCount + 2
        else:
            triCount = triCount + 1

    triangles = np.zeros((triCount, 3))
    dataIdx = list(range(triCount))

    triCount = 0
    for c in mesh.cells():
        if c.shape().nodeCount() == 4:
            triangles[triCount, 0] = c.node(0).id()
            triangles[triCount, 1] = c.node(1).id()
            triangles[triCount, 2] = c.node(2).id()
            dataIdx[triCount] = c.id()
            triCount = triCount + 1

            triangles[triCount, 0] = c.node(0).id()
            triangles[triCount, 1] = c.node(2).id()
            triangles[triCount, 2] = c.node(3).id()
            dataIdx[triCount] = c.id()
            triCount = triCount + 1
        else:
            triangles[triCount, 0] = c.node(0).id()
            triangles[triCount, 1] = c.node(1).id()
            triangles[triCount, 2] = c.node(2).id()
            dataIdx[triCount] = c.id()
            triCount = triCount + 1

    z = None
    if data is not None:
        if len(data) == mesh.cellCount():
            # strange behavior if we just use these slice
            z = np.array(data[dataIdx])
        else:
            z = np.array(data)

    return x, y, triangles, z, dataIdx

def drawMPLTri(ax, mesh, data=None,
               cMin=None, cMax=None, logScale=True,
               **kwargs):
    """Draw mesh based scalar field using matplotlib triplot.

    Draw scalar field into MPL axes using matplotlib triplot.

    TODO
        * Examples
        * Doc: Interpolation variants

    Parameters
    ----------
    data: iterable
        Scalar field values. Can be of length mesh.cellCount()
        or mesh.nodeCount().

    **kwargs:
        * shading: interpolation algorithm [flat]
        * fillContour: [True]
        * withContourLines: [True]
    Returns
    -------
        gci : image object
            The current image object useful for post color scaling
    Examples
    --------
    >>>
    """
    # deprecated remove me
    if 'cMap' in kwargs or 'cmap' in kwargs:
        pg.warn('cMap|cmap argument is deprecated for draw functions.' +
                'Please use show or customize a colorbar.')
    # deprecated remove me

    x, y, triangles, z, _ = createTriangles(mesh, data)

    gci = None

    levels = kwargs.pop('levels', [])
    nLevs = kwargs.pop('nLevs', 5)

    if len(levels) == 0:
        levels = autolevel(data, nLevs, zmin=cMin, zmax=cMax,
                           logScale=logScale)

    if len(z) == len(triangles):
        shading = kwargs.pop('shading', 'flat')

        # bounds = np.linspace(levels[0], levels[-1], nLevs)
        # norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
        if shading == 'gouraud':
            z = pg.meshtools.cellDataToNodeData(mesh, data)
            gci = ax.tripcolor(x, y, triangles, z,
                               shading=shading, **kwargs)
        else:
            gci = ax.tripcolor(x, y, triangles, facecolors=z,
                               shading=shading, **kwargs)

    elif len(z) == mesh.nodeCount():
        shading = kwargs.pop('shading', None)

        if shading is not None:
            gci = ax.tripcolor(x, y, triangles, z, shading=shading, **kwargs)
        else:

            fillContour = kwargs.pop('fillContour', True)
            contourLines = kwargs.pop('withContourLines', True)

            if fillContour:
                # add outer climits to fill lower and upper too
                levs = np.array(levels)

                if min(z) < min(levels):
                    levs = np.hstack([min(z), levs])

                if max(z) > max(levels):
                    levs = np.hstack([levs, max(z)])

                gci = ax.tricontourf(x, y, triangles, z, levels=levs,
                                     **kwargs)
            if contourLines:
                ax.tricontour(x, y, triangles, z, levels=levels,
                              colors=kwargs.pop('colors', ['0.5']), **kwargs)
    else:
        gci = None
        raise Exception("Data size does not fit mesh size: ", len(z),
                        mesh.cellCount(), mesh.nodeCount())

    if gci and cMin and cMax:
        gci.set_clim(cMin, cMax)

    if kwargs.pop('fitView', True):
        ax.set_xlim(mesh.xmin(), mesh.xmax())
        ax.set_ylim(mesh.ymin(), mesh.ymax())
        ax.set_aspect('equal')

    updateAxes_(ax)
    return gci

def drawField(ax, mesh, data=None, **kwargs):
    """Draw a mesh-related (node or cell based) field onto a given MPL axis.

        Only for triangle/quadrangle meshes currently

    Parameters
    ----------
    ax : MPL axes

    mesh : :gimliapi:`GIMLI::Mesh`

    data: iterable
        Scalar field values. Can be of length mesh.cellCount()
        or mesh.nodeCount().

    **kwargs:
        * shading: interpolation algorithm [flat]
        * fillContour: [True]
        * withContourLines: [True]
    Returns
    -------
        gci : image object
            The current image object useful for post color scaling

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawField
    >>> n = np.linspace(0, -2, 11)
    >>> mesh = pg.createGrid(x=n, y=n)
    >>> nx = pg.x(mesh.positions())
    >>> ny = pg.y(mesh.positions())
    >>> data = np.cos(1.5 * nx) * np.sin(1.5 * ny)
    >>> fig, ax = plt.subplots()
    >>> drawField(ax, mesh, data)
    <matplotlib.tri.tricontour.TriContourSet ...>
    """
    return drawMPLTri(ax, mesh, data, **kwargs)

def drawStreamLines(ax, mesh, u, nx=25, ny=25, **kwargs):
    """Draw streamlines for the gradients of field values u on a mesh.

    The matplotlib routine streamplot needs equidistant spacings so
    we interpolate first on a grid defined by nx and ny nodes.
    Additionally arguments are piped to streamplot.

    This works only for rectangular regions.
    You should use pg.mplviewer.drawStreams, which is more comfortable and
    more flexible.
    """
    X, Y = np.meshgrid(
        np.linspace(mesh.xmin(), mesh.xmax(), nx),
        np.linspace(mesh.ymin(), mesh.ymax(), ny))

    U = X.copy()
    V = X.copy()

    for i, row in enumerate(X):
        for j in range(len(row)):
            p = [X[i, j], Y[i, j]]
            gr = [0.0, 0.0]
            c = mesh.findCell(p)
            if c:
                gr = c.grad(p, u)

            U[i, j] = -gr[0]
            V[i, j] = -gr[1]

    gci = ax.streamplot(X, Y, U, V, **kwargs)

    updateAxes_(ax)
    return gci

def drawStreamLine_(ax, mesh, c, data, dataMesh=None, linewidth=1.0,
                    dropTol=0.0, **kwargs):
    """Draw a single streamline.

    Draw a single streamline into a given mesh for given data stating at
    the center of cell c.
    The Streamline will be enlarged until she reached a cell that
    already contains a streamline.

    TODO
        linewidth and color depends on absolute velocity
        or background color saturation

    Parameters
    ----------

    ax : matplotlib.ax
        ax to draw into

    mesh : :gimliapi:`GIMLI::Mesh`
        2d Mesh to draw the streamline

    c : :gimliapi:`GIMLI::Cell`
        start cell

    data : iterable float | [float, float]
        If data is an array (per cell or node) gradients are calculated
        otherwise the data will be interpreted as vector field.

    dataMesh : :gimliapi:`GIMLI::Mesh` [None]

        Optional mesh for the data. If you want high resolution
        data to plot on coarse draw mesh.

    linewidth : float [1.0]

        Streamline linewidth

    dropTol : float [0.0]

        Don't draw stream lines with velocity lower than drop tolerance.

    """
    x, y, v = streamline(mesh, data, startCoord=c.center(), dLengthSteps=5,
                         dataMesh=dataMesh, maxSteps=10000, verbose=False,
                         coords=[0, 1])

    if 'color' not in kwargs:
        kwargs['color'] = 'black'

    lines = None

    if len(x) > 2:
        points = np.array([x, y]).T.reshape(-1, 1, 2)

        segments = np.concatenate([points[:-1], points[1:]], axis=1)

        lwidths = pg.RVector(len(v), linewidth)
        lwidths[pg.find(pg.RVector(v) < dropTol)] = 0.0

        lines = mpl.collections.LineCollection(
            segments, linewidths=lwidths, **kwargs)
        ax.add_collection(lines)

        # probably the limits are wrong without plot call
        # lines = ax.plot(x, y, **kwargs)
        # updateAxes_(ax, lines)
        # ax.plot(x, y, '.-', color='black', **kwargs)
    if len(x) > 3:
        xmid = int(len(x) / 2)
        ymid = int(len(y) / 2)
        dx = x[xmid + 1] - x[xmid]
        dy = y[ymid + 1] - y[ymid]
        c = mesh.findCell([x[xmid], y[ymid]])
        # dLength = c.center().dist(c.node(0).pos()) / 4.  # NOT USED

        if v[xmid] > dropTol:
            # ax.arrow(x[xmid], y[ymid], dx, dy,
            #          #width=dLength / 3.,
            #          width=0,
            #          head_width=0.01,
            #          head_length=0.02
            #         #  head_width=dLength / 3.,
            #         #  head_length=dLength / 3.,
            #          head_starts_at_zero=True,
            #          length_includes_head=False,
            #          lw=4,
            #          ls=None,
            #          **kwargs)

            dx90 = -dy
            dy90 = dx
            aLen = 3
            aWid = 1
            xy = list(zip([x[xmid] + dx90*aWid, x[xmid] + dx*aLen,
                           x[xmid] - dx90*aWid],
                          [y[ymid] + dy90*aWid, y[ymid] + dy*aLen,
                           y[ymid] - dy90*aWid]))

            arrow = mpl.patches.Polygon(xy, ls=None, lw=0, closed=True,
                                        **kwargs)
            # arrow = mpl.collections.PolyCollection(xy, lines=None,
            #                                        closed=True, **kwargs)
            ax.add_patch(arrow)

    return lines

def drawStreams(ax, mesh, data, startStream=3, **kwargs):
    """Draw streamlines based on an unstructured mesh.

    Every cell contains only one streamline and every new stream line
    starts in the center of a cell. You can alternatively provide a second mesh
    with coarser mesh to draw streams for.

    Parameters
    ----------

    ax : matplotlib.ax
        ax to draw into

    mesh : :gimliapi:`GIMLI::Mesh`
        2d Mesh to draw the streamline

    data : iterable float | [float, float] | pg.R3Vector
        If data is an array (per cell or node) gradients are calculated
        otherwise the data will be interpreted as vector field.

    startStream : int
        variate the start stream drawing, try values from 1 to 3 what every
        you like more.

    **kwargs: forward to drawStreamLine_

        * coarseMesh

            Instead of draw a stream for every cell in mesh, draw a streamline
            segment for each cell in coarseMesh.

        * quiver: bool

            Draw arrows instead of streamlines.

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawStreams
    >>> n = np.linspace(0, 1, 10)
    >>> mesh = pg.createGrid(x=n, y=n)
    >>> nx = pg.x(mesh.positions())
    >>> ny = pg.y(mesh.positions())
    >>> data = np.cos(1.5 * nx) * np.sin(1.5 * ny)
    >>> fig, ax = plt.subplots()
    >>> drawStreams(ax, mesh, data, color='red')
    >>> drawStreams(ax, mesh, data, dropTol=0.9)
    >>> drawStreams(ax, mesh, pg.solver.grad(mesh, data),
    ...             color='green', quiver=True)
    >>> ax.set_aspect('equal')
    >>> pg.wait()
    """
    viewMesh = None
    dataMesh = None

    quiver = kwargs.pop('quiver', False)

    if quiver:

        x = None
        y = None
        u = None
        v = None

        if len(data) == mesh.nodeCount():
            x = pg.x(mesh.positions())
            y = pg.y(mesh.positions())
        elif len(data) == mesh.cellCount():
            x = pg.x(mesh.cellCenters())
            y = pg.y(mesh.cellCenters())
        elif len(data) == mesh.boundaryCount():
            x = pg.x(mesh.boundaryCenters())
            y = pg.y(mesh.boundaryCenters())

        if isinstance(data, pg.R3Vector):
            u = pg.x(data)
            v = pg.y(data)
        else:
            u = data[:, 0]
            v = data[:, 1]

        ax.quiver(x, y, u, v, **kwargs)

        updateAxes_(ax)
        return

    if 'coarseMesh' in kwargs:
        viewMesh = kwargs['coarseMesh']
        dataMesh = mesh
        dataMesh.createNeighbourInfos()
        del kwargs['coarseMesh']
    else:
        viewMesh = mesh

    viewMesh.createNeighbourInfos()

    for c in viewMesh.cells():
        c.setValid(True)

    if startStream == 1:
        # start a stream from each boundary cell
        for y in np.linspace(viewMesh.ymin(), viewMesh.ymax(), 100):
            c = viewMesh.findCell(
                [(viewMesh.xmax() - viewMesh.xmax()) / 2.0, y])
            if c is not None:
                if c.valid():
                    drawStreamLine_(ax, viewMesh, c, data, dataMesh, **kwargs)

    elif startStream == 2:
        # start a stream from each boundary cell
        for x in np.linspace(viewMesh.xmin(), viewMesh.xmax(), 100):
            c = viewMesh.findCell(
                [x, (viewMesh.ymax() - viewMesh.ymax()) / 2.0])
            if c is not None:
                if c.valid():
                    drawStreamLine_(ax, viewMesh, c, data, dataMesh, **kwargs)

    elif startStream == 3:
        # start a stream from each boundary cell
        for b in viewMesh.findBoundaryByMarker(1, 99):
            c = b.leftCell()
            if c is None:
                c = b.rightCell()

            if c.valid():
                drawStreamLine_(ax, viewMesh, c, data, dataMesh, **kwargs)

    # start a stream from each unused cell
    for c in viewMesh.cells():
        if c.valid():
            drawStreamLine_(ax, viewMesh, c, data, dataMesh, **kwargs)

    for c in viewMesh.cells():
        c.setValid(True)

    updateAxes_(ax)

def drawSensors(ax, sensors, diam=None, coords=None, verbose=False, **kwargs):
    """Draw sensor positions as black dots with a given diameter.

    Parameters
    ----------
    sensors : vector or list of RVector3
        list of positions to plot
    diam : float [None]
        diameter of circles (None leads to point distance by 8)
    coords: (int, int) [0, 1]
        Coordinates to take (usually x and y)

    Examples
    --------
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> import pygimli as pg
    >>> from pygimli.mplviewer import drawSensors
    >>> sensors = np.random.rand(5, 2)
    >>> fig, ax = pg.plt.subplots()
    >>> drawSensors(ax, sensors, diam=0.02, coords=[0, 1])
    >>> ax.set_aspect('equal')
    >>> pg.wait()
    """
    if coords is None:
        coords = [0, 2]
        if pg.yVari(sensors):
            coords = [0, 1]

    eCircles = []

    if diam is None:
        eSpacing = sensors[0].distance(sensors[1])
        diam = eSpacing / 8.0

    for i, e in enumerate(sensors):
        if verbose:
            print(e, diam, e[coords[0]], e[coords[1]])
        eCircles.append(
            mpl.patches.Circle((e[coords[0]], e[coords[1]]), diam, **kwargs))

    p = mpl.collections.PatchCollection(eCircles, **kwargs)
    p.set_zorder(100)
    ax.add_collection(p)

    updateAxes_(ax)

def _createParameterContraintsLines(mesh, cMat, cWeight=None):
    """TODO Documentme."""
    C = pg.RMatrix()
    if isinstance(cMat, pg.SparseMapMatrix):
        cMat.save('tmpC.matrix')
        pg.loadMatrixCol(C, 'tmpC.matrix')
    else:
        C = cMat

    cellList = dict()
    for c in mesh.cells():
        pID = c.marker()
        if pID not in cellList:
            cellList[pID] = []
        cellList[pID].append(c)

    paraCenter = dict()
    for pID, vals in list(cellList.items()):
        p = pg.RVector3(0.0, 0.0, 0.0)
        for c in vals:
            p += c.center()
        p /= float(len(vals))
        paraCenter[pID] = p

    nConstraints = C[0].size()
    start = []
    end = []
    #    swatch = pg.Stopwatch(True)  # not used
    for i in range(0, int(nConstraints / 2)):
        # print i
        # if i == 3: break;

        idL = int(C[1][i * 2])
        idR = int(C[1][i * 2 + 1])

        p1 = paraCenter[idL]
        p2 = paraCenter[idR]

        if cWeight is not None:
            pa = pg.RVector3(p1 + (p2 - p1) / 2.0 * (1.0 - cWeight[i]))
            pb = pg.RVector3(p2 + (p1 - p2) / 2.0 * (1.0 - cWeight[i]))
        else:
            pa = p1
            pb = p2

        # print(i, idL, idR, p1, p2)

        start.append(pa)
        end.append(pb)

#    updateAxes_(ax)  # not existing

    return start, end

def drawParameterConstraints(ax, mesh, cMat, cWeight=None):
    """Draw inter parameter constraints between cells.

    Parameters
    ----------
    ax : MPL axes
    mesh :
    """
    start, end = _createParameterContraintsLines(mesh, cMat, cWeight)

    lines = []
    colors = []
    linewidths = []
    for i, _ in enumerate(start):
        lines.append(list(zip([start[i].x(), end[i].x()],
                              [start[i].y(), end[i].y()])))

        linewidth = 0.5
        col = (0.0, 0.0, 1.0, 1.0)
        colors.append(col)
        linewidths.append(linewidth)

    linCol = mpl.collections.LineCollection(lines, antialiaseds=True)
    linCol.set_color(colors)
    linCol.set_linewidth(linewidths)
    ax.add_collection(linCol)

    updateAxes_(ax)


from ..Types.Numeric import Numeric
from ..Types.Date import Date
from ..Types.String import String
from ..Types.Img import Img
from ..Types.Block import Block
from .Queue import Queue
from .Map import Map
from .Team import Team
from .Player import Player

class Match(Block):

    def __init__(self):
        Block.__init__(self, "Match")
        self.content = {
            "queueId" : Queue(),
            "mapId" : Map(),
            "gameDuration" : Numeric("Duration"),
            "gameCreation" : Date("Creation"),
            "patch" : String("Patch"),
            "redTeam" : Team("Red team"),
            "blueTeam" : Team("Blue team"),
            "player" : Player()
        }

    def setValue(self, data):
        for k in self.content:
            if k in data.keys():
                self.content[k].setValue(data[k])

        self.content["patch"].setValue(".".join(data["gameVersion"].split(".")[:2]))

        playerNames = {}
        for p in data["participantIdentities"]:
            playerNames[p["participantId"]] = p["player"]["summonerName"]
        for k,p in enumerate(data["participants"]):
            data["participants"][k]["name"] = playerNames[p["participantId"]]

        self.content["redTeam"].setValue(data)
        self.content["blueTeam"].setValue(data)

    def setPlayer(self, playerName):
        #print(self.content["redTeam"].content["players"].__dict__)
        for player in self.content["redTeam"].content["players"].content:
            if player.content["name"].getData()["value"] == playerName:
                self.content["player"] = player
        for player in self.content["blueTeam"].content["players"].content:
            if player.content["name"].getData()["value"] == playerName:
                self.content["player"] = player


import pygame as pg
from pygame.math import Vector2
import sys

def run():
    # pygame constants
    CLOCK = pg.time.Clock()
    FRAMES: int = 60

    # world
    FLOOR_CHUNK = 64
    SCREEN_SIZE = ["small", "medium", "large"]
    SCREEN_SELECT = SCREEN_SIZE[1]
    if SCREEN_SELECT == "medium":
        WIN_WIDTH = FLOOR_CHUNK * 20   # 1280
        WIN_HEIGHT = FLOOR_CHUNK * 10  # 640
    elif SCREEN_SELECT == "large":
        WIN_WIDTH = FLOOR_CHUNK * 30   # 1920
        WIN_HEIGHT = FLOOR_CHUNK * 20  # 1280
    else:
        WIN_WIDTH = FLOOR_CHUNK * 10   # 640
        WIN_HEIGHT = FLOOR_CHUNK * 5   # 320

    WIN_SIZE = (WIN_WIDTH, WIN_HEIGHT)

    # pygame setup
    pg.init()
    pg.display.set_caption("Drawings")

    # screen
    window = pg.display.set_mode(WIN_SIZE, 0, 32)
    background = pg.Surface(WIN_SIZE)

    # entities
    FLOOR_SIZE = Vector2(FLOOR_CHUNK, FLOOR_CHUNK)
    floor_tiles = [pg.Rect(x * FLOOR_CHUNK, FLOOR_CHUNK *  8, FLOOR_CHUNK, FLOOR_CHUNK) for x in range(25)]

    # Game data
    game_data = {
        'run': True
    }

    # handlers
    def events_handler():

        for event in pg.event.get():
            if event.type == pg.QUIT or (
                    event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):  # x button and esc terminates the game!
                game_data["run"] = False

    screen_clear = lambda: background.fill((0, 0, 0))
    screen_paint = lambda: window.blit(background, (0, 0))
    game_update = lambda: pg.display.update()
    game_tick = lambda: CLOCK.tick(FRAMES)

    triangle_increase = 0
    triangle_point_1 = 25

    circle_roll = 0
    circle_x = 155

    while game_data["run"]:
        screen_clear()  # screen clear

        # ==============================================================================
        #                               GAME LOGIC HERE
        # ==============================================================================

        # Render entities
        for i, tile in enumerate(floor_tiles):
            img = pg.Surface(FLOOR_SIZE)
            if i % 2 == 0:
                img.fill((75, 75, 175))
            else:
                img.fill((255, 250, 60))

            background.blit(img, tile.topleft)

        # Draw stuff
        if triangle_increase > 20:
            triangle_increase = 20
            triangle_point_1 += 1
        else:
            triangle_increase += 1

        if circle_roll > 5:
            circle_roll = 0
            circle_x += 20
        else:
            circle_roll += 1

        # square
        pg.draw.rect(background, (255, 0, 0), pg.Rect(Vector2(155, 390), Vector2(120, 120)))
        # Rectangle
        pg.draw.rect(background, (255, 255, 75), pg.Rect(Vector2(WIN_WIDTH / 2, WIN_HEIGHT / 2), Vector2(200, 150)))
        # Triangle
        pg.draw.polygon(background, (0, 255, 175), ((triangle_point_1, 25), (320, 125), (250, 390)))
        # Circle
        pg.draw.circle(background, (0, 255, 0), Vector2(circle_x, 200), 140)
        # Elispe 1 ( Inside square )
        pg.draw.ellipse(background, (50, 120, 120), pg.Rect(Vector2(155, 390), Vector2(120, 120)), 200)
        # Elispe 2
        pg.draw.ellipse(background, (50, 120, 120), pg.Rect(Vector2(800, 100), Vector2(99, 320)), 200)
        # Line 1
        pg.draw.line(background, (255, 255, 255), Vector2(850, 50), Vector2(10, 500))
        pg.draw.line(background, (255, 255, 255), Vector2(50, 850), Vector2(500, 10))
        # Lines
        pg.draw.lines(background, (0, 120, 245), True, (Vector2(980, 320), Vector2(235, 560)), width=20)

        # ==============================================================================
        # ..............................................................................
        # ==============================================================================

        # This in order
        events_handler()    # 1) Evebts
        game_update()       # 2) Update the game
        screen_paint()      # 3) Repaint the screen
        game_tick()         # 4) Wait 60 Frames

    # Do some resource clean up ...
    pg.quit()
    sys.exit(0)

if __name__ == '__main__':
    run()


import yt
import numpy as np
from galaxy_analysis.plot.plot_styles import *
import matplotlib.pyplot as plt

from scipy.stats import pearsonr

def correlation(field, ds, dmax = 600.0, N = 100, Ndist = 60,
                           Niter = 1):
    """
    Computes the Pearson correlation statistic for a given field
    as a function of distance. This is a spatial autocorrelation.
    Currently samples points in the midplane of a disk, then computes
    correlation as a function of spherical radius around those points.

    Updates needed for this function:
        1) Ability to request random initial points over arbitrary geometry
        2) Ability to use other distances (cylindrical, etc.)
        3) Use of Niter parameter to sample Niter times around initial
           points and average, rather than just once
           (makes for Niter x N x Ndist total samples.. so.. a lot..)
        4) Parallelization? Primarily if iterations used. Read from
           disk may be issue here though (probably not... just preload?)
        5) If above two, sampling is inefficient constructing the
           pt_x and pt_x_prime arrays of point objects. Could do a faster
           job with a FRB (maybe?) with data loaded in memory already
    """

    domain_width = ds.domain_width
    center = ds.domain_center.to('pc') / domain_width

    # get initial random points
    theta = np.random.rand(N) * np.pi * 2.0
    r_cyl = np.random.rand(N) * dmax**2
    xvec  = (np.sqrt(r_cyl) * np.cos(theta)) * yt.units.pc / domain_width[0] + center[0]
    yvec  = (np.sqrt(r_cyl) * np.cos(theta)) * yt.units.pc / domain_width[1] + center[1]
    zvec  = (np.zeros(N))                    * yt.units.pc / domain_width[2] + center[2]

    # list of yt point objects
    pt_x  = [ ds.r[ [xvec[i].value, yvec[i].value, zvec[i].value] ] for i in np.arange(N)]

    # field values
    S_x   = np.array([p[metal_field][0] for p in pt_x])

    rsample   = np.linspace(0.0, dmax, Ndist)
    corrcoeff = np.zeros(np.size(rsample))
    for i in np.arange(np.size(rsample)):

        # get new random points around each
        theta = np.random.rand(N) * np.pi * 2.0
        r_cyl = np.random.rand(N) * (rsample[i])**2.0

        xprime  = (np.sqrt(r_cyl) * np.cos(theta)) * yt.units.pc / domain_width[0] + xvec
        yprime  = (np.sqrt(r_cyl) * np.cos(theta)) * yt.units.pc / domain_width[1] + yvec
        zprime  = (np.zeros(N))                    * yt.units.pc / domain_width[2] + zvec

        # new list of point objects and new values
        pt_x_prime = [ ds.r[ [xprime[j].value,yprime[j].value,zprime[j].value]] for j in np.arange(N)]
        S_x_prime  = np.array([p[metal_field][0] for p in pt_x_prime])

        # compute coefficient
        corrcoeff[i] = pearsonr(S_x, S_x_prime)[0]

    return corrcoeff


# Estudiante: Dewins Murillo García
# Universidad Autónoma de Occidente
# Postgrado Inteligencia Artificial
# Entrega Preliminar:
# Proyectos de Inteligenciar Artificial
# Aplicando Ingeniería de Software

# RX.py (abstract)
# Esta clase tiene como objetivo procesar la imagen de acuerdo al tipo
# de imagen, pueden ser jpeg o dicom, además puede tratar la imagen
# a partir de un array

from PIL import Image
import numpy as np
import pydicom as dicom
import cv2
from abc import ABC,abstractmethod

class RX(ABC):
    @abstractmethod
    def read_file(self,path):
        pass

    @abstractmethod
    def read_file_from_array(self,pixel_array):
        pass

class DCM(RX):

    def read_file(self,path):
        img = dicom.read_file(path)
        img_array = img.pixel_array
        img2show = Image.fromarray(img_array)
        img2 = img_array.astype(float)
        img2 = (np.maximum(img2, 0) / img2.max()) * 255.0
        img2 = np.uint8(img2)
        img_RGB = cv2.cvtColor(img2, cv2.COLOR_GRAY2RGB)
        return img_RGB, img2show

    def read_file_from_array(self,pixel_array):
        img_array = pixel_array
        img2show = Image.fromarray(img_array)
        img2 = img_array.astype(float)
        img2 = (np.maximum(img2, 0) / img2.max()) * 255.0
        img2 = np.uint8(img2)
        img_RGB = cv2.cvtColor(img2, cv2.COLOR_GRAY2RGB)
        return img_RGB, img2show

class JPGPNG(RX):

    def read_file(self,path):
        img = cv2.imread(path)
        img_array = np.asarray(img)
        img2show = Image.fromarray(img_array)
        img2 = img_array.astype(float)
        img2 = (np.maximum(img2, 0) / img2.max()) * 255.0
        img2 = np.uint8(img2)
        return img2, img2show

    def read_file_from_array(self,pixel_array):
        img_array = pixel_array
        img2show = Image.fromarray(img_array)
        img2 = img_array.astype(float)
        img2 = (np.maximum(img2, 0) / img2.max()) * 255.0
        img2 = np.uint8(img2)
        return img2, img2show

class RXRead():
    def __init__(self,c:RX):
        self.client = c

    def read_file(self,path):
        self.array, img2show = self.client.read_file(path)
        return self.array, img2show

    def read_file_from_array(self,pixel_array):
        self.array, img2show = self.client.read_file_from_array(pixel_array)
        return self.array, img2show


import multiprocessing
import logging
import json
import os
import signal
from dotenv import load_dotenv

from .log import Log, LogMessage
from .courier import Courier

def _process_wrapper(process_id, _courier, *args, **kwargs):
    waitingProcess = True
    while waitingProcess:
        received = _courier.receive(wait=True)
        if received.subject != 'process':
            _courier.receiveQueue.put(received)
        else:
            waitingProcess = False
    process_function = received.message
    _courier.info(f"Starting Process - {process_id}")
    try:
        process_function(_courier, *args, **kwargs)
    except Exception as e:
        _courier.error(f"Process {process_id} failed with error: {e}")
        _courier.shutdown()

class Core:
    def __init__(self, log_level=logging.INFO, log_folder=None, log_file=None, log_environment=None, environment_file=None):
        self.log_level = log_level
        self.log_folder = log_folder
        self.log_file = log_file
        self.log_environment = log_environment
        self.environment_file = environment_file
        self._log_process_event = multiprocessing.Event()

        self._process_event = multiprocessing.Event()
        self._processes = {}
        self._process_count = 0

        signal.signal(signal.SIGINT, self._cancel_handler)

        self._setup_environment()

        self._courier = Courier("Core", self._process_event, multiprocessing.Event())
        self._log = Log(self._log_process_event, self.log_level, self.log_folder, self.log_file, self.log_environment)
        self._watcher_process = multiprocessing.Process(target=self.watcher)

        if self.log_environment is not None and self.environment_file is None:
            self._courier.log(logging.WARNING, "Environment File not Given - Use of Environment Will be Attempted")

    def _cancel_handler(self, signum, frame):
        self._process_event.set()
        self._log.log_queue.put(LogMessage("Core", f"Interrupt Received", Log._INFO))

    def _setup_environment(self):
        if self.environment_file is not None:
            if ".json" in self.environment_file:
                with open(self.environment_file, "r") as ej:
                    ejd = json.load(ej)
                    for key, value in ejd.items():
                        os.environ[str(key)] = str(value)
            elif ".env" in self.enviornment_file:
                load_dotenv(self.environment_file)
            else:
                self._courier.log(logging.ERROR, "Environment File Supplied Unknown Format - Environment File can either be .env or .json")

    def create_process(self, process_id, process_function, process_args=None, process_kwargs=None, force_terminate=False, worker_count=1):
        if process_args is None:
            process_args = []
        if process_kwargs is None:
            process_kwargs = {}
        receive_queue = None
        for i in range(worker_count):
            self._process_count += 1
            shutdown = multiprocessing.Event()
            courier = Courier(process_id, self._process_event, shutdown, self._log.log_queue, receive_queue)
            if receive_queue is None:
                receive_queue = courier.receiveQueue
                self.update_couriers(process_id, courier.receiveQueue)
            courier.add_send_queue(self._courier.id, self._courier.receiveQueue)
            for processData in self._processes.values():
                courier.add_send_queue(processData["receiver_id"], processData["courier"].receiveQueue)
            nprocess_id = process_id
            if worker_count > 1:
                nprocess_id = f"{process_id}_{i}"
                courier.set_identifier(nprocess_id)
            process_args.insert(0, courier)
            process_args.insert(0, nprocess_id)
            process = multiprocessing.Process(target=_process_wrapper, args=process_args, kwargs=process_kwargs)
            self._processes[nprocess_id] = {
                "process": process,
                "shutdown": shutdown,
                "courier": courier,
                "pid": None,
                "isShutdown": False,
                "forceTerminate": force_terminate,
                'function': process_function,
                'bulk': False if worker_count == 1 else True,
                'receiver_id': process_id
            }

    def update_couriers(self, newCourierId: str, newCourierQueue: multiprocessing.Queue):
        for processData in self._processes.values():
            processData["courier"].add_send_queue(newCourierId, newCourierQueue)
        self._courier.add_send_queue(newCourierId, newCourierQueue)

    def _check_shutdowns(self, terminate=False):
        for id, processData in self._processes.items():
            if not processData["isShutdown"] and processData["shutdown"].is_set():
                self._log.log_queue.put(LogMessage(id, f"Shutdown Received - {processData['courier'].id}", Log._INFO))
                self._process_event.set()
                self._process_count -= 1
                processData["isShutdown"] = True
            elif not processData["isShutdown"] and terminate and processData["forceTerminate"]:
                self._log.log_queue.put(LogMessage(id, f"Forcing Shutdown - {processData['courier'].id}", Log._INFO))
                self._process_event.set()
                self._process_count -= 1
                processData["isShutdown"] = True
                processData["process"].terminate()

    def send(self, receiver: str, subject:str, message=None):
        self._courier.send(receiver, subject, message)

    def watcher(self):
        while not self._process_event.is_set():
            try:
                self._check_shutdowns()
                message = self._courier.receive()
                if message is not None:
                    if message.subject == "PID":
                        self._processes[message.sender]["pid"] = message.message
            except (InterruptedError, KeyboardInterrupt):
                pass
        self._log.log_queue.put(LogMessage("Core", "Watcher Shutdown Complete", Log._INFO))
        force_shutdowns = [i["courier"].id for i in self._processes.values() if i["forceTerminate"]]
        if len(force_shutdowns) > 0:
            self._log.log_queue.put(LogMessage("Core", f"{force_shutdowns} Process Required to be Force Terminated - Shutting down Quietly", Log._INFO))
        self._log_process_event.set()

    def start(self):
        self._watcher_process.start()
        for processData in self._processes.values():
            processData["process"].start()
            self.send(processData["receiver_id"], subject='process', message=processData['function'])
        self._log.start() # Blocking Function, Exits on Shutdown Event
        while self._process_count != 0:
            try:
                self._check_shutdowns(True)
            except (InterruptedError, KeyboardInterrupt):
                pass


#!/usr/bin/python3

import serial

def monitor():
    ser = serial.Serial(COMPORT, BAUDRATE, timeout=0)
    f_name = input("Enter the file name : ")
    text_file = open(f_name, "w")
    text_file.close()
    while (1):
        val = ser.read().decode('windows-1252')
        if (val != b''):
             print(val,end='')
             text_file = open(f_name, "a")
             text_file.write(val)
             text_file.close()
    print("Stop Monitoring")

print("Start Serial Monitor")

COMPORT = "/dev/ttyUSB0";
BAUDRATE = 115200

monitor()


# Python episode N01
# challenge N03 => (Create Phone Number) - 6Kyu

# By ALGORITHM

def create_phone_number(n):
  n = ''.join(map(str,n))
  return "({}) {}-{}".format(n[:3],n[3:6],n[6:])

print(create_phone_number([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))
print(create_phone_number([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]))


def bubble_sort(arr):
    length = len(arr) - 1
    for i in range(length):
        isSort = False
        for j in range(length - i):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                isSort = True
        print(arr)
        if isSort == False:
            break

    return arr

def selection_sort(array):
    for i in range(len(array)):
        min_index = i  # 가장 작은 원소의 인덱스
        for j in range(i + 1, len(array)):
            if array[min_index] > array[j]:
                min_index = j
        array[i], array[min_index] = array[min_index], array[i]  # 스와프
        print(array)

def insertion_sort(array):
    for i in range(1, len(array)):
        for j in range(i, 0, -1):  # 인덱스 i부터 1까지 감소하며 반복
            if array[j] < array[j - 1]:  # 한 칸씩 왼쪽으로 이동
                array[j], array[j - 1] = array[j - 1], array[j]
            else:  # 자기보다 작은 데이터를 만나면 그 위치에서 멈춤
                break
        print(array)

def quick_sort(array):
    # 리스트가 하나 이하의 원소만을 담고 있다면 종료
    if len(array) <= 1:
        return array
    pivot = array[0]  # 피벗은 첫 번째 원소
    tail = array[1:]  # 피벗을 제외한 리스트

    left_side = [x for x in tail if x <= pivot]  # 분할된 왼쪽 부분
    right_side = [x for x in tail if x > pivot]  # 분할된 오른쪽 부분

    print(f"pivot : {pivot}")
    print(left_side + [pivot] + right_side)
    # 분할 이후 왼쪽 부분과 오른쪽 부분에서 각각 정렬 수행하고, 전체 리스트 반환
    return quick_sort(left_side) + [pivot] + quick_sort(right_side)

def merge_sort(array):
    # 두 부분으로 쪼개는 작업을 재귀적으로 반복
    if len(array) < 2:
        return array

    mid = len(array) // 2
    low_arr = merge_sort(array[:mid])
    high_arr = merge_sort(array[mid:])
    print("-" * 40)
    print(f"low_arr : {low_arr} , high_arr : {high_arr}")

    # 쪼갠 순서의 반대로 작은 값부터 병합
    merged_arr = []
    l = h = 0
    while l < len(low_arr) and h < len(high_arr):
        if low_arr[l] < high_arr[h]:
            merged_arr.append(low_arr[l])
            l += 1
        else:
            merged_arr.append(high_arr[h])
            h += 1
    merged_arr += low_arr[l:]
    merged_arr += high_arr[h:]
    print(f"merged_arr : {merged_arr}")
    return merged_arr

if __name__ == "__main__":
    array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]
    print("before: ", array)
    # bubble_sort(array)
    # selection_sort(array)
    # insertion_sort(array)
    # array = quick_sort(array)
    array = merge_sort(array)
    print("after: ", array)


from flask import Flask, jsonify
import time, requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By

URL = [
    'https://repositorio.unam.mx/contenidos?f=883.%23.%23.a_lit:Repositorio%20de%20la%20Direcci%C3%B3n%20General%20de%20Bibliotecas%20y%20Servicios%20Digitales%20de%20Informaci%C3%B3n'
]

app = Flask(__name__)

@app.route("/find/<word>")
def hello_world(word):
    options = webdriver.ChromeOptions()
    options.headless = True
    options.add_argument("window-size=1920x1080")
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-gpu')
    driver = webdriver.Chrome(options=options)
    driver.get('https://repositorio.unam.mx/contenidos?f=883.%23.%23.a_lit:Repositorio%20de%20la%20Direcci%C3%B3n%20General%20de%20Bibliotecas%20y%20Servicios%20Digitales%20de%20Informaci%C3%B3n')
    time.sleep(0.5)
    print('Buscando')
    search = driver.find_element(by=By.ID,value='input-search')
    search.send_keys(str(word))
    find = driver.find_element(by= By.ID, value='btn-general-buscar')
    find.click()
    time.sleep(0.5)
    print('resultados')
    document = driver.find_element(by= By.CLASS_NAME , value='element-ing-data-record' and 'img-portada')
    print('document')
    document.click()
    time.sleep(1.75)
    openforlink = driver.find_element(by= By.CLASS_NAME, value='cont-link-resource')
    openforlink.click()
    time.sleep(1)
    driver._switch_to.window(driver.window_handles[1])
    table = driver.find_element(by= By.CLASS_NAME, value='table' and 'itemDisplayTable').text
    preLink = table.split('\n')
    numberdocument = preLink[14].replace('Fuente TESIUNAM:  ', '').split('/')
    print(numberdocument)
    link = preLink[14].replace('Fuente TESIUNAM:  ', '').replace('Index.html', numberdocument[4]+'.pdf')
    des= requests.get(link)
    pdf = open(numberdocument[4]+str()+".pdf", 'wb')
    driver.implicitly_wait(10)
    pdf.write(des.content)
    pdf.close()
    driver.close()
    # return des.json()
    return "ok"