import torch
import torch.nn as nn
from torch.nn import Parameter
import torch.nn.functional as F
from torch.distributions.relaxed_categorical import RelaxedOneHotCategorical
# from prior_discriminator import PriorDiscriminator

class MacridVAE(nn.Module):
    def __init__(self, arg, num_items, device, aug=False):
        super(MacridVAE, self).__init__()

        kfac, dfac = arg.kfac, arg.dfac
        self.lam = arg.rg
        self.lr = arg.lr
        self.random_seed = arg.seed
        self.n_items = num_items
        self.arg = arg
        self.device = device
        self.aug = aug

        self.init_weight(kfac, dfac)

    def init_weight(self, kfac, dfac):
        self.enc_dims = [self.n_items, dfac, dfac]
        self.encoder = []
        proj_hid = self.arg.proj_hid

        if not self.aug:
            self.proj_head = nn.Sequential(
                nn.Linear(dfac, proj_hid),
                nn.ReLU(),
                # nn.Tanh(),
                nn.Linear(proj_hid, proj_hid)
                # nn.BatchNorm1d(kfac)
            )

            for m in self.proj_head:
                if isinstance(m, nn.Linear):
                    nn.init.xavier_uniform_(m.weight)
                    if m.bias is not None:
                        m.bias.data.fill_(0.0)

        # self.prior_d = PriorDiscriminator(self.arg)

        for i, (d_in, d_out) in enumerate(zip(self.enc_dims[:-1], self.enc_dims[1:])):
            if i == len(self.enc_dims[:-1]) - 1:
                d_out *= 2  # mu & var
            self.encoder.append(nn.Linear(d_in, d_out))
            nn.init.xavier_uniform_(self.encoder[-1].weight)
            nn.init.trunc_normal_(self.encoder[-1].bias, std=0.001)
            self.encoder.append(nn.Tanh())
        self.encoder = nn.Sequential(*self.encoder[:-1])

        # item representation h_i, maintained by the clustering & decoding part
        self.items = Parameter(torch.zeros((self.n_items, dfac)))

        # concept(facet) prototype m_i
        self.cores = Parameter(torch.zeros((kfac, dfac)))

        nn.init.xavier_uniform_(self.items)
        nn.init.xavier_uniform_(self.cores)

    def encode(self, input, is_train):
        cores = F.normalize(self.cores, dim=1)
        items = F.normalize(self.items, dim=1)
        cates = self.generate_cate(is_train, cores, items)
        z_list = []

        for k in range(self.arg.kfac):
            cates_k = cates[:, k].reshape(1, -1)

            x_k = input * cates_k
            h_k = F.normalize(x_k, dim=1)
            h_k = F.dropout(h_k, p=1 - self.arg.keep, training=is_train)
            h_k = self.encoder(h_k)

            mu_k = F.normalize(h_k[:, :self.arg.dfac], dim=1)
            lnvarq_sub_lnvar0 = -h_k[:, self.arg.dfac:]
            std_k = torch.exp(0.5 * lnvarq_sub_lnvar0) * self.arg.std
            epsilon = torch.randn(mu_k.size()).to(self.device)
            z_k = mu_k + epsilon * std_k

            z_k = F.normalize(z_k, dim=1)
            z_list.append(z_k)

        z = torch.stack(z_list, dim=0)
        z_proj = self.proj_head(z.transpose(0, 1))

        if is_train:
            return z_proj.transpose(0, 1)
        else:
            return z
        # return z

    def forward(self, input, output, is_train=False):
        cores = F.normalize(self.cores, dim=1)
        items = F.normalize(self.items, dim=1)
        cates = self.generate_cate(is_train, cores, items)
        z_list = []
        probs, kl = 0., 0.

        for k in range(self.arg.kfac):
            cates_k = cates[:, k].reshape(1, -1)
            # Encoder part
            x_k = input * cates_k

            h_k = F.normalize(x_k, dim=1)
            h_k = F.dropout(h_k, p=1 - self.arg.keep, training=is_train)
            h_k = self.encoder(h_k)

            mu_k = F.normalize(h_k[:, :self.arg.dfac], dim=1)
            lnvarq_sub_lnvar0 = -h_k[:, self.arg.dfac:]
            std_k = torch.exp(0.5 * lnvarq_sub_lnvar0) * self.arg.std
            kl_k = 0.5 * (-lnvarq_sub_lnvar0 + torch.exp(lnvarq_sub_lnvar0) - 1.).sum(dim=1).mean()

            epsilon = torch.randn(mu_k.size()).to(self.device)
            z_k = mu_k + (epsilon * std_k if is_train else 0)

            if not self.aug:
                # kl += self.prior_d(z_k)
                kl += kl_k

            # Decoder part
            z_k = F.normalize(z_k, dim=1)
            z_list.append(z_k)
            logits_k = torch.mm(z_k, items.T) / self.arg.tau
            probs_k = torch.exp(logits_k) * cates_k
            probs += probs_k

        logits = torch.log(probs)
        logits = F.log_softmax(logits, dim=1)
        recon_loss = torch.sum(-logits * output, dim=-1).mean()

        z = torch.stack(z_list, dim=0)

        if self.aug:
            return probs

        z_proj = self.proj_head(z.transpose(0, 1))

        return probs, recon_loss, kl, z_proj.transpose(0, 1)

    def generate_reg(self):
        return torch.sum(torch.Tensor([torch.norm(param) for param in self.parameters()]))

    def generate_cate(self, is_train, cores, items):
        # clustering
        cates_logits = torch.mm(items, cores.T) / self.arg.tau

        # Gumbel-Softmax sampling
        cates_sample = RelaxedOneHotCategorical(1, logits=cates_logits).sample()
        cates_mode = torch.softmax(cates_logits, dim=1)

        # categorial matrix C
        cates = cates_sample if is_train else cates_mode
        return cates


#!/usr/bin/env python
# mypy: ignore-errors
import aoc
import logging
import pprint

logger = logging.getLogger("Part")

class Part1(aoc.Part):
    @staticmethod
    def logic(inp):
        # parsing
        tiles = {}
        for line in inp:
            if "Tile" in line:
                current_tile_id = int(line[5:-1])
                tiles[current_tile_id] = {}
                y = 0
            else:
                for x, char in enumerate(line):
                    if char == "#":
                        tiles[current_tile_id][(x, y)] = 1
                    elif char == ".":
                        tiles[current_tile_id][(x, y)] = 0
                    else:
                        raise ValueError(f"Unexpected character {char}")
                y += 1

        # building edge list for each tile, moving in a clockwise direction
        # this includes reverse directions
        tile_edges = {}
        for tile_id, tile in tiles.items():
            edges = []
            edge = []

            # top edge
            for x in range(10):
                edge.append(tiles[tile_id][(x, 0)])
            edges.append(edge)
            edges.append(list(reversed(edge)))

            edge = []
            # bottom edge
            for x in range(9, -1, -1):
                edge.append(tiles[tile_id][(x, 9)])
            edges.append(edge)
            edges.append(list(reversed(edge)))

            edge = []
            # right edge
            for y in range(10):
                edge.append(tiles[tile_id][(9, y)])
            edges.append(edge)
            edges.append(list(reversed(edge)))

            edge = []
            # left edge
            for y in range(9, -1, -1):
                edge.append(tiles[tile_id][(0, y)])
            edges.append(edge)
            edges.append(list(reversed(edge)))

            tile_edges[tile_id] = edges

        # finding matched sides
        tile_matches = {}
        for tile_id, edges in tile_edges.items():
            matching_edges = 0
            for edge in edges:
                for match_tile_id, match_edges in tile_edges.items():
                    if edge in match_edges:
                        matching_edges += 1
            # logger.debug(f"Tile {tile_id} - Matching edges: {matching_edges}")
            tile_matches[tile_id] = matching_edges

        min_match_tile_ids = [
            tile_id
            for tile_id, match_number in tile_matches.items()
            if match_number == min(tile_matches.values())
        ]
        id_mult = 1
        # logger.debug(
        #     f"{len(min_match_tile_ids)} with match number of {min(tile_matches.values())}"
        # )
        for match_number in min_match_tile_ids:
            id_mult = id_mult * match_number
        return id_mult

def print_tile(tile_id, tile):
    print_str = f"Tile: {tile_id}\n"
    x_coords = [key[0] for key, value in tile.items()]
    y_coords = [key[1] for key, value in tile.items()]
    for y in range(min(y_coords), max(y_coords) + 1):
        for x in range(min(x_coords), max(x_coords) + 1):
            value = tile[(x, y)]
            if value == 1:
                print_str = print_str + "#"
            elif value == 0:
                print_str = print_str + "."
            else:
                raise ValueError(f"Unexpected value {value}")
        print_str = print_str + "\n"
    return print_str

class Part2(aoc.Part):
    @staticmethod
    def logic(inp):
        # parsing
        tiles = {}
        for line in inp:
            if "Tile" in line:
                current_tile_id = int(line[5:-1])
                tiles[current_tile_id] = {}
                y = 0
            else:
                for x, char in enumerate(line):
                    if char == "#":
                        tiles[current_tile_id][(x, y)] = 1
                    elif char == ".":
                        tiles[current_tile_id][(x, y)] = 0
                    else:
                        raise ValueError(f"Unexpected character {char}")
                y += 1

        # building edge list for each tile, moving in a clockwise direction
        # this includes reverse directions
        tile_edges = {}
        for tile_id, tile in tiles.items():
            edges = []

            # first letter is cardinal direction of edge (N, E, S, W)
            # second letter is normal or reverse (N, R)
            # top edge
            edge = []
            for x in range(10):
                edge.append(tiles[tile_id][(x, 0)])
            edges.append({"edge": edge, "orientation": 0, "flipped": False})
            flipped_edge = edge.copy()
            flipped_edge.reverse()
            edges.append({"edge": flipped_edge, "orientation": 0, "flipped": True})

            # bottom edge
            edge = []
            for x in range(9, -1, -1):
                edge.append(tiles[tile_id][(x, 9)])
            edges.append({"edge": edge, "orientation": 180, "flipped": False})
            flipped_edge = edge.copy()
            flipped_edge.reverse()
            edges.append({"edge": flipped_edge, "orientation": 180, "flipped": True})

            # right edge
            edge = []
            for y in range(10):
                edge.append(tiles[tile_id][(9, y)])
            edges.append({"edge": edge, "orientation": 90, "flipped": False})
            flipped_edge = edge.copy()
            flipped_edge.reverse()
            edges.append({"edge": flipped_edge, "orientation": 270, "flipped": True})

            # left edge
            edge = []
            for y in range(9, -1, -1):
                edge.append(tiles[tile_id][(0, y)])
            edges.append({"edge": edge, "orientation": 270, "flipped": False})
            flipped_edge = edge.copy()
            flipped_edge.reverse()
            edges.append({"edge": flipped_edge, "orientation": 90, "flipped": True})

            tile_edges[tile_id] = edges

        # Starting with the lowest ID tile and building orientation and location map
        tile_locations = {}
        starting_tile_id = min(tiles)
        unset_tile_ids = list(tile_edges.keys())
        unset_tile_ids.remove(starting_tile_id)
        set_tile_ids = [starting_tile_id]
        current_tile = {
            "coords": (0, 0),
            "orientation": 0,  # 0 is N, 90 is E, 180 is S
            "flipped": False,
        }
        tile_locations[starting_tile_id] = current_tile
        while len(unset_tile_ids) > 0:
            for index in range(len(set_tile_ids) - 1, -1, -1):
                current_tile_id = set_tile_ids[index]
                current_tile = tile_locations[current_tile_id]
                # logging.debug(f"Now matching {current_tile_id}")
                for edge in tile_edges[current_tile_id]:
                    if edge["flipped"] == current_tile["flipped"]:
                        for matching_tile_id in unset_tile_ids:
                            for matching_edge in tile_edges[matching_tile_id]:
                                if edge["edge"] == matching_edge["edge"]:
                                    # logger.debug(
                                    #     f"\nmatch {current_tile_id} ({edge})\n with {matching_tile_id} ({matching_edge})"
                                    # )

                                    orientation = (
                                        edge["orientation"]
                                        + matching_edge["orientation"]
                                        + current_tile["orientation"]
                                        + 180
                                    ) % 360

                                    if (
                                        edge["orientation"]
                                        + current_tile["orientation"]
                                    ) % 360 == 0:
                                        coords = (
                                            current_tile["coords"][0],
                                            current_tile["coords"][1] - 1,
                                        )
                                    elif (
                                        edge["orientation"]
                                        + current_tile["orientation"]
                                    ) % 360 == 90:
                                        coords = (
                                            current_tile["coords"][0] + 1,
                                            current_tile["coords"][1],
                                        )
                                    elif (
                                        edge["orientation"]
                                        + current_tile["orientation"]
                                    ) % 360 == 180:
                                        coords = (
                                            current_tile["coords"][0],
                                            current_tile["coords"][1] + 1,
                                        )
                                    elif (
                                        edge["orientation"]
                                        + current_tile["orientation"]
                                    ) % 360 == 270:
                                        coords = (
                                            current_tile["coords"][0] - 1,
                                            current_tile["coords"][1],
                                        )
                                    else:
                                        raise ValueError(current_tile["orientation"])

                                    flipped = not matching_edge["flipped"]
                                    tile_locations[matching_tile_id] = {
                                        "coords": coords,
                                        "orientation": orientation,
                                        "flipped": flipped,
                                    }
                                    set_tile_ids.append(matching_tile_id)
                                    unset_tile_ids.remove(matching_tile_id)
                del set_tile_ids[index]

        # Removing edges and building grid
        full_grid = {}
        mult = 8
        for tile_id, tile_info in tile_locations.items():
            tile = tiles[tile_id]
            if tile_info["flipped"]:
                tile = flip_tile_horizontal(tile)
            tile = rotate_tile_CW(tile, rotations=tile_info["orientation"] // 90)
            x_coords = [key[0] for key, value in tile.items()]
            y_coords = [key[1] for key, value in tile.items()]
            for y in range(min(y_coords) + 1, max(y_coords)):
                for x in range(min(x_coords) + 1, max(x_coords)):
                    x_val = x + tile_locations[tile_id]["coords"][0] * mult
                    y_val = y + tile_locations[tile_id]["coords"][1] * mult
                    full_grid[(x_val, y_val)] = tile[(x, y)]

        # normalizing grid to (0, 0) top left corner so rotations actually work
        x_shift = min([key[0] for key, value in full_grid.items()])
        y_shift = min([key[1] for key, value in full_grid.items()])
        full_grid = {
            (x - x_shift, y - y_shift): value for (x, y), value in full_grid.items()
        }

        # converting sea monster to dictionary coords
        # only need the # symbols
        sea_monster_str = [
            "                  # ",
            "#    ##    ##    ###",
            " #  #  #  #  #  #   ",
        ]
        sea_monster = {}
        for y, line in enumerate(sea_monster_str):
            for x, char in enumerate(line):
                if char == "#":
                    sea_monster[(x, y)] = 1

        # checking each orientation for sea monsters
        x_size = max([key[0] for key, value in full_grid.items()])
        y_size = max([key[1] for key, value in full_grid.items()])
        total_sea_monsters = 0
        for i in range(2):
            for j in range(4):
                for y in range(y_size + 1):
                    for x in range(x_size + 1):
                        monster = True
                        for monster_x, monster_y in sea_monster.keys():
                            value = full_grid.get((x + monster_x, y + monster_y), 0)
                            if value == 0:
                                monster = False
                        if monster:
                            total_sea_monsters += 1
                            logger.debug(
                                f"Found a sea monster in {i}, {j}. Total {total_sea_monsters}"
                            )

                # rotating for next round
                full_grid = rotate_tile_CW(full_grid)
            # flipping for next round
            full_grid = flip_tile_horizontal(full_grid)

        waves = sum(full_grid.values()) - total_sea_monsters * sum(sea_monster.values())
        return waves

def rotate_tile_CW(tile, rotations=1):
    rot_tile = tile.copy()
    temp_tile = tile.copy()
    for i in range(rotations):
        x_coords = [key[0] for key, value in temp_tile.items()]
        y_coords = [key[1] for key, value in temp_tile.items()]
        min_x = min(x_coords)
        max_x = max(x_coords)
        min_y = min(y_coords)
        max_y = max(y_coords)
        for y in range(min_y, max_y + 1):
            for x in range(min_x, max_x + 1):
                rot_tile[(max_y - y, x)] = temp_tile[(x, y)]
        temp_tile = rot_tile.copy()
    return rot_tile

def flip_tile_horizontal(tile):
    rot_tile = {}
    x_coords = [key[0] for key, value in tile.items()]
    y_coords = [key[1] for key, value in tile.items()]
    min_x = min(x_coords)
    max_x = max(x_coords)
    min_y = min(y_coords)
    max_y = max(y_coords)
    for y in range(min_y, max_y + 1):
        for x in range(min_x, max_x + 1):
            rot_tile[(max_x - x, y)] = tile[(x, y)]
    return rot_tile

if __name__ == "__main__":
    day_number = 20
    test_input = [
        "Tile 2311:",
        "..##.#..#.",
        "##..#.....",
        "#...##..#.",
        "####.#...#",
        "##.##.###.",
        "##...#.###",
        ".#.#.#..##",
        "..#....#..",
        "###...#.#.",
        "..###..###",
        "",
        "Tile 1951:",
        "#.##...##.",
        "#.####...#",
        ".....#..##",
        "#...######",
        ".##.#....#",
        ".###.#####",
        "###.##.##.",
        ".###....#.",
        "..#.#..#.#",
        "#...##.#..",
        "",
        "Tile 1171:",
        "####...##.",
        "#..##.#..#",
        "##.#..#.#.",
        ".###.####.",
        "..###.####",
        ".##....##.",
        ".#...####.",
        "#.##.####.",
        "####..#...",
        ".....##...",
        "",
        "Tile 1427:",
        "###.##.#..",
        ".#..#.##..",
        ".#.##.#..#",
        "#.#.#.##.#",
        "....#...##",
        "...##..##.",
        "...#.#####",
        ".#.####.#.",
        "..#..###.#",
        "..##.#..#.",
        "",
        "Tile 1489:",
        "##.#.#....",
        "..##...#..",
        ".##..##...",
        "..#...#...",
        "#####...#.",
        "#..#.#.#.#",
        "...#.#.#..",
        "##.#...##.",
        "..##.##.##",
        "###.##.#..",
        "",
        "Tile 2473:",
        "#....####.",
        "#..#.##...",
        "#.##..#...",
        "######.#.#",
        ".#...#.#.#",
        ".#########",
        ".###.#..#.",
        "########.#",
        "##...##.#.",
        "..###.#.#.",
        "",
        "Tile 2971:",
        "..#.#....#",
        "#...###...",
        "#.#.###...",
        "##.##..#..",
        ".#####..##",
        ".#..####.#",
        "#..#.#..#.",
        "..####.###",
        "..#.#.###.",
        "...#.#.#.#",
        "",
        "Tile 2729:",
        "...#.#.#.#",
        "####.#....",
        "..#.#.....",
        "....#..#.#",
        ".##..##.#.",
        ".#.####...",
        "####.#.#..",
        "##.####...",
        "##..#.##..",
        "#.##...##.",
        "",
        "Tile 3079:",
        "#.#.#####.",
        ".#..######",
        "..#.......",
        "######....",
        "####.#..#.",
        ".#...#.##.",
        "#.#####.##",
        "..#.###...",
        "..#.......",
        "..#.###...",
    ]

    part_1 = Part1(
        day_number=day_number,
        part=1,
        test_input=test_input,
        test_answer=20899048083289,
    )
    part_1.submit_answer()

    part_2 = Part2(
        day_number=day_number,
        part=2,
        test_input=test_input,
        test_answer=273,
    )
    part_2.submit_answer()


from Crypto.Cipher import AES
import hashlib
import os
import pathlib

def encrypt_file(key,filein,fileout=None,IV=None):
    modes = [1,2,8,3,6,9,11,12,10,5]
    if os.path.isfile(filein):
        if IV == None:
            IV = 16 * b'\x00'
        else:
            IV = IV.encode("utf-8")
        if len(IV)==16:
            if fileout == None:
                fileout_path = pathlib.Path(filein).parent
                fileout_name = pathlib.Path(filein).name
            else:
                fileout_path = pathlib.Path(fileout).parent
                fileout_name = pathlib.Path(fileout).name
                print (fileout_path, fileout_name )
                if os.path.exists(fileout_path) == False:
                    print("Path Does Not Exists")
                    return

            encryptor = AES.new(hashlib.sha256(key.encode("utf-8")).digest(), 3, IV=IV)
            with open(filein,"rb") as f :
                f = f.read()
                encr_bytes = encryptor.encrypt(f)
                file = open(str(fileout_path)+"\\"+str(fileout_name)+".enc","wb")
                file.write(encr_bytes)
                file.close()
                del encryptor
        else:
            print ("IV must 16 bytes long")
            return
    else:
        print("No file path")
        return

def dencrypt_file(key,filein,fileout=None,IV=None,TXT = False):
    if os.path.isfile(filein):
        if IV == None:
            IV = 16 * b'\x00'
        else:
            IV = IV.encode("utf-8")
        if len(IV)==16:
            if fileout == None:
                fileout_path = pathlib.Path(filein).parent
                fileout_name = pathlib.Path(filein).name
                list_name = fileout_name.split(".")
            else:
                fileout_path = pathlib.Path(fileout).parent
                fileout_name = pathlib.Path(fileout).name
                list_name =  fileout_name.split(".")
                if os.path.exists(fileout_path) == False:
                    print("Path Does Not Exists")
                    return
            file_name = list_name[0] + "." + list_name[1]
            if os.path.isfile(str(fileout_path)+"\\"+str(file_name)):
                file_name = list_name[0] + "new" +"." + list_name[1]
                print(file_name, "OK")
            else:
                file_name = file_name
            final_path = str(fileout_path) + "\\" +  file_name
            encryptor = AES.new(hashlib.sha256(key.encode("utf-8")).digest(), 3, IV=IV)
            with open(filein,"rb") as f :
                if TXT == False:
                    file = open(final_path,"wb")
                    file.write(encryptor.decrypt(f.read()))
                    file.close()
                else:
                    return encryptor.decrypt(f.read()).decode("utf-8")
        else:
            print ("IV must 16 bytes long")
            return
    else:
        print("No file path")
        return

# print(AES.MODE_SIV)
# print(dir(AES))
#
# #dencrypt_file("sss","C:\\Users\\john\\Desktop\\MISP.txt.enc",IV="dededebfbfbfbfbf")
# x = dencrypt_file("sss","C:\\Users\\john\\Desktop\\MISP1.txt.enc","C:\\Users\\john\\Desktop\\MISP.txt",IV="dededebfbfbfbfbf",TXT=True)
# print(x)
# #encrypt_file("sss","C:\\Users\\john\\De33sktop\\MIddSP", IV="dededebfbfbfbfbf")
# #encrypt_file("sss","C:\\Users\\john\\Desktop\\MISP1.txt", IV="dededebfbfbfbfbf")


import collections
from json_reader import read_matches

# extracting features from json
def extract_features_csv(match):
    row = [
        ('match_id_hash', match['match_id_hash']),
    ]

    for slot, player in enumerate(match['players']):
        if slot < 5:
            player_name = 'r%d' % (slot + 1)
        else:
            player_name = 'd%d' % (slot - 4)

        total_time_dead = 0

        if len(player['life_state']) == 3:
            total_time_dead = player['life_state']['2']
        else:
            total_time_dead = 0

        hero_dmg_columns = [c for c in player['damage'] if 'npc_dota_hero_' in c]
        hero_damage = sum([player['damage'][i] for i in hero_dmg_columns])

        hero_dmg_received_cols = [c for c in player['damage_taken'] if 'npc_dota_hero_' in c]
        hero_damage_received = sum([player['damage_taken'][i] for i in hero_dmg_received_cols])

        row.append((f'{player_name}_ability_level', len(player['ability_upgrades'])))
        row.append((f'{player_name}_max_hero_hit', player['max_hero_hit']['value']))
        row.append((f'{player_name}_purchase_count', len(player['purchase_log'])))
        row.append((f'{player_name}_count_ability_use', sum(player['ability_uses'].values())))
        row.append((f'{player_name}_abs_damage_dealt', sum(player['damage'].values())))
        row.append((f'{player_name}_abs_damage_received', sum(player['damage_taken'].values())))

        row.append((f'{player_name}_hero_damage_dealt', hero_damage))
        row.append((f'{player_name}_hero_damage_received', hero_damage_received))

        row.append((f'{player_name}_time_dead', total_time_dead))

    return collections.OrderedDict(row)

def create_features_from_jsonl(matches_file):
    df_new_features = []

    # Process raw data and add new features
    for match in read_matches(matches_file):
        features = extract_features_csv(match)

        df_new_features.append(features)

    df_new_features = pd.DataFrame.from_records(df_new_features, index='match_id_hash')
    return df_new_features

def create_new_player_features(train_data, test_data, path_to_data):
    train_new_player_features = create_features_from_jsonl(os.path.join(path_to_data, 'train_matches.jsonl')).fillna(0)
    test_new_player_features = create_features_from_jsonl(os.path.join(path_to_data, 'test_matches.jsonl')).fillna(0)

    train_features = pd.concat([train_data, train_new_player_features], axis=1)
    test_features = pd.concat([test_data, test_new_player_features], axis=1)

    return train_features, test_features


#https://docs.opencv.org/4.5.3/d6/d00/tutorial_py_root.html
#https://docs.opencv.org/4.5.3/dd/d43/tutorial_py_video_display.html "Playing Video from file "

from __future__ import print_function

import argparse
import numpy as np
import cv2

import matplotlib.pyplot as plt

def computeMSE(prev, curr):
    assert(curr.shape == prev.shape)
    h, w = curr.shape[:2]
    return np.sum(np.square(curr.astype(np.int32) - prev.astype(np.int32))) / (h * w)

def computeMSE_test():
    black = np.zeros([10, 10])
    white = np.ones([10, 10]) * 255

    assert(computeMSE(black, black) == 0.0)
    assert(computeMSE(white, white) == 0.0)
    assert(computeMSE(white, black) == np.square(255))
    print("MSE test passed!")

def computePSNR(mse):
    if mse <= 0 :
        return 0
    return 10 * np.log10(np.square(255) / mse)

def computePSNR_test(): # assumes that computeMSE works correctly
    black = np.zeros([10, 10])
    white = np.ones([10, 10]) * 255
    assert(computePSNR(computeMSE(white, black)) == 0.0)
    print("PSNR test passed!")

def computeEntropy(img):
    hist, _ = np.histogram(img, 256, [0, 255])
    p = hist / img.size # probability histogram
    return -np.sum(p * np.log2(p, where=p!= 0))

def computeEntropy_test():
    assert(computeEntropy(np.zeros([10, 10])) == 0.0)
    img = np.concatenate((np.zeros([5, 5]), np.ones([5, 5])))
    assert(computeEntropy(img) == 1.0)
    print("Entropy test passed!")

def computeErrorImage(im1, im2):
    return np.minimum(255, np.maximum(0, im1 - im2 + 128))
    # return im1

def computeOpticalFlow1(prev, curr):
    flow = cv2.calcOpticalFlowFarneback(curr, prev, flow=None, pyr_scale=0.5, levels=3, winsize=20, iterations=15, poly_n=5, poly_sigma=1.2, flags=cv2.OPTFLOW_FARNEBACK_GAUSSIAN)
    return flow

def computeCompensatedFrame(prev, flow):
    h, w = flow.shape[:2]
    map = flow.copy()
    map[:,:,0] += np.arange(w)
    map[:,:,1] += np.arange(h)[:,np.newaxis]
    res = cv2.remap(prev, map, None, cv2.INTER_LINEAR)
    return res

def computeGME(flow): #flow = vecteurs de déplacement
    src = np.zeros_like(flow)
    h, w = flow.shape[:2]
    c = np.array([w/2, h/2])
    src[:,:,0] += np.arange(w)
    src[:,:,1] += np.arange(h)[:,np.newaxis]
    src -= c;
    srcPts = src.reshape((h*w, 2))

    dst = src + flow
    dstPts = dst.reshape((h*w, 2))
    H, mask = cv2.findHomography(srcPts, dstPts, cv2.RANSAC)
    gme = cv2.perspectiveTransform(src, H) - src
    return gme

def computeGMEError(flow, gme):
    h, w = flow.shape[:2]
    flowX, flowY = np.dsplit(flow, 2)
    gmeX, gmeY = np.dsplit(gme, 2)

    err = np.sqrt(np.square(flowX - gmeX) +
                  np.square(flowY - gmeY))

    return err / np.max(err)

def draw_flow(img, flow, step=16):
    h, w = img.shape[:2]
    y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2,-1).astype(int)
    fx, fy = flow[y,x].T
    lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2)
    lines = np.int32(lines + 0.5)
    vis = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    cv2.polylines(vis, lines, 0, (0, 255, 0))
    for (x1, y1), (_x2, _y2) in lines:
        cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)
    return vis

if __name__=='__main__':

    # computeMSE_test()
    # computePSNR_test()
    # computeEntropy_test()
    # assert False

    parser = argparse.ArgumentParser(description='Read video file')
    parser.add_argument('video', help='input video filename')
    parser.add_argument('deltaT', help='input deltaT between frames', type=int)

    args = parser.parse_args()

    cap = cv2.VideoCapture(args.video)

    if (cap.isOpened() == False):
        print("ERROR: unable to open video: "+args.video)
        quit()

    deltaT=args.deltaT

    previousFrames=[]
    frameNumbers = []
    mses = []
    psnrs = []
    mse0s = []
    psnr0s = []
    ents = []
    entEs = []

    i=0
    while(cap.isOpened()):
        ret, frame = cap.read()

        if (ret==False):
            break

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        if (len(previousFrames) >= deltaT):
            prev = previousFrames.pop(0)

            flow = computeOpticalFlow1(prev, gray)

            compensatedFrame = computeCompensatedFrame(prev, flow)

            #cv2.imshow('compensated', compensatedFrame)

            imErr0 = computeErrorImage(prev, gray)
            imErr = computeErrorImage(compensatedFrame, gray)

            # cv2.imshow('imErr0', imErr0)
            # cv2.imshow('imErr', imErr)

            mse0 = computeMSE(prev, gray)
            psnr0 = computePSNR(mse0)
            mse = computeMSE(compensatedFrame, gray)
            psnr = computePSNR(mse)
            ent = computeEntropy(gray)
            entE = computeEntropy(imErr)

            frameNumbers.append(i)
            mses.append(mse)
            psnrs.append(psnr)
            mse0s.append(mse0)
            psnr0s.append(psnr0)
            ents.append(ent)
            entEs.append(entE)

            gme = computeGME(flow)

            gmeError = computeGMEError(flow, gme)

            # cv2.imshow('flow', draw_flow(gray, flow))
            # cv2.imshow('gme', draw_flow(gray, gme))
            # cv2.imshow('gmeError', gmeError)

        previousFrames.append(gray.copy())
        i+=1

        # cv2.imshow('frame', gray)

        cv2.waitKey(1)

    plt.plot(frameNumbers, mse0s, label='MSE0')
    plt.plot(frameNumbers, mses, label='MSE')
    plt.xlabel('frames')
    plt.ylabel('MSE')
    plt.legend()
    plt.title('MSE0 vs MSE')
    plt.savefig("mse.png")
    # plt.show()
    plt.cla()

    plt.plot(frameNumbers, ents, label='Entropy)')
    plt.plot(frameNumbers, entEs, label='EntropyE')
    plt.xlabel('frames')
    plt.ylabel('Entropy')
    plt.legend()
    plt.title('Entropy vs EntropyE')
    plt.savefig("entropy.png")
    # plt.show()
    plt.cla()

    plt.plot(frameNumbers, psnr0s, label='PSNR0')
    plt.plot(frameNumbers, psnrs, label='PSNR')
    plt.xlabel('frames')
    plt.ylabel('PSNR')
    plt.legend()
    plt.title('PSNR0 vs PSNR')
    plt.savefig("psnr.png")
    # plt.show()
    plt.cla()

    cap.release()
    cv2.destroyAllWindows()


import numpy as np
import cv2
import mahotas as mt
import os
import pandas as pd
from math import tan

#se declara varible que contien la carpeta donde se tienes las imagenes capturadas
pathInput = "D:\\PROGRAMA_TESIS\\Recorte_imagen"
#nombreImagenes me permite conocer el nombre de los archivos que se tienen en la carpeta de pathInput
nombreImagenes = os.listdir(pathInput)
#se declara varible que contien la carpeta donde se guardaran las imagenes segmentadas
pathOutput = "D:\\PROGRAMA_TESIS\\segmentada"
#Se crea ciclo for del tamaño de elementos de las imagenes_procesamiento para
#Que recorra todas las imagenes y haga el procesamiento
def coordenadasxy(imagen):
    imagenHeight = imagen.shape[0]
    imagenWidth = imagen.shape[1]
    CX = int(imagenHeight/2)
    CY = int(imagenWidth/2)
    CoordenadasPixeles = []
    for i in range(imagenHeight):
        for j in range(imagenWidth):
            if imagen[i][j] != 0:
                features = { # caracteristicas cambiando el centro y modificando la distancia al centro
                    "x": (j - CY),
                    "y": (CX - i)
                }
                # features = {
                #     "x": (i),
                #     "y": (j)
                # }
                CoordenadasPixeles.append(features)
    return CoordenadasPixeles

def coordenadasXYZ(coordenasxy):
    #Datos constante Calculados del experimento
    angulo=69.44
    angulo=np.radians(angulo)
    f=2087
    b=7.5
    cot=1/tan(angulo)

    coordenadasXYZ = []
    for i in range(len(coordenasxy)):
        caracteristicasPixel = coordenasxy[i]
        x = caracteristicasPixel["x"]
        y = caracteristicasPixel["y"]

        X = (b/(f*(cot)-x))*x
        Y = (b/(f*(cot)-x))*y
        Z = (b/(cot)-x)

        features = {
            "X": X,
            "Y": Y,
            "Z": Z
        }
        coordenadasXYZ.append(features)
    return coordenadasXYZ

for i in range(len(nombreImagenes)):
    #pathImagen contiene la dirección y nombre de cada imagen contenidas en imagenes_procesamiento
    pathImagen = os.path.join(pathInput,nombreImagenes[i])
    #pathImagenOut contiene la dirección y nombre de cada imagen contenidas que se almacenara en segmentada
    pathImagenOut = os.path.join(pathOutput,nombreImagenes[i])
    # imagen se creo para pasar las imagenes de imagenes_procesamiento a RGB

    imagen = cv2.imread(pathImagen,cv2.COLOR_BGR2RGB)
    #imagenGray se creo para pasar las imagenes de imagenes_procesamiento a escalas de grises
    imagenGray = cv2.cvtColor(imagen,cv2.COLOR_RGB2GRAY)
    imagenGray = cv2.GaussianBlur(imagenGray,(5,5),0.5) # suavizar bordes con filtro gaussiano
    #threshold crea un umbral Si el valor del píxel es menor que el umbral, se establece en 0; de lo contrario, se establece en un valor máximo
    ret, imagenBinaria = cv2.threshold(imagenGray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    #kernel
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(10,10))
    kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(10,10))
    #Operaacion morfologica elimina el ruido de la imagen
    imagenBinaria = cv2.morphologyEx(imagenBinaria,cv2.MORPH_CLOSE,kernel)
    imagenBinaria = cv2.morphologyEx(imagenBinaria,cv2.MORPH_ERODE,kernel2)
    imagenBinaria = mt.thin(imagenBinaria)*255
    #Se escoje la imagen binaria y se le agrea la mascara del procesamiento
    imagenSegmentada = cv2.bitwise_and(imagenGray,imagenGray,mask=imagenBinaria)
    #se guardan las imagenes esqueletizadas
    ############### Extraccion de caracteristicas ###################
    caracteristicasImagen = coordenadasxy(imagenBinaria)
    print(caracteristicasImagen)
    caracteristicasEspaciales = coordenadasXYZ(caracteristicasImagen)
    # # Creacion del archivo CSV para mesh lab
    df = pd.DataFrame(caracteristicasEspaciales)
    print(df.head())
    print(df.tail())
    cv2.imwrite(pathImagenOut,imagenBinaria)
    # # Visualizacion
    imagenRectangulo = np.zeros((imagenBinaria.shape[0],imagenBinaria.shape[1]),dtype=np.uint8)


from unittest.mock import patch

from django.test import TestCase
from users.models import User
from django.core import mail
from http import HTTPStatus as status

class UserTests(TestCase):
    def test_get_absolute_url_method(self):
        user = User(username='Mohammed', email='mr-m@yahoo.com')
        user.save()
        response = self.client.get(user.get_absolute_url())
        self.assertEqual(response.status_code, status.OK)

    @patch('django.core.mail.message.DNS_NAME', 'www.volla.co')
    def test_email_user(self):
        user = User(username='Missy Elliot', email='missy@gmail.com')
        user.save()
        self.assertEqual(len(mail.outbox), 0)
        user.email_user('Yo', 'Keep it real girl')
        self.assertEqual(len(mail.outbox), 1)
        email = mail.outbox[0]
        self.assertEqual(email.to, ['missy@gmail.com'])
        self.assertEqual(email.from_email, 'support@volla.co')


from django.conf.urls import patterns, include, url
from django.contrib.auth.decorators import login_required
from django.contrib import admin
from app import views

from app.views import LatestEntriesFeed

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'legalsuff.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    url(r'^$', 'app.views.home', name='home'),
    url(r'^rss/$', LatestEntriesFeed()),
    url(r'^view/$', 'app.views.view_documents', name='view_documents'),
    url(r'^view/(?P<pk>[\w|-]+)/$', 'app.views.print_documents', name='print_documents'),
    url(r'^published/$', 'app.views.published_query', name='published_query'),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^search/$', 'app.views.search', name='search'),
    url(r'^document/$', 'app.views.new_document', name='new_document'),
    url(r'^document/(?P<pk>[\w|-]+)/$', login_required(views.DocumentUpdate.as_view()), name='document-detail'),
    url(r'^accounts/login/$', 'django.contrib.auth.views.login', {'redirect_field_name':'/'}),
    url(r'^logout/$', 'django.contrib.auth.views.logout',
                          {'next_page': '/'}),
    url(r'^redactor/', include('redactor.urls')),
)


import numpy as np
import os
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.metrics import accuracy_score,f1_score
import warnings
import os

def diverse_samples(inst_annot,full,Path):
    path = os.path.join(Path,"Diverse_Samples.png")
    unique, frequency = np.unique(inst_annot.sum(1),return_counts=True)
    unique = unique.tolist()
    frequency = frequency.tolist()
    num = [int(i) for i in unique]
    num.append(inst_annot.shape[1])
    frequency.append(full)
    print(num,frequency)

    fig = plt.figure(figsize = (10, 5))

    # creating the bar plot
    plt.bar(num, frequency, color ='yellow',
            width = 0.4)

    plt.xlabel("No. of Annotators Queried")
    plt.ylabel("No. of Instances")
    plt.title("Diversity of samples")
    plt.savefig(path)
    plt.show()

def Knowledge_Base_Metrics(Knowledge_Base,similar_instances,new_active_y,Parent_dir):

    data_KB = [{} for i in range(len(Knowledge_Base.keys()))]
    data_Sim = [{} for i in range(len(similar_instances.keys()))]
    # df_c_KB = pd.DataFrame(data_c_KB,index = ['Annotator Model', 'W Optimal', 'Majority','True Labels'])
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore")
        print('\nKNOWLEDGE BASE METRIC')
        i = 0
        for key in Knowledge_Base:
            i = i + 1
            ground_truth = []
            for idx in Knowledge_Base[key]['index']:
                ground_truth.append(new_active_y.loc[idx])
            print('\nSize of Annotator Knowledge Base ',i,' : ',len(ground_truth))
            print('Accuracy Score of Annotator Knowledge Base ',i," : ",accuracy_score(Knowledge_Base[key]['label'],ground_truth))
            print('F1 Score of Annotator Knowledge Base ',i," : ",f1_score(Knowledge_Base[key]['label'],ground_truth))
            data_KB[i-1]['No. of Instances'] = len(ground_truth)
            data_KB[i-1]['Accuracy'] = accuracy_score(Knowledge_Base[key]['label'],ground_truth)
            data_KB[i-1]['f1-score'] = f1_score(Knowledge_Base[key]['label'],ground_truth)

        df_KB = pd.DataFrame(data_KB, index = Knowledge_Base.keys())

        print('\n\nSIMILAR INSTANCES METRIC')
        i = 0
        for inst in similar_instances:
            i = i + 1
            ground_truth = []
            for idx in similar_instances[inst]['index']:
                ground_truth.append(new_active_y.loc[idx])
            labels = [similar_instances[inst]['label'] for j in range(len(ground_truth))]
            print('\nNumber of similar instances to instance ',inst," : ",len(ground_truth))
            print('Accuracy Score of instances similar to instance ',inst," : ",accuracy_score(labels,ground_truth))
            print('F1 Score of instances similar to instance ',inst," : ",f1_score(labels,ground_truth))
            data_Sim[i-1]['No. of similar Instances'] = len(ground_truth)
            data_Sim[i-1]['Accuracy'] = accuracy_score(labels,ground_truth)
            data_Sim[i-1]['f1-score'] = f1_score(labels,ground_truth)
        df_Sim = pd.DataFrame(data_Sim, index = similar_instances.keys())

        dir1 = "Knowledge_Base_Metrics.csv"
        dir2 = "Similar_Instances_Metrics.csv"

        path1 = os.path.join(Parent_dir,dir1)
        path2 = os.path.join(Parent_dir,dir2)

        df_KB.to_csv(path1)
        df_Sim.to_csv(path2)


# import the necessary packages
from pyimagesearch.indexer.baseindexer import BaseIndexer
import numpy as np
import h5py
#[12-11-2016] added additional data in the HDF5 DB
#[28-11-2016] added title as new scraped data
#[26-12-2016] added option to append data to an existing feature file

class FeatureIndexer(BaseIndexer): #is inherited from the Baseindexer

	def __init__(self, dbPath, appendSwitch, estNumImages=500, maxBufferSize=50000, dbResizeFactor=2,
		verbose=True ): #[26-12-2016 +++] append boolean added if data needs to be added to file
                # call the parent constructor
                #print(dbPath)
                super(FeatureIndexer, self).__init__(dbPath, estNumImages=estNumImages,
                        maxBufferSize=maxBufferSize, dbResizeFactor=dbResizeFactor,
                        verbose=verbose)

                #[26-12-2016 +++] initialize the db dataset with existing set if append and none if new
                if appendSwitch == True:
                # open the HDF5 database for writing and initialize the datasets within
                # the group
                        self.db = h5py.File(self.dbPath, mode="a") #[26-12-2016 +++] changed mode from w to a

                        self.imageIDDB = self.db["image_ids"]
                        self.indexDB = self.db["index"]
                        self.featuresDB = self.db["features"]
                        self.dataDB = self.db["addData"]

                        nbImagesIdx = self.db["image_ids"].shape[0] #Select nb image_ids https://gurus.pyimagesearch.com/lessons/extracting-keypoints-and-local-invariant-descriptors/
                        nbFeaturesIdx = self.db["features"].shape[0] #Select nb of rows in the image_ids. Same reference
                        self.idxs = {"index": nbImagesIdx, "features": nbFeaturesIdx}

                else:
                # open the HDF5 database for writing and initialize the datasets within
                # the group
                        self.db = h5py.File(self.dbPath, mode="w")

                        self.imageIDDB = None
                        self.indexDB = None
                        self.featuresDB = None
                        self.dataDB = None #+++[12-11-2016] - added additional data field

                        self.idxs = {"index": 0, "features": 0}
                #[26-12-2016 ---]

                # initialize the image IDs buffer, index buffer and the keypoints +
                # features buffer
                self.imageIDBuffer = []
                self.indexBuffer = []
                self.featuresBuffer = None
                self.dataBuffer = [] #+++[12-11-2016] - added additional data field

                # initialize the total number of features in the buffer along with the
                # indexes dictionary
                self.totalFeatures = 0
                #+++ [12-11-2016] added additional data in the HDF5 DB (affiliate link)
	def add(self, imageID, kps, features, additionalData):
		# compute the starting and ending index for the features lookup
		# the additional data is the ASIN code, and the title provided
		# as an additionalData list and stored in the DB
		start = self.idxs["features"] + self.totalFeatures
		end = start + len(features)

		# update the image IDs buffer, features buffer, and index buffer,
		# followed by incrementing the feature count
		self.imageIDBuffer.append(imageID)
		self.featuresBuffer = BaseIndexer.featureStack(np.hstack([kps, features]),
			self.featuresBuffer)
		self.indexBuffer.append((start, end))
		self.dataBuffer.append(additionalData)#+++[12-11-2016] added additional data in the HDF5 DB (affiliate link)

		self.totalFeatures += len(features)

		# check to see if we have reached the maximum buffer size
		if self.totalFeatures >= self.maxBufferSize:
			# if the databases have not been created yet, create them
			if None in (self.imageIDDB, self.indexDB, self.featuresDB, self.dataDB):#+++[12-11-2016] added additional data in the HDF5 DB
				self._debug("initial buffer full")
				self._createDatasets()

			# write the buffers to file
			self._writeBuffers()

	def _createDatasets(self):
		# compute the average number of features extracted from the initial buffer
		# and use this number to determine the approximate number of features for
		# the entire dataset
		avgFeatures = self.totalFeatures / float(len(self.imageIDBuffer))
		approxFeatures = int(avgFeatures * self.estNumImages)

		# grab the feature vector size
		fvectorSize = self.featuresBuffer.shape[1]

		# initialize the datasets
		self._debug("creating datasets...")
		self.imageIDDB = self.db.create_dataset("image_ids", (self.estNumImages,),
			maxshape=(None,), dtype=h5py.special_dtype(vlen=str))
		self.indexDB = self.db.create_dataset("index", (self.estNumImages, 2),
			maxshape=(None, 2), dtype="int")
		self.featuresDB = self.db.create_dataset("features",
			(approxFeatures, fvectorSize), maxshape=(None, fvectorSize),
			dtype="float")
		#+++[12-11-2016] - added additional data field containing only 1 data - the affiliate URL
		self.dataDB = self.db.create_dataset("addData", (self.estNumImages,),
			maxshape=(None,), dtype=h5py.special_dtype(vlen=str))
		#---[12-11-2016] - added additional data ASIN field

	def _writeBuffers(self):
		# write the buffers to disk
		self._writeBuffer(self.imageIDDB, "image_ids", self.imageIDBuffer,
			"index")
		self._writeBuffer(self.indexDB, "index", self.indexBuffer, "index")
		self._writeBuffer(self.featuresDB, "features", self.featuresBuffer,
			"features")
		#+++[12-11-2016] added additional data in the HDF5 DB (affiliate link)
		self._writeBuffer(self.dataDB, "addData", self.dataBuffer, "index")
		#---[12-11-2016] added additional data in the HDF5 DB (affiliate link)
		# increment the indexes
		self.idxs["index"] += len(self.imageIDBuffer)
		self.idxs["features"] += self.totalFeatures

		# reset the buffers and feature counts
		self.imageIDBuffer = []
		self.indexBuffer = []
		self.featuresBuffer = None
		self.dataBuffer = [] #+++[12-11-2016] added additional data in the HDF5 DB (affiliate link)
		self.totalFeatures = 0

	def finish(self):
		# if the databases have not been initialized, then the original
		# buffers were never filled up
		if None in (self.imageIDDB, self.indexDB, self.featuresDB, self.dataDB):#+++[12-11-2016] added additional data in the HDF5 DB (affiliate link)
			self._debug("minimum init buffer not reached", msgType="[WARN]")
			self._createDatasets()

		# write any unempty buffers to file
		self._debug("writing un-empty buffers...")
		self._writeBuffers()

		# compact datasets
		self._debug("compacting datasets...")
		self._resizeDataset(self.imageIDDB, "image_ids", finished=self.idxs["index"])
		self._resizeDataset(self.indexDB, "index", finished=self.idxs["index"])
		self._resizeDataset(self.featuresDB, "features", finished=self.idxs["features"])
		self._resizeDataset(self.dataDB, "addData", finished=self.idxs["index"])#+++[12-11-2016] added additional data in the HDF5 DB (affiliate link)

		# close the database
		self.db.close()


import pandas as pd
import json
from understat import Understat
import asyncio
import aiohttp
import time
import pickle
from understat_players import Player
import requests

API_URL = "https://fantasy.premierleague.com/api/fixtures/"

fpl_to_understat= {1:'Arsenal',
2: 'Aston Villa',
3:'Bournemouth',
4: 'Brighton',
5: 'Burnley',
6: 'Chelsea',
7: 'Crystal Palace',
8: 'Everton',
9: 'Leicester',
10: 'Liverpool',
11: 'Manchester City',
12: 'Manchester United',
13: 'Newcastle United',
14: 'Norwich',
15: 'Sheffield United',
16: 'Southampton',
17: 'Tottenham',
18: 'Watford',
19: 'West Ham',
20: 'Wolverhampton Wanderers'}

difficulty = pd.read_csv('EPL_Fixturelist_1920.csv')

class Team():

	def __init__(self, team, players):
		self.club = team
		self.players = players
		player_list = list(players.values())
		self.team_num = player_list[0].player['team']

	def calculate_xg_xga(self, understat_data):
		self.xg_for = {i:0 for i in range(2,6)}
		self.xga = {i:0 for i in range(2,6)}
		self.games_played = {i:0 for i in range(2,6)}
		for index, row in understat_data.iterrows():
			if row['side'] == 'h':
				self.xg_for[difficulty[difficulty['Team'] == row['a']['title']]['Home'].values[0]] += float(row['xG']['h'])
				self.xga[difficulty[difficulty['Team'] == row['a']['title']]['Home'].values[0]] += float(row['xG']['a'])
				self.games_played[difficulty[difficulty['Team'] == row['a']['title']]['Home'].values[0]] += 1
			elif row['side'] == 'a':
				self.xg_for[difficulty[difficulty['Team'] == row['h']['title']]['Away'].values[0]] += float(row['xG']['a'])
				self.xga[difficulty[difficulty['Team'] == row['h']['title']]['Away'].values[0]] += float(row['xG']['h'])
				self.games_played[difficulty[difficulty['Team'] == row['h']['title']]['Away'].values[0]] += 1
			else:
				raise ValueError('Team not found')
		for key in self.xg_for:
			self.xg_for[key] = self.xg_for[key]/self.games_played[key]
			self.xga[key] = self.xga[key]/self.games_played[key]
		self.xga_to_players()

	def xga_to_players(self):
		for player in self.players.values():
			player.add_xga(self.xga)

	def add_fixtures(self, fixture_list):
		self.fixtures = []
		for i, fixture in fixture_list.iterrows():
			if fixture['team_h'] == self.team_num:
				self.fixtures.append(difficulty[difficulty['Team'] ==  fpl_to_understat[fixture['team_a']]]['Home'].values[0])
			elif fixture['team_a'] == self.team_num:
				self.fixtures.append(difficulty[difficulty['Team'] ==  fpl_to_understat[fixture['team_h']]]['Away'].values[0])
			else:
			 	continue

def get_fixtures():
	r = requests.get(API_URL)
	data = json.dumps(r.json())
	data_df = pd.read_json(data, orient = 'records')
	data_df.to_csv('fixtures_list.csv')
	return data_df

async def get_team_data(team):
	async with aiohttp.ClientSession() as session:
		understat = Understat(session)
		team_json = await understat.get_team_results(team, '2018')
		data = json.dumps(team_json)
		data_df = pd.read_json(data, orient='records')
		return data_df

def main():
	with open('players_xg_xa.pickle', 'rb') as handle:
		players_dict = pickle.load(handle)
	loop = asyncio.get_event_loop()
	teams = dict()
	fixtures = get_fixtures()
	for team in players_dict.keys():
		team_matches = loop.run_until_complete(get_team_data(team))
		new_team = Team(team, players_dict[team])
		new_team.calculate_xg_xga(team_matches)
		new_team.add_fixtures(fixtures)
		teams[team] = new_team
		print(team)
	with open('teams_data.pickle', 'wb') as handle:
		pickle.dump(teams, handle, protocol=pickle.HIGHEST_PROTOCOL)

if __name__ == "__main__":
	main()


import numpy as np
import torch
import torchvision
import torch.nn as nn
import h5py
import time
import random
import torchvision.transforms as transforms
from torch.autograd import Variable
import torch.nn.functional as F
import torchvision.models as models

transform_train = transforms.Compose([
    transforms.RandomResizedCrop(32, scale=(0.7, 1.0), ratio=(1.0,1.0)),
    transforms.ColorJitter(
            brightness=0.1*torch.randn(1),
            contrast=0.1*torch.randn(1),
            saturation=0.1*torch.randn(1),
            hue=0.1*torch.randn(1)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])

transform_test = transforms.Compose([
    transforms.CenterCrop(32),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])

class discriminator(nn.Module):
    def __init__(self):
        super(discriminator, self).__init__()
        self.main = nn.Sequential(
            nn.Conv2d(3, 196, kernel_size=3, stride=1, padding=1),
            nn.LayerNorm((196, 32, 32)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=2, padding=1),
            nn.LayerNorm((196, 16, 16)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=1, padding=1),
            nn.LayerNorm((196, 16, 16)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=2, padding=1),
            nn.LayerNorm((196, 8, 8)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=1, padding=1),
            nn.LayerNorm((196, 8, 8)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=1, padding=1),
            nn.LayerNorm((196, 8, 8)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=1, padding=1),
            nn.LayerNorm((196, 8, 8)),
            nn.LeakyReLU(0.1),

            nn.Conv2d(196, 196, kernel_size=3, stride=2, padding=1),
            nn.LayerNorm((196, 4, 4)),
            nn.LeakyReLU(0.1),

            nn.MaxPool2d(kernel_size=4, stride=4)
        )
        self.fc1 = nn.Linear(196, 1)
        self.fc10 = nn.Linear(196, 10)
    def forward(self, input):
        x = self.main(input)
        x = x.view(-1, 196)
        x = self.fc10(x)
        return x

learning_rate = 0.0001
batch_size = 128
Num_Epochs = 100

trainset = torchvision.datasets.CIFAR10(root='./', train=True, download=True, transform=transform_train)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=8)

testset = torchvision.datasets.CIFAR10(root='./', train=False, download=True, transform=transform_test)
test_loader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=8)

model = discriminator()
model.cuda()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)

for epoch in range(Num_Epochs):
    for group in optimizer.param_groups:
        for p in group['params']:
            state = optimizer.state[p]
            if('step' in state and state['step']>=1024):
                state['step'] = 1000
    time1 = time.time()
    if(epoch==50):
        for param_group in optimizer.param_groups:
            param_group['lr'] = learning_rate/10.0
    if(epoch==75):
        for param_group in optimizer.param_groups:
            param_group['lr'] = learning_rate/100.0
    model.train()
    for i, (images, classes) in enumerate(train_loader):
        if(classes.shape[0] < batch_size):
            continue
        data, target = Variable(images).cuda(), Variable(classes).cuda()
#         data, target = Variable(images), Variable(classes)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
    model.eval()

    # Test Loss
    counter = 0
    test_accuracy_sum = 0.0
    for i, (images, classes) in enumerate(test_loader):
        data, target = Variable(images).cuda(), Variable(classes).cuda()
#         data, target = Variable(images), Variable(classes)
        output = model(data)
        prediction = output.data.max(1)[1]
        accuracy = (float(prediction.eq(target.data).sum())/float(batch_size))*100.0
        counter += 1
        test_accuracy_sum = test_accuracy_sum + accuracy
    test_accuracy_ave = test_accuracy_sum/float(counter)
    time2 = time.time()
    time_elapsed = time2 - time1
    print(epoch, test_accuracy_ave, time_elapsed)

torch.save(model,'cifar10.model')


# Clone a repo from Github
# 12f23eddde <rzhe@pku.edu.cn> - Apr 12 2021
import git
import os
import pandas as pd
import numpy as np
import requests

def clone_project_from_github(owner: str, repo: str, branch='master',REPOS_DIR='repos/', proxy=None):
    path = os.path.abspath(f'{REPOS_DIR}/{owner}/{repo}/{branch}')

    if not os.path.exists(path):
        os.makedirs(path)

    git_config=f"http.proxy={proxy}" if proxy else None
    git.Repo.clone_from(f'http://github.com/{owner}/{repo}.git', to_path=path, branch=branch,
        config=git_config)

def download_file_from_github(owner:str, repo: str, branch='master', file='requirements.txt', overwrite=True, proxy=None, REPOS_DIR='repos/') -> bool:
    requests_proxies = {
        "http": f"{proxy}",
        'https': f"{proxy}"
    } if proxy else None

    url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{file}"

    try:
        r = requests.get(url=url, proxies=requests_proxies)
    except FileNotFoundError:
        return False

    # create folder, place file owner/repo/branch/file
    if not os.path.exists(f"{REPOS_DIR}/{owner}/{repo}/{branch}"):
        os.makedirs(f"{REPOS_DIR}/{owner}/{repo}/{branch}")

    # do not overwrite if file exists
    if not overwrite and os.path.exists(f"{REPOS_DIR}/{owner}/{repo}/{branch}/{file}"):
        return False

    if r.status_code != 200:
        return False

    with open(f"{REPOS_DIR}/{owner}/{repo}/{branch}/{file}", 'w+') as f:
        f.write(r.text)

    return True

if __name__ == '__main__':
    clone_project_from_github('requests', 'requests-oauthlib', REPOS_DIR='./tests/', proxy='socks5://10.128.188.189:1081')
    download_file_from_github('requests', 'requests-oauthlib', REPOS_DIR='./tests/', proxy='socks5://10.128.188.189:1081')


import numpy as np
import pandas as pd
import pickle
import streamlit as st

print('Pandas {}'.format(pd.__version__))
print('Numpy {}'.format(np.__version__))
print('Streamlit {}'.format(st.__version__))
class_names = ['saudável', 'pesticidas', 'outros']

# load model
load_model = pickle.load(open('xgb_clf.pkl', 'rb'))

# main menu
def menu():

    st.sidebar.header('Home')
    page = st.sidebar.radio("", ('SciCrop',
                                 'Plataforma ML',
                                 'Safra Predição',
                                 'Contato'))
    # hide the menu
    hide_streamlit_style = """
                <style>
                # MainMenu {visibility: hidden;}
                footer {visibility: hidden;}
                </style>
                """
    st.markdown(hide_streamlit_style, unsafe_allow_html=True)

    if page == 'SciCrop':
        scicrop()

    if page == 'Plataforma ML':
        plataform()

    if page == 'Safra Predição':
        safra()

    if page == 'Contato':
        contato()

# ScriCrop page
def scicrop():
    st.title('SciCrop')
    st.write('A SciCrop é uma startup no segmento de soluções de Big Data e hoje oferece soluções de processamento de dados mais rápidas, eficientes e flexíveis para fornecer informações estratégicas por meio de algoritmos para a gestão de seus negócios.')
    st.image(
        'https://agevolution.canalrural.com.br/wp-content/uploads/2019/06/Scicrop.jpg',
        width=700,
        height=700,
        caption='SciCrop Logo')

# SciCrop ML
def plataform():
    st.title('Machine learning Plataform')
    st.write('Apesar do agro gerar muito lucro, a vida dos agricultores não é fácil, mas sim um verdadeiro teste de resistência e determinação. Uma vez que tenhamos semeado as sementes, o agricultor precisa trabalhar dia e noite para garantir uma boa safra no final da estação. Uma boa colheita depende de diversos fatores \
             como disponibilidade de água, fertilidade do solo, proteção das culturas, uso oportuno de pesticidas, outros fatores químicos úteis e da natureza. Muitos desses dados são quase impossíveis de se controlar, mas a quantidade e a frequência de pesticidas é algo que o agricultor pode administrar. Os pesticidas podem protegem a colheita com a dosagem certa. Mas, se adicionados em quantidade inadequada, podem prejudicar toda a safra.')
    st.write('\n')
    st.image('https://www.sulinfoco.com.br/wp-content/uploads/2019/05/Agricultura-Giro-Rural-1.jpg',
             width=600,
             height=300)
    st.write('\n')
    st.write('Essa plataforma de Machine learning foi desenvolvida para resolver está dor do Agricultor brasileiro, na plataforma ele consegue fazer a previsão da colheita da Safra.  \
             A plataforma é armazenada na nuvem e está disponível para uso do agricultor, permitindo que ele tenha acesso apenas entrando neste aplicativo web e  através do preenchimento dos dados, \
             em segundos a inferência é feita ao modelo de machine learning,  e ele terá a previsão em mãos sobre o resultado da colheita de safra.')

# Safra Prediction
def safra():
    st.image(
        'https://agevolution.canalrural.com.br/wp-content/uploads/2019/06/Scicrop.jpg',
        width=500,
        height=200)
    st.title('Safra Prediction')
    st.write('\n')
    st.write('\n')
    st.write('\n')

    # estimativa de insetos
    numero_insetos = st.number_input(
        'Estimativa_de_Insetos', min_value=0, max_value=100000, value=0)

    # tipo de cultivo
    tipo_cultivo = st.selectbox('Tipo_de_Cultivo', [0, 1])

    # tipo de solo
    tipo_solo = st.selectbox('Tipo_de_Solo', [0, 1])

    # categoria pesticidas
    cat_pesticidas = st.selectbox('Categoria_Pesticida', [0, 1, 2])

    # doses semana
    doses_semana = st.number_input(
        'Doses_Semana', min_value=0, max_value=10000000, value=0)

    # semanas utilizando
    semanas_utlizando = st.number_input(
        'Semanas_Utilizando', min_value=0, max_value=10000000, value=0)
    # semanas sem uso
    semanas_sem_uso = st.number_input(
        'Semanas_Sem_Uso', min_value=0, max_value=1000000, value=0)

    # temporada climática
    temporada = st.selectbox('Temporada', [1, 2, 3])

    input_dict = {'Estimativa_de_Insetos': [numero_insetos], 'Tipo_de_Cultivo': [tipo_cultivo],
                  'Tipo_de_Solo': [tipo_solo], 'Categoria_Pesticida': [cat_pesticidas],
                  'Doses_Semana': [doses_semana], 'Semanas_Utilizando': [semanas_utlizando],
                  'Semanas_Sem_Uso': [semanas_sem_uso], 'Temporada': [temporada]}

    input_df = pd.DataFrame(input_dict)
    load_model = pickle.load(open('xgb_clf.pkl', 'rb'))

    if st.button('Predição'):
        output = load_model.predict(input_df)
        if output == 0:
            st.success('Colheita de Safra:  {}'.format('Sem danos'))
        elif output == 1:
            st.success('Colheita de Safra:  {}'.format(
                'Danos por outros motivos'))
        else:
            st.success('Colheita de Safra:  {}'.format(
                'Danos gerados por pesticidas'))

# contato
def contato():
    st.title('Contato')
    st.image('https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSh8_BbTxZSsHWdLsSVjvVGVjASl3WynpmbMg&usqp=CAU',
             width=100, height=100)
    st.write('\n')
    st.write('\n')
    st.write('Este projeto foi desenvolvido por Felipe Oliveira, \
             Dúvidas ou sugestões podem me encaminhar um e-mail ou uma mensagem no LinkedIn.')
    st.write('\n')
    st.markdown(
        '[LinkedIn](https://www.linkedin.com/in/felipe-oliveira-18a573189/)')
    st.write('\n')
    st.write('E-mail: felipe.oliveiras2000@gmail.com')

if __name__ == '__main__':
    menu()


"""
From https://github.com/ehoogeboom/e3_diffusion_for_molecules/
"""

import tempfile
import warnings
import torch
import pickle
import os
import openbabel
import numpy as np

from rdkit import Chem
from typing import Any, Dict, List, Optional, Tuple
from rdkit.Chem.rdForceFieldHelpers import UFFOptimizeMolecule, UFFHasAllMoleculeParams

from src.datamodules.components.edm import dataset, get_bond_order_batch, get_bond_length_arrays
from src.datamodules.components.edm.datasets_config import get_dataset_info

from torchtyping import TensorType, patch_typeguard
from typeguard import typechecked

from src.models.components import write_xyz_file
from src.utils.pylogger import get_pylogger

patch_typeguard()  # use before @typechecked

log = get_pylogger(__name__)

def compute_qm9_smiles(dataset_name, data_dir, remove_h):
    """

    :param dataset_name: QM9 or QM9_second_half
    :return:
    """
    log.info("\tConverting QM9 dataset to SMILES ...")

    class StaticArgs:
        def __init__(self, dataset, data_dir, remove_h):
            self.dataset = dataset
            self.batch_size = 1
            self.num_workers = 1
            self.filter_n_atoms = None
            self.data_dir = data_dir
            self.remove_h = remove_h
            self.include_charges = True
            self.subtract_thermo = True
            self.force_download = False
            self.create_pyg_graphs = False
            self.num_radials = 1
            self.device = "cpu"
            self.num_train = -1
            self.num_valid = -1
            self.num_test = -1
            self.shuffle = True
            self.drop_last = True
    args_dataset = StaticArgs(dataset_name, data_dir, remove_h)
    dataloaders, _ = dataset.retrieve_dataloaders(args_dataset)
    dataset_info = get_dataset_info(args_dataset.dataset, args_dataset.remove_h)
    n_types = 4 if remove_h else 5
    mols_smiles = []
    for i, data in enumerate(dataloaders["train"]):
        positions = data["positions"][0].view(-1, 3).numpy()
        one_hot = data["one_hot"][0].view(-1, n_types).type(torch.float32)
        atom_type = torch.argmax(one_hot, dim=1).numpy()
        charges = data["charges"][0].squeeze(-1)

        mol = build_molecule(torch.tensor(positions), torch.tensor(atom_type), dataset_info, charges)
        mol = mol2smiles(mol)
        if mol is not None:
            mols_smiles.append(mol)
        if i % 1000 == 0:
            log.info("\tConverting QM9 dataset to SMILES {0:.2%}".format(float(i)/len(dataloaders["train"])))
    return mols_smiles

@typechecked
def retrieve_qm9_smiles(
    dataset_info: Dict[str, Any],
    data_dir: str
) -> List[str]:
    dataset_name = dataset_info["name"]
    if dataset_info["with_h"]:
        pickle_name = dataset_name
    else:
        pickle_name = dataset_name + "_noH"

    file_path = os.path.join(data_dir, dataset_name, f"{pickle_name}_smiles.pickle")
    try:
        with open(file_path, "rb") as f:
            qm9_smiles = pickle.load(f)
        return qm9_smiles
    except OSError:
        try:
            os.makedirs(data_dir, exist_ok=True)
        except:
            pass
        qm9_smiles = compute_qm9_smiles(
            dataset_name,
            data_dir=data_dir,
            remove_h=not dataset_info["with_h"]
        )
        with open(file_path, "wb") as f:
            pickle.dump(qm9_smiles, f)
        return qm9_smiles

#### New implementation ####

bond_dict = [
    None,
    Chem.rdchem.BondType.SINGLE,
    Chem.rdchem.BondType.DOUBLE,
    Chem.rdchem.BondType.TRIPLE,
    Chem.rdchem.BondType.AROMATIC
]

class BasicMolecularMetrics(object):
    def __init__(
        self,
        dataset_info: Dict[str, Any],
        data_dir: str,
        dataset_smiles_list: Optional[np.ndarray] = None
    ):
        self.atom_decoder = dataset_info["atom_decoder"]
        self.dataset_smiles_list = dataset_smiles_list
        self.dataset_info = dataset_info

        # retrieve dataset smiles only for the QM9 dataset currently
        if dataset_smiles_list is None and "QM9" in dataset_info["name"]:
            bonds = get_bond_length_arrays(self.dataset_info["atom_encoder"])
            self.dataset_info["bonds1"], self.dataset_info["bonds2"], self.dataset_info["bonds3"] = (
                bonds[0], bonds[1], bonds[2]
            )
            self.dataset_smiles_list = retrieve_qm9_smiles(self.dataset_info, data_dir)

    @typechecked
    def compute_validity(self, rdmols: List[Chem.RWMol]) -> Tuple[List[str], float]:
        valid = []
        for mol in rdmols:
            smiles = mol2smiles(mol)
            if smiles is not None:
                mol_frags = Chem.rdmolops.GetMolFrags(mol, asMols=True)
                largest_mol = max(mol_frags, default=mol, key=lambda m: m.GetNumAtoms())
                smiles = mol2smiles(largest_mol)
                valid.append(smiles)
        return valid, len(valid) / len(rdmols)

    @typechecked
    def compute_uniqueness(self, valid: List[str]) -> Tuple[List[str], float]:
        # note: `valid` is a list of SMILES strings
        return list(set(valid)), len(set(valid)) / len(valid)

    @typechecked
    def compute_novelty(self, unique: List[str]) -> Tuple[List[str], float]:
        num_novel = 0
        novel = []
        for smiles in unique:
            if smiles not in self.dataset_smiles_list:
                novel.append(smiles)
                num_novel += 1
        return novel, num_novel / len(unique)

    @typechecked
    def evaluate_rdmols(self, rdmols: List[Chem.RWMol], verbose: bool = True) -> List[float]:
        valid, validity = self.compute_validity(rdmols)
        if verbose:
            log.info(f"Validity over {len(rdmols)} molecules: {validity * 100 :.2f}%")
        if validity > 0:
            unique, uniqueness = self.compute_uniqueness(valid)
            if verbose:
                log.info(f"Uniqueness over {len(valid)} valid molecules: {uniqueness * 100 :.2f}%")
            if self.dataset_smiles_list is not None:
                _, novelty = self.compute_novelty(unique)
                if verbose:
                    log.info(f"Novelty over {len(unique)} unique valid molecules: {novelty * 100 :.2f}%")
            else:
                novelty = 0.0
        else:
            uniqueness = 0.0
            novelty = 0.0
        return [validity, uniqueness, novelty]

    @typechecked
    def evaluate(
        self,
        generated: List[Tuple[torch.Tensor, ...]]
    ) -> List[float]:
        """
        note: `generated` is a list of pairs (`positions`: [n x 3], `atom_types`: n [int]; `charges`: n [int]);
        also note: the positions and atom types should already be masked.
        """
        rdmols = [build_molecule(*graph, self.dataset_info) for graph in generated]
        return self.evaluate_rdmols(rdmols)

def mol2smiles(mol):
    try:
        Chem.SanitizeMol(mol)
    except ValueError:
        return None
    return Chem.MolToSmiles(mol)

@typechecked
def build_molecule(
    positions: TensorType["num_nodes", 3],
    atom_types: TensorType["num_nodes"],
    dataset_info: Dict[str, Any],
    charges: Optional[TensorType["num_nodes"]] = None,
    add_coords: bool = False,
    use_openbabel: bool = False
) -> Chem.RWMol:
    """
    Build RDKit molecule
    Args:
        positions: N x 3
        atom_types: N
        charges: N
        dataset_info: dict
        add_coords: Add conformer to mol (always added if use_openbabel=True)
        use_openbabel: use OpenBabel to create bonds
    Returns:
        RDKit molecule
    """
    if use_openbabel:
        mol = make_mol_openbabel(positions, atom_types, dataset_info["atom_decoder"])
    else:
        mol = make_mol_edm(positions, atom_types, dataset_info, add_coords)

    return mol

@typechecked
def make_mol_openbabel(
    positions: TensorType["num_nodes", 3],
    atom_types: TensorType["num_nodes"],
    atom_decoder: Dict[int, str]
) -> Chem.RWMol:
    """
    Build an RDKit molecule using openbabel for creating bonds
    Args:
        positions: N x 3
        atom_types: N
        atom_decoder: maps indices to atom types
    Returns:
        rdkit molecule
    """
    atom_types = [atom_decoder[x] for x in atom_types]

    with tempfile.NamedTemporaryFile() as tmp:
        tmp_file = tmp.name

        # Write xyz file
        write_xyz_file(positions, atom_types, tmp_file)

        # Convert to sdf file with openbabel
        # openbabel will add bonds
        obConversion = openbabel.OBConversion()
        obConversion.SetInAndOutFormats("xyz", "sdf")
        ob_mol = openbabel.OBMol()
        obConversion.ReadFile(ob_mol, tmp_file)

        obConversion.WriteFile(ob_mol, tmp_file)

        # Read sdf file with RDKit
        mol = Chem.SDMolSupplier(tmp_file, sanitize=False)[0]

    return mol

@typechecked
def make_mol_edm(
    positions: TensorType["num_nodes", 3],
    atom_types: TensorType["num_nodes"],
    dataset_info: Dict[str, Any],
    add_coords: bool
) -> Chem.RWMol:
    """
    Note: Equivalent to EDM's way of building RDKit molecules
    """
    n = len(positions)
    limit_bonds_to_one = "GEOM" in dataset_info["name"]

    # (X, A, E): atom_types, adjacency matrix, edge_types
    # X: N (int)
    # A: N x N (bool) -> (binary adjacency matrix)
    # E: N x N (int) -> (bond type, 0 if no bond)
    pos = positions.unsqueeze(0)  # add batch dim
    dists = torch.cdist(pos, pos, p=2).squeeze(0).view(-1)  # remove batch dim & flatten
    atoms1, atoms2 = torch.cartesian_prod(atom_types, atom_types).T
    E_full = get_bond_order_batch(
        atoms1, atoms2, dists, dataset_info, limit_bonds_to_one=limit_bonds_to_one
    ).view(n, n)
    E = torch.tril(E_full, diagonal=-1)  # Warning: the graph should be DIRECTED
    A = E.bool()
    X = atom_types

    mol = Chem.RWMol()
    for atom in X:
        a = Chem.Atom(dataset_info["atom_decoder"][atom.item()])
        mol.AddAtom(a)

    all_bonds = torch.nonzero(A)
    for bond in all_bonds:
        mol.AddBond(bond[0].item(), bond[1].item(),
                    bond_dict[E[bond[0], bond[1]].item()])

    if add_coords:
        conf = Chem.Conformer(mol.GetNumAtoms())
        for i in range(mol.GetNumAtoms()):
            conf.SetAtomPosition(i, (positions[i, 0].item(),
                                     positions[i, 1].item(),
                                     positions[i, 2].item()))
        mol.AddConformer(conf)

    return mol

@typechecked
def process_molecule(
    rdmol: Chem.Mol,
    add_hydrogens: bool = False,
    sanitize: bool = False,
    relax_iter: int = 0,
    largest_frag: bool = False
) -> Optional[Chem.Mol]:
    """
    Apply filters to an RDKit molecule. Makes a copy first.
    Args:
        rdmol: RDKit molecule
        add_hydrogens: whether to add hydrogen atoms to the generated molecule
        sanitize: whether to sanitize molecules
        relax_iter: maximum number of UFF optimization iterations
        largest_frag: whether to filter out the largest fragment in a set of disjoint molecules
    Returns:
        RDKit molecule or `None` if it does not pass the filters
    """
    # create a copy
    mol = Chem.Mol(rdmol)

    if sanitize:
        try:
            Chem.SanitizeMol(mol)
        except ValueError:
            warnings.warn('Sanitization failed. Returning None.')
            return None

    if add_hydrogens:
        mol = Chem.AddHs(mol, addCoords=(len(mol.GetConformers()) > 0))

    if largest_frag:
        mol_frags = Chem.GetMolFrags(mol, asMols=True, sanitizeFrags=False)
        mol = max(mol_frags, default=mol, key=lambda m: m.GetNumAtoms())
        if sanitize:
            # sanitize the updated molecule
            try:
                Chem.SanitizeMol(mol)
            except ValueError:
                return None

    if relax_iter > 0:
        if not UFFHasAllMoleculeParams(mol):
            warnings.warn('UFF parameters not available for all atoms. '
                          'Returning None.')
            return None

        try:
            uff_relax(mol, relax_iter)
            if sanitize:
                # sanitize the updated molecule
                Chem.SanitizeMol(mol)
        except (RuntimeError, ValueError) as e:
            return None

    return mol

@typechecked
def uff_relax(mol: Chem.Mol, max_iter: int = 200) -> bool:
    """
    Uses RDKit's universal force field (UFF) implementation to optimize a
    molecule.
    """
    convergence_status = UFFOptimizeMolecule(mol, maxIters=max_iter)
    more_iterations_required = convergence_status == 1
    if more_iterations_required:
        warnings.warn(f'Maximum number of FF iterations reached. '
                      f'Returning molecule after {max_iter} relaxation steps.')
    return more_iterations_required

if __name__ == "__main__":
    smiles_mol = "C1CCC1"
    log.info("Smiles mol %s" % smiles_mol)
    chem_mol = Chem.MolFromSmiles(smiles_mol)
    block_mol = Chem.MolToMolBlock(chem_mol)
    log.info("Block mol:")
    log.info(block_mol)


import requests

# Replace this with the base URL of your Flask application
BASE_URL = 'http://localhost:5000'

# Function to send a GET request and print the response
def print_response(response):
    print(f"Status code: {response.status_code}")
    try:
        print("Response:")
        print(response.json())
    except Exception as e:
        print(f"Error reading response JSON: {e}")
    print("\n")

# Test creating a new Bairro
def test_create_bairro():
    data = {
        'nome_bairro': 'Test Bairro',
        'regiao': 'Test Region',
        'cep': '12345678',
        'id_cidade': 1  # Replace with the ID of the associated Cidade
    }
    response = requests.post(f"{BASE_URL}/bairro", json=data)
    print_response(response)

# Test getting all Bairros
def test_get_bairros():
    response = requests.get(f"{BASE_URL}/bairros")
    print_response(response)

# Test getting a specific Bairro by ID
def test_get_bairro_by_id(id):
    response = requests.get(f"{BASE_URL}/bairro/{id}")
    print_response(response)

# Test updating an existing Bairro by ID
def test_update_bairro(id):
    data = {
        'nome_bairro': 'Updated Bairro',
        'regiao': 'Updated Region',
        'cep': '87654321',
        'id_cidade': 2  # Replace with the new ID of the associated Cidade
    }
    response = requests.put(f"{BASE_URL}/bairro/{id}", json=data)
    print_response(response)

# Test deleting a specific Bairro by ID
def test_delete_bairro(id):
    response = requests.delete(f"{BASE_URL}/bairro/{id}")
    print_response(response)

# Test creating a new Logradouro
def test_create_logradouro():
    data = {
        'nome_rua': 'Test Street',
        'numero': 123,
        'complemento': 'Test Complement',
        'id_bairro': 1  # Replace with the ID of the associated Bairro
    }
    response = requests.post(f"{BASE_URL}/logradouro", json=data)
    print_response(response)

# Test getting all Logradouros
def test_get_logradouros():
    response = requests.get(f"{BASE_URL}/logradouros")
    print_response(response)

# Test getting a specific Logradouro by ID
def test_get_logradouro_by_id(id):
    response = requests.get(f"{BASE_URL}/logradouro/{id}")
    print_response(response)

# Test updating an existing Logradouro by ID
def test_update_logradouro(id):
    data = {
        'nome_rua': 'Updated Street',
        'numero': 456,
        'complemento': 'Updated Complement',
        'id_bairro': 2  # Replace with the new ID of the associated Bairro
    }
    response = requests.put(f"{BASE_URL}/logradouro/{id}", json=data)
    print_response(response)

# Test deleting a specific Logradouro by ID
def test_delete_logradouro(id):
    response = requests.delete(f"{BASE_URL}/logradouro/{id}")
    print_response(response)

if __name__ == '__main__':
    # Test all CRUD operations for Bairro
    test_create_bairro()

    test_get_bairros()

    test_get_bairro_by_id(1)  # Replace 1 with the ID of the Bairro you want to retrieve

    test_update_bairro(1)  # Replace 1 with the ID of the Bairro you want to update

    test_delete_bairro(1)  # Replace 1 with the ID of the Bairro you want to delete

    # Test all CRUD operations for Logradouro
    test_create_logradouro()

    test_get_logradouros()

    test_get_logradouro_by_id(1)  # Replace 1 with the ID of the Logradouro you want to retrieve

    test_update_logradouro(1)  # Replace 1 with the ID of the Logradouro you want to update

    test_delete_logradouro(1)  # Replace 1 with the ID of the Logradouro you want to delete