import logging
import json
import numpy as np
import time
import os
from pathlib import Path
from tqdm import tqdm

formatter = logging.Formatter("%(asctime)s %(message)s")

def setup_logger(name, log_file, level=logging.INFO):
    """To setup as many loggers as you want"""

    handler = logging.FileHandler(log_file)
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(handler)

    return logger

class DataLogging:
    """
    Logging class that handles the setup and data-flow in order to write a log for the graph-planner in an iterative
    manner.
    :Authors:
        * Tim Stahl <tim.stahl@tum.de>
        * Alexander Heilmeier
    :Created on:
        23.01.2019
    """

    # ----------------------------------------------------------------------------------------------------------
    # CONSTRUCTOR ----------------------------------------------------------------------------------------------
    # ----------------------------------------------------------------------------------------------------------
    def __init__(self, log_path: str) -> None:
        """"""
        # Create directories
        Path(os.path.dirname(log_path)).mkdir(parents=True, exist_ok=True)
        # write header to logging file
        self.__log_path = log_path
        with open(self.__log_path, "w+") as fh:
            header = (
                "time;obj_dict;hist_input;boundaries_input;pred_dict;" "calc_time_avg"
            )
            fh.write(header)

    # ----------------------------------------------------------------------------------------------------------
    # CLASS METHODS --------------------------------------------------------------------------------------------
    # ----------------------------------------------------------------------------------------------------------

    def log_pred_data(
        self,
        time: float,
        obj_dict: dict,
        hist_input: dict,
        boundaries_input: dict,
        pred_dict: dict,
        calc_time_avg: float,
        log_params=None,
    ) -> None:
        """
        Write one line to the log file.
        :param time:             current time stamp (float time)
        """

        if log_params is not None:
            hist_input_local = {}
            boundaries_input_local = {}
            pred_dict_local = {}

            for ID in pred_dict.keys():
                if log_params["history"]:
                    hist_input_local[ID] = hist_input[ID]
                else:
                    hist_input_local[ID] = None

                if log_params["boundaries"]:
                    boundaries_input_local[ID] = boundaries_input[ID]
                else:
                    boundaries_input_local[ID] = None

                # num_covs = (
                #     log_params["num_covs"]
                #     if log_params["num_covs"] < pred_dict[ID]["cov"].shape[0]
                #     else pred_dict[ID]["cov"].shape[0]
                # )

                pred_dict_local[ID] = {
                    "vehicle_id": pred_dict[ID]["vehicle_id"],
                    "prediction_type": pred_dict[ID]["prediction_type"],
                    "t_abs_perception": pred_dict[ID]["t_abs_perception"],
                    "t": pred_dict[ID]["t"] if log_params["time_array"] else None,
                    "x": pred_dict[ID]["x"],
                    "y": pred_dict[ID]["y"],
                    # "cov": pred_dict[ID]["cov"][:num_covs, :, :],
                    "heading": pred_dict[ID]["heading"]
                    if log_params["heading"]
                    else None,
                }

            with open(self.__log_path, "a") as fh:
                fh.write(
                    "\n"
                    + str(time)
                    + ";"
                    + json.dumps(obj_dict, default=default)
                    + ";"
                    + json.dumps(hist_input_local, default=default)
                    + ";"
                    + json.dumps(boundaries_input_local, default=default)
                    + ";"
                    + json.dumps(pred_dict_local, default=default)
                    + ";"
                    + json.dumps(calc_time_avg, default=default)
                )
        else:
            with open(self.__log_path, "a") as fh:
                fh.write(
                    "\n"
                    + str(time)
                    + ";"
                    + json.dumps(obj_dict, default=default)
                    + ";"
                    + json.dumps(hist_input, default=default)
                    + ";"
                    + json.dumps(boundaries_input, default=default)
                    + ";"
                    + json.dumps(pred_dict, default=default)
                    + ";"
                    + json.dumps(calc_time_avg, default=default)
                )

    def log_tracking_data(
        self,
        time: float,
        detection_dict: dict,
        log_ego_state: list,
        match_dict: list,
        filter_log: dict,
        object_dict: dict,
        old_object_dict: dict,
        data_based_input: dict,
        pred_dict: dict,
        calc_time_avg: float,
    ) -> None:
        """
        Write one line to the log file.
        :param time:             current time stamp (float time)
        """
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time)
                + ";"
                + json.dumps(detection_dict, default=default)
                + ";"
                + json.dumps(log_ego_state, default=default)
                + ";"
                + json.dumps(match_dict, default=default)
                + ";"
                + json.dumps(filter_log, default=default)
                + ";"
                + json.dumps(object_dict, default=default)
                + ";"
                + json.dumps(old_object_dict, default=default)
                + ";"
                + json.dumps(data_based_input, default=default)
                + ";"
                + json.dumps(pred_dict, default=default)
                + ";"
                + json.dumps(calc_time_avg, default=default)
            )

class MessageLogging:
    """
    Logging class that handles the setup and data-flow in order to write a log for the graph-planner in an iterative
    manner.
    :Authors:
        * Tim Stahl <tim.stahl@tum.de>
        * Alexander Heilmeier
    :Created on:
        23.01.2019
    """

    # ----------------------------------------------------------------------------------------------------------
    # CONSTRUCTOR ----------------------------------------------------------------------------------------------
    # ----------------------------------------------------------------------------------------------------------
    def __init__(self, log_path: str) -> None:
        """"""
        # write header to logging file
        self.__log_path = log_path
        with open(self.__log_path, "w+") as fh:
            header = "time;type;message"
            fh.write(header)

    # ----------------------------------------------------------------------------------------------------------
    # CLASS METHODS --------------------------------------------------------------------------------------------
    # ----------------------------------------------------------------------------------------------------------

    def log_message(self, time: float, msg_type: str, message: str) -> None:
        """
        Write one line to the log file.
        :param time:             current time stamp (float time)
        """
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time)
                + ";"
                + json.dumps(msg_type, default=default)
                + ";"
                + json.dumps(message, default=default)
            )

    def warning(self, message: str) -> None:
        msg_type = "WARNING"
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time.time())
                + ";"
                + json.dumps(msg_type, default=default)
                + ";"
                + json.dumps(message, default=default)
            )

    def info(self, message: str) -> None:
        msg_type = "INFO"
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time.time())
                + ";"
                + json.dumps(msg_type, default=default)
                + ";"
                + json.dumps(message, default=default)
            )

    def debug(self, message: str) -> None:
        msg_type = "DEBUG"
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time.time())
                + ";"
                + json.dumps(msg_type, default=default)
                + ";"
                + json.dumps(message, default=default)
            )

    def error(self, message: str) -> None:
        msg_type = "error"
        with open(self.__log_path, "a") as fh:
            fh.write(
                "\n"
                + str(time.time())
                + ";"
                + json.dumps(msg_type, default=default)
                + ";"
                + json.dumps(message, default=default)
            )

def default(obj):
    # handle numpy arrays when converting to json
    if isinstance(obj, np.ndarray):
        return obj.tolist()
    if isinstance(obj, np.integer):
        return int(obj)
    raise TypeError("Not serializable (type: " + str(type(obj)) + ")")

def read_all_data(file_path_in, keys=None, zip_horz=False):

    with open(file_path_in) as f:
        total_lines = sum(1 for _ in f)

    total_lines = max(1, total_lines)

    all_data = None

    assert (
        total_lines > 1
    ), "Invalid logs: No tracking files, most likely short simulation time"

    # extract a certain line number (based on time_stamp)

    with open(file_path_in) as file:
        # get to top of file (1st line)
        file.seek(0)
        # get header (":-1" in order to remove tailing newline character)
        header = file.readline()[:-1]
        # extract line
        line = ""
        for j in tqdm(range(total_lines - 1)):
            line = file.readline()

            if zip_horz:
                if all_data is None:
                    all_data = []
                    all_data = [header.split(";"), [None] * (total_lines - 1)]

                all_data[1][j] = tuple(json.loads(ll) for ll in line.split(";"))
            else:
                # parse the data objects we want to retrieve from that line
                data = dict(zip(header.split(";"), line.split(";")))
                if all_data is None:
                    if keys is None:
                        keys = data.keys()
                    all_data = {key: [0.0] * (total_lines - 1) for key in keys}
                for key in keys:
                    all_data[key][j] = json.loads(data[key])

    return all_data

def read_info_data(info_file_path):
    """Extracts the infos about the observations with each ID and stores them in
    a dictionary, which can be queried by the IDs.
    """

    info_dict = {}
    with open(info_file_path) as f:
        for line in f:
            if "Prediction-ID" in line:
                i0 = line.index("Prediction-ID") + len("Prediction-ID") + 1
                i1 = line.index(":", i0)
                ID = line[i0:i1]
                info_dict[ID] = []

                if "static" in line:
                    info_dict[ID].append("static")

                elif "physics-prediction" in line:
                    info_dict[ID].append("physics-based")

                    i0 = line.index("reason:") + len("reason:") + 1
                    i1 = line.index(",", i0)
                    info_dict[ID].append(line[i0:i1])

                elif "data-prediction" in line:
                    info_dict[ID].append("data-based")

                    if "mixers" in line:
                        i0 = line.index("mixers")
                        info_dict[ID].append("\n")
                        info_dict[ID].append(line[i0:])

                elif "data-physics-override-prediction" in line:
                    info_dict[ID].append("data-physics-override")

                    if "mixers" in line:
                        i0 = line.index("mixers")
                        info_dict[ID].append("\n")
                        info_dict[ID].append(line[i0:])

                elif "rail-prediction" in line:
                    info_dict[ID].append("rail-based")

                    i0 = line.index("reason:") + len("reason:") + 1
                    i1 = line.index(",", i0)
                    info_dict[ID].append(line[i0:i1])

                elif "potential-field" in line:
                    info_dict[ID].append("potential-field")

                    i0 = (
                        line.index("potential-field prediction")
                        + len("potential-field prediction")
                        + 2
                    )
                    i1 = line.index("id:")
                    info_dict[ID].append(line[i0:i1])

                elif "Invalid" in line:
                    info_dict[ID].append("invalid")

            elif "Collision" in line:
                i0 = line.index("IDs") + len("IDs") + 1
                i1 = line.index("(", i0) - 1
                ID1 = line[i0:i1]

                i0 = line.index("and") + len("and") + 1
                i1 = line.index("(", i0) - 1
                ID2 = line[i0:i1]

                i0 = line.index("timestep") + len("timestep") + 1
                ts = line[i0:-2]

                info_dict[ID1].append("collision with ID " + ID2 + " at " + ts)
                info_dict[ID2].append("collision with ID " + ID1 + " at " + ts)

            elif "not adjusted" in line:
                pass

            elif "adjusted" in line:
                if "ID" in line:
                    i0 = line.index("ID") + len("ID") + 1
                    i1 = line.index("adjusted", i0) - 1
                    ID = line[i0:i1]
                    if "right" in line:
                        direction = "right"
                    else:
                        direction = "left"

                    i0 = line.index("distance of") + len("distance of") + 1
                    dist = line[i0:-2]

                    info_dict[ID].append("adjusted to the " + direction + " by " + dist)

    return info_dict

def recover_trajectories(obj_data):
    """Recovers the trajectories from the log. It takes the list of obj_dicts, which
    were recovered from the data log file and creates x, y, t arrays for each vehicle
    based in them.

    args:
        obj_data: (list of dicts), the list of obj_dicts that was recovered from the log.
    returns:
        trajectories: (dict of dicts), The keys of the main dict are the vehicle IDs
            and the value of each vehicle ID is an other dict, which contains the x, y and time
            data.
    """

    trajectories = {}

    for object_dict in obj_data:

        if not bool(object_dict):
            continue
        for objID, xy_pos, t_abs in object_dict.values():
            # division by 1e9 is needed, because mod_object sends nanosecs:
            t = float(t_abs) / 1e9

            if objID not in trajectories.keys():
                trajectories[objID] = {"t_list": [], "x_list": [], "y_list": []}
                trajectories[objID]["t_list"].append(t)
                trajectories[objID]["x_list"].append(xy_pos[0])
                trajectories[objID]["y_list"].append(xy_pos[1])

            elif t != trajectories[objID]["t_list"][-1]:
                trajectories[objID]["t_list"].append(t)
                trajectories[objID]["x_list"].append(xy_pos[0])
                trajectories[objID]["y_list"].append(xy_pos[1])

    # numpify:
    for ID, val in trajectories.items():
        trajectories[ID]["t_list"] = np.array(val["t_list"])
        trajectories[ID]["x_list"] = np.array(val["x_list"])
        trajectories[ID]["y_list"] = np.array(val["y_list"])

    return trajectories

def recover_params(info_file):
    """recovers the parameters that were used. If the log file is old and
    hence no params are logged in it, some dummy params are created, so that
    the rest of the visualization will not complain.

    args:
        info_file: (string), the main log file.

    returns:
        dict, that contains the params that were used.
    """

    with open(info_file) as f:

        params = {}
        master_key = None
        started = False

        for line in f:
            if "=====" in line:
                if not started:
                    started = True
                else:
                    break
            elif started:
                if ":" in line:
                    i0 = line.index(" - ") + len(" - ")
                    i1 = line.index(":", i0)
                    key = line[i0:i1]

                    i0 = i1 + 2
                    i1 = line.index('"', i0)
                    val = line[i0:i1]

                    if master_key is not None:
                        params[master_key][key] = val
                    else:
                        print("Failed to recover params from log.\n")
                        return {}
                else:
                    i0 = line.index("INFO") + len("INFO") + 3
                    i1 = line.index('"', i0)
                    master_key = line[i0:i1]
                    params[master_key] = {}
    if "MODEL_PARAMS" not in list(params.keys()):
        # creating dummy params:
        params["MODEL_PARAMS"] = {}
        params["MODEL_PARAMS"]["sampling_frequency"] = "10"
        params["MODEL_PARAMS"]["data_min_obs_length"] = "1.0"
        params["MODEL_PARAMS"]["view"] = "400"
        params["MODEL_PARAMS"]["dist"] = "20"

        print("Could not recover params, hence dummy params are provided.")
    else:
        if "sampling_frequency" not in (list(params["MODEL_PARAMS"].keys())):
            params["MODEL_PARAMS"]["sampling_frequency"] = "10"
            print("using dummy param for MODEL_PARAMS/sampling_frequency")

        if "data_min_obs_length" not in (list(params["MODEL_PARAMS"].keys())):
            params["MODEL_PARAMS"]["data_min_obs_length"] = "1.0"
            print("using dummy param for MODEL_PARAMS/data_min_obs_length")

        if "view" not in (list(params["MODEL_PARAMS"].keys())):
            params["MODEL_PARAMS"]["view"] = "400"
            print("using dummy param for MODEL_PARAMS/view")

        if "dist" not in (list(params["MODEL_PARAMS"].keys())):
            params["MODEL_PARAMS"]["dist"] = "20"
            print("using dummy param for MODEL_PARAMS/dist")

    if "OBJ_HANDLING_PARAMS" not in list(params.keys()):
        params["OBJ_HANDLING_PARAMS"] = {}
        params["OBJ_HANDLING_PARAMS"]["max_obs_length"] = "30"
    else:
        if "max_obs_length" not in (list(params["OBJ_HANDLING_PARAMS"].keys())):
            params["OBJ_HANDLING_PARAMS"]["max_obs_length"] = "30"
            print("using dummy param for MODEL_PARAMS/max_obs_length")

    rpl_key = []
    for param_key in params.keys():
        if [param_key] == list(params[param_key].keys()):
            rpl_key.append(param_key)

    for param_key in rpl_key:
        params[param_key] = params[param_key][param_key]

    for param_key in params.keys():
        if "path" in param_key:
            repo_path = os.path.dirname(
                os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
            )
            params[param_key] = params[param_key].replace(
                "/dev_ws/src/mod_prediction", repo_path
            )
    return params

def get_data_from_line(file_path_in: str, line_num: int, log_type: str = "prediction"):
    line_num = max(1, line_num)
    # extract a certain line number (based on time_stamp)
    with open(file_path_in) as file:
        # get to top of file (1st line)
        file.seek(0)
        # get header (":-1" in order to remove tailing newline character)
        header = file.readline()[:-1]
        # extract line
        line = ""
        for _ in range(line_num):
            line = file.readline()

        # parse the data objects we want to retrieve from that line
        data = dict(zip(header.split(";"), line.split(";")))

        if log_type == "tracking":
            detection_dict = json.loads(data["detection_dict"])
            log_ego_state = json.loads(data["log_ego_state"])
            match_dict = json.loads(data["match_dict"])
            filter_log = json.loads(data["filter_log"])
            object_dict = json.loads(data["object_dict"])
            old_object_dict = json.loads(data["old_object_dict"])
            pred_input_dict = json.loads(data["pred_input_dict"])
            pred_dict = json.loads(data["pred_dict"])
            calc_time_avg = json.loads(data["calc_time_avg"])
            return (
                detection_dict,
                log_ego_state,
                match_dict,
                filter_log,
                object_dict,
                old_object_dict,
                pred_input_dict,
                pred_dict,
                calc_time_avg,
            )
        else:
            obj_dict = json.loads(data["obj_dict"])
            hist = json.loads(data["hist_input"])
            boundaries = json.loads(data["boundaries_input"])
            pred_dict = json.loads(data["pred_dict"])
            calc_time_avg = json.loads(data["calc_time_avg"])
            return obj_dict, hist, boundaries, pred_dict, calc_time_avg

def get_number_of_lines(file_path_in: str):
    with open(file_path_in) as file:
        row_count = sum(1 for row in file)

    return row_count

def log_param_dict(param_dict, logger):
    logger.info("=" * 40)
    for sec, dic in param_dict.items():
        logger.info(sec)
        if isinstance(dic, dict):
            for key, val in dic.items():
                logger.info(" - {}: {}".format(key, val))
        else:
            logger.info(" - {}: {}".format(sec, dic))
    logger.info("=" * 40)


import torch
import tqdm
from torch import Tensor
from torch.nn import Module
from torch.utils.data import DataLoader
from torch_geometric.typing import EdgeType
from typing import NamedTuple
from . import get_device

__all__ = ('rank_k',)

class Ranking(NamedTuple):
    score: Tensor
    label: Tensor
    count: Tensor
    item: Tensor
    user: Tensor

@torch.no_grad()
def rank_k(
    module: type[Module],
    loader: type[DataLoader],
    *,
    pred_fn: callable,
    at_k: int = 20,
    edge_type: EdgeType,
    device: torch.device = None,
    verbose: bool = False
) -> Ranking:

    # Resolves the device.
    device = get_device(device)
    # Empties the GPU cache, if that device is set.
    if device.type == 'cuda':
        torch.cuda.empty_cache()
    # Setting up the model for evaluation.
    module = module.to(device).eval()
    # Unpacks the given edge type.
    src_node, _, dst_node = edge_type

    # Identifies the total number of source node and their respective IDs.
    gbl_idx = loader.data[edge_type].edge_label_index[0].unique()
    gbl_uid = loader.data[src_node].n_id[gbl_idx].to(device)

    # Wraps the loader in a progress tracker object, if verbose is set.
    if verbose is True:
        loader_ = tqdm.tqdm(loader, mininterval=1., position=0, leave=True)
    else:
        loader_ = loader
    # Creates the hash-map that is to be housing the output buffers.
    score_buffer = [list() for _ in gbl_uid]
    label_buffer = [list() for _ in gbl_uid]
    id_buffer = [list() for _ in gbl_uid]
    # Iterates over the data-loader's batches.
    for batch in loader_:

        # Predicts edge-wise scores.
        edge_score = pred_fn(module,
            data=batch,
            edge_type=edge_type,
            device=device
        )

        # Unpacks the edge label index.
        src_idx, dst_idx = batch[edge_type].edge_label_index
        # Derives the source and destination node IDs.
        src_id = batch[src_node].n_id[src_idx]
        dst_id = batch[dst_node].n_id[dst_idx]
        # Extracts the edge labels and destination node IDs.
        edge_label = batch[edge_type].edge_label

        # Identifies the unique source node IDs in the batch.
        src_uid = src_id.unique()
        # Infers the relevant source nodes in the tracked set.
        out_idx = (
            gbl_uid.unsqueeze(1)
                ==
            src_uid.unsqueeze(0)
        ).any(dim=-1).nonzero().ravel()
        # Generates the relevant nodes' element mask.
        out_msk = gbl_uid[out_idx].unsqueeze(1) == src_id.unsqueeze(0)

        # Updates the relevant source nodes' buffers.
        for idx, msk in zip(out_idx, out_msk):
            score_buffer[idx].append(edge_score[msk].cpu())
            label_buffer[idx].append(edge_label[msk].cpu())
            id_buffer[idx].append(dst_id[msk].cpu())

    # Concatenates all sub-buffers for the score, label and ID buffers.
    scores = [torch.cat(buffer) for buffer in score_buffer]
    labels = [torch.cat(buffer) for buffer in label_buffer]
    ids = [torch.cat(buffer) for buffer in id_buffer]

    # Initializes the positive edge counter.
    positive_count = []
    # Initializes the top-k score, label and id buffers.
    score_top, label_top, id_top = [], [], []
    # Computes the top-k item scores, labels and ids.
    for scores, labels, ids in zip(scores, labels, ids):

        # Computes the top-k values and their indices.
        scores, index = torch.topk(scores, k=at_k)

        # Saves the top-k scores, labels and ids.
        score_top.append(scores)
        label_top.append(labels[index])
        id_top.append(ids[index])

        # Counts the total number of positive edges.
        positive_count.append(labels.sum())

    # Reformats the top-k buffers to pure tensors.
    score = torch.stack(score_top)
    label = torch.stack(label_top)
    id = torch.stack(id_top)
    count = torch.tensor(positive_count)

    # Returns the top-k labels and the total positive edge count.
    return Ranking(score, label, count, item=id, user=gbl_uid)


# -*- coding: utf-8 -*-
"""
This script creates a image dataset from the Fast.AI multi-label CNN classifier.
This dataset is a collection from existing images.  Currently we have ~7,500
shark images which have detailed labels.  There is an additional ~110,000 shark
images with minimal labeling, some simply labeled as a shark image.  Finally,
there are ~530,000 images labeled 'No Shark'.  This scripts attempts to maximize
the use of the images with detailed labels while also using other available image
without creating a very uneven dataset.

Created on Thu Jan  9 15:46:28 2020

@author: Deep Thought
"""
import os

os.chdir('C:\\Users\\Deep Thought\\Desktop\\White Shark CNN\\Multi-Label CNN Model\\')

import WS_Utils as Util

'''No Shark image set subset'''
#Subset a series of images simply labeled 'No Shark'.  The total data set is
#currently 533,956 images.

print('Starting to sub-sample No Shark images')

#Proportion of 'No Shark' images .  This should yield ~43,000 images.
NS_Prop = 0.05

#Image path
path = "D:\\Population Study Videos\\Training Data\\Training Images\\No shark\\"

#subset path
output_path = "E:\\CNN_training_set_2017\\"

Util.ImageSubSample(path, output_path, NS_Prop)

'''Shark subset image set'''
#Subset a series of images simply labeled 'Shark'.  The total data set is
#currently 60,165 images.

print('Starting to sub-sample Shark images')

#Proportion of 'Shark' images.  This should yield ~9,000 images
S_Prop = 0.15

#Image path
path = "D:\\Population Study Videos\\Training Data\\Training Images\\Shark_Clean\\"

#subset path
output_path = "E:\\Shark\\"

Util.ImageSubSample(path, output_path, S_Prop)

#random_file=random.choice(os.listdir("Folder_Destination"))

'''Collate the labeled Shark image set'''
#Shark images with detailed labels are currently in multiple directories and
#sub-directories.  This function moves them all into the training folder (~30,500).

print('Collating labeled shark images')

#Image path
path = "D:\\Population Study Videos\\Training Data\\Training Images\\ID Catalog\\"

#subset path
output_path = "E:\\Shark_2\\"

Util.ImageAgg(path, output_path)

'''Meta-data labeling'''
#The FastAI multi-label CNN requires a csv listing the filename and labels.

print('Creating a metadata files')

#Image path
path = "E:\\CNN_training_set_2017\\"

metaFileName = 'ws_metadata.csv'

#keyword_filter
kw_filter = ['Shark', 'No_shark', 'Gill', 'Pelvic', 'Caudal', 'Dorsal']

Util.imageMetaLabel(path, metaFileName, kw_filter)


from django.shortcuts import render, redirect
from django.urls import reverse
from django.core.paginator import Paginator
from django.conf import settings

import csv

def index(request):
    return redirect(reverse('bus_stations'))

def bus_stations(request):
    with open(settings.BUS_STATION_CSV, encoding='utf-8', newline='') as csvfile:
        reader = csv.DictReader(csvfile)
        bas_book = []
        for row in reader:
            bas_book.append(row)

    page = int(request.GET.get('page', 1))
    pagi = Paginator(bas_book, 10)

    if page < 1:
        page = 1
    if page > pagi.num_pages:
        page = pagi.num_pages

    context = {
        'bus_stations': pagi.page(page).object_list,
        'page': pagi.page(page),
    }

    return render(request, 'stations/index.html', context)


from __future__ import annotations

from django.conf import settings
from django.http import HttpRequest

def get_scheme(request: HttpRequest) -> str:
    scheme = "https"
    if settings.DEBUG and request is not None:
        if "HTTP_X_FORWARDED_PROTO" in request.META:
            scheme = request.META["HTTP_X_FORWARDED_PROTO"]
        else:
            scheme = request.scheme
    return scheme


#8.8 Python Crash Course
def make_album(artist_name, album_title, number_songs=''):
    """Returns a dictionary with artist and album name"""
    album = {'artist': artist_name, 'album': album_title}
    if number_songs:
        album['number_songs'] = number_songs
    return album

while True:
    print("\nEnter the album's information: ")
    print("(enter 'q' to quit anytime)")

    artist_name = input("Artists name: ")
    if artist_name == 'q':
        break

    album_title = input("Album title: ")
    if album_title == 'q':
        break

    album_info = make_album(artist_name, album_title)
    print(album_info)


import math
import sys

import numpy as np
# SEGUNDA PARTE -- pygame
import pygame

#VARIABLES GLOBALES
FILAS = 6
COLUMNAS = 7
AZUL = (0,0,255)
NEGRO = (0,0,0)
ROJO = (255,0,0)
AMARILLO = (255,255,0)

"Para representar el tablero utilizaremos una matriz de 6 filas y 7 columnas"
def create_board():
    tablero = np.zeros((6,7))
    return tablero

"Funcion que coloca la ficha del jugador en la posicion indicada comprobando dicha posicion"
def coloca_ficha(tablero, seleccion, turno):

    # Lo primero que hacemos es comprobar si en la casilla no hay una ficha previa
    valida, rows = es_valida(tablero, seleccion)

    if valida:
        if turno == 0:
            # Las fichas del jugador #1 tendran el id = 1
            tablero[rows-1][seleccion-1] = 1

        else:
            # Las fichas del jugador #2 tendran el id = 2
            tablero[rows-1][seleccion-1] = 2

    return tablero, rows

"Funcion que comprueba si en la casilla seleccionada se puede colocar una ficha o no"
def es_valida(tablero, seleccion):
    check = True
    rows,_ = tablero.shape

    if tablero[rows-1][seleccion-1] != 0:
        check = False
    else:
        return check, rows

    # En el caso de que la posicion este ocupada, comprobamos las filas superiores buscando alguna casillas vacia hasta llegar a la ultima fila
    if not check:
        # Se podria utilizar un bucle for also! :)
        while tablero[rows-1][seleccion-1] != 0:
            rows -= 1
            # En este caso, la columna estara ocupada por lo que no se permiten nuevas casillas
            if rows == 0:
                return False, rows

        return True, rows

"Funcion que comprueba una jugada ganadora que finalice la partida"
def movimiento_ganador(tablero, num_ficha):
    # Para implementar esta funcion lo que vamos a hacer consiste en ir recorriendo el tablero y comprobar si hay alguna jugada que finalice el juego. No es la manera
    # mas eficaz, ya que seria ir comprobando alrededor de la ficha actual, pero de primeras vamos a implementarlo de esta manera.

    # Filas y columnas
    rows, columns = tablero.shape

    # Caso horizantal: Recorremos hasta la 4º columna, ya que a partir de esta no se pueden dar 4 fichas consecutivas iguales
    for i in range(rows):
        for j in range(columns-3):
            if tablero[i][j] == num_ficha and tablero[i][j+1] == num_ficha and tablero[i][j+2] == num_ficha and tablero[i][j+3] == num_ficha:
                return True

    # Caso horizantal: Recorremos hasta la 3º fila, ya que a partir de esta no se pueden dar 4 fichas consecutivas iguales
    for j in range(columns):
         for i in range(rows-3):
            if tablero[i][j] == num_ficha and tablero[i+1][j] == num_ficha and tablero[i+2][j] == num_ficha and tablero[i+3][j] == num_ficha:
                return True

    # Caso diagonal pendiente negativa: En este caso, recorremos hasta la 4º columna y 3º fila
    for i in range(rows-3):
        for j in range(columns-3):
            if tablero[i][j] == num_ficha and tablero[i+1][j+1] == num_ficha and tablero[i+2][j+2] == num_ficha and tablero[i+3][j+3] == num_ficha:
                return True

    # Caso diagonal pendiente positiva:
    for i in range(3,rows):
        for j in range(columns-3):
            if tablero[i][j] == num_ficha and tablero[i-1][j+1] == num_ficha and tablero[i-2][j+2] == num_ficha and tablero[i-3][j+3] == num_ficha:
                return True

# Funcion mediante la cual se implementa el diseño de la interfaz
def dibuja_tablero(tablero, tam_circulo, radio):

    for i in range(FILAS):
        for j in range(COLUMNAS):
            # Dibujamos un rectangulo azul que represente el tablero y despues dibujamos los circulos correspondientes
            pygame.draw.rect(Screen, AZUL, (j*tam_circulo, i*tam_circulo+tam_circulo, tam_circulo, tam_circulo))

            # En este caso, no hay ninguna ficha colocada -> Circulo negro (vacio)
            if tablero[i][j] == 0:
                pygame.draw.circle(Screen, NEGRO, (int(j*tam_circulo + tam_circulo/2), int(i*tam_circulo + tam_circulo + tam_circulo/2)), radio)

            # En este caso, el jugador 1 coloca ficha, por lo que dibujamos el circulo de color rojo
            elif tablero[i][j] == 1:
                # Dibujo de los circulos
                pygame.draw.circle(Screen, ROJO, (int(j*tam_circulo + tam_circulo/2), int(i*tam_circulo + tam_circulo + tam_circulo/2)), radio)

            # En este caso, el jugador 2 coloca ficha, por lo que dibujamos el circulo de color amarillo
            else:
                pygame.draw.circle(Screen, AMARILLO, (int(j*tam_circulo + tam_circulo/2), int(i*tam_circulo + tam_circulo + tam_circulo/2)), radio)

    pygame.display.update()

# Creamos el tablero
tablero = create_board()

# Mediante el siguiente loop vamos a controlar si la partida ha terminado o no
game_over = False
turno = 0
filas, columnas = tablero.shape

# Inicializamos el pygame
pygame.init()

# Elementos
tam_circulo = 100 #100 px
width = columnas * tam_circulo
height = (filas+1) * tam_circulo #Fila adicional donde se muestra la ficha antes de ser colocada

size = (width, height)

radio = int(tam_circulo/2 - 5)

Screen = pygame.display.set_mode(size)

dibuja_tablero(tablero, tam_circulo, radio)
pygame.display.update()

texto = pygame.font.SysFont("monospace", 60)

while not game_over:

    for event in pygame.event.get():
        # Cierre de la ventana
        if event.type == pygame.QUIT:
            sys.exit()

        if event.type == pygame.MOUSEMOTION:
            # Para evitar que todo el rectangulo superior se quede rojo o amarillo, lo que hacemos es dibujar de nuevo un rectangulo
            pygame.draw.rect(Screen, NEGRO, (0, 0, width, tam_circulo))
            posx = event.pos[0]

            if turno == 0:
                pygame.draw.circle(Screen, ROJO, (posx, int(tam_circulo/2)), radio)
            else:
                pygame.draw.circle(Screen, AMARILLO, (posx, int(tam_circulo/2)), radio)

        pygame.display.update()

        # Ahora, ya no necesitamos pedirle a los usuarios que inserten una entreda. Luego mediante el evento click del raton, colocaremos las fichas en la columna
        # que se haya indicado
        if event.type == pygame.MOUSEBUTTONDOWN:

            # Entrada del jugador #1
            if turno == 0:
                #Obtenemos la posicion x del tablero
                posx = event.pos[0] #Este valor se encuentra entre 1 y 700

                #Como en el problema original pediamos al usuario un valor entre 1-7, ahora para mantener esa dinamica lo que hacemos es sumar
                #a la variable una unidad para ambos turnos
                seleccion = int(math.floor(posx/tam_circulo)) +1 #Redondeo mediante math.floor

                tablero, rows = coloca_ficha(tablero, seleccion, turno)

                # Ahora deberemos comprobar si el jugador ha ganado o la partida continua
                game_over = movimiento_ganador(tablero, 1)

                if game_over:
                    pygame.draw.rect(Screen, NEGRO, (0, 0, width, tam_circulo))
                    label1 = texto.render("GANA EL JUGADOR #1", 1, ROJO)
                    Screen.blit(label1, (40,10))

            # Entrada del jugador #2
            else:
                posx = event.pos[0] #Este valor se encuentra entre 0 y 700
                seleccion = int(math.floor(posx/tam_circulo)) + 1

                tablero, rows = coloca_ficha(tablero, seleccion, turno)

                # Ahora deberemos comprobar si el jugador ha ganado o la partida continua
                game_over = movimiento_ganador(tablero, 2)

                if game_over:
                    pygame.draw.rect(Screen, NEGRO, (0, 0, width, tam_circulo))
                    label2 = texto.render("GANA EL JUGADOR #2", 1, AMARILLO)
                    Screen.blit(label2, (40,10))

            # De esta manera, vamos variando la variable turno entre 0 y 1
            turno += 1
            turno = turno % 2

            print(tablero, "\n")
            dibuja_tablero(tablero, tam_circulo, radio)

            # Cuando la partida haya terminado, implementamos una espera para mostrar un texto referido al jugador ganador
            if game_over:
                pygame.time.wait(4500)


from domain.carte import Carte

class CarteService:
    def __init__(self, repo):
        self.__repo = repo
        self.lista = []

    def add_carte(self, is_carte, titlu, autor, an_apaitie):
        carte = Carte(id_carte=is_carte, titlu=titlu, autor=autor, an_aparitie=an_apaitie)
        self.__repo.add(carte)

    def sterge_carte(self, numar):
        for carte in self.__repo.carti:
            # print(carte.get_an_aparitie())
            an_aparitie = carte.get_an_aparitie()
            ok = 0
            while an_aparitie > 0:
                uc = an_aparitie % 10
                if uc == numar:
                    ok = 1
                an_aparitie = an_aparitie / 10
            if ok == 1:
                self.lista.append(carte)
                res = self.__repo.stergere(carte.get_an_aparitie())
                if res == True:
                    print("Cartile au fost sterse cu succes!")

    def filtru(self, titlu, an):
        lista = []
        for carte in self.__repo.carti:
            if carte.get_titlu() == titlu and carte.get_an_aparitie() < an:
                lista.append(carte)
        if titlu == "" and an == -1:
            lista.append(self.__repo.carti)
        return lista

Semestrul 1/FP/practic FP/rezolvari practic/biblioteca/service/carte_service.py


import requests
import random
from Crypto.Cipher import AES
import base64
import codecs
import os
import json
from pypinyin import  lazy_pinyin

class Netease:
    def __init__ (self):
        self.b = '010001'
        self.c = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
        self.d = '0CoJUm6Qyw8W8jud'
    #随机的十六位字符串

    def createSecretKey(self, size):
        return (''.join(map(lambda xx: (hex(ord(xx))[2:]), str(os.urandom(size)))))[0:16]
    #AES加密算法

    def AES_encrypt(self, text, key, iv):
        pad = 16 - len(text) % 16
        if type(text)==type(b''):
            text = str(text, encoding='utf-8')
        text = text + str(pad * chr(pad))
        encryptor = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8"))
        encrypt_text = encryptor.encrypt(text.encode("utf8"))
        encrypt_text = base64.b64encode(encrypt_text)
        return encrypt_text
    #得到第一个加密参数

    def Getparams(self, a, SecretKey):
        #0102030405060708是偏移量，固定值
        iv = '0102030405060708'
        h_encText = self.AES_encrypt(a, self.d, iv)
        h_encText = self.AES_encrypt(h_encText,SecretKey,iv)
        return h_encText

    #得到第二个加密参数

    def GetSecKey(self, text, pubKey, modulus):
        # 因为JS做了一次逆序操作
        text = text[::-1]
        rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16)
        return format(rs, 'x').zfill(256)

    #得到表单的两个参数

    def GetFormData(self, a):
        SecretKey = self.createSecretKey(16)
        params = self.Getparams(a, SecretKey)
        enSecKey = self.GetSecKey(SecretKey, self.b, self.c)
        data = {
            "params" : str(params, encoding='utf-8'),
            "encSecKey" : enSecKey
        }
        return data

    def searchMusic(self, search_song, headers):
        searchUrl = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
        musicStr = ''.join(lazy_pinyin(search_song))
        key = '{hlpretag:"",hlposttag:"</span>",s:"' + musicStr + '",type:"1",csrf_token:"",limit:"30",total:"true",offset:"0"}'
        dataStr = str({'s': musicStr, 'csrf_token': ''})
        FormData = self.GetFormData(key)
        response = requests.request('POST', searchUrl, data=FormData, headers={
            'User-agent': headers,
            'referer': 'https://music.163.com/',
            'Host': 'music.163.com',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'})
        song_writer = []
        song_id = []
        song_name = []
        song_zj = []
        song_dict = json.loads(response.text)
        # for song in song_dict['result']['songs']:
        #     print(song)
        #     song_name.append(song['name'])
        #     song_id.append(song['id'])
        #     song_ar = song['ar']
        #     if len(song_ar) == 2:
        #         song_writer.append(song_ar[0]['name'] + '_' + song_ar[1]['name'])
        #     else:
        #         song_writer.append(song_ar[0]['name'])
        #     song_zj.append(song['al']['name'])
        return song_dict['result']['songs']
if __name__ == '__main__':
    search_song = input("请输入搜索音乐的名称:")
    nete_music = Netease()
    song = nete_music.searchMusic(search_song, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36')
    print(song)


import random
import os
import pickle
from tensorflow.python import keras
import numpy as np
import processor as processor

class Data:
    def __init__(self, dir_path):
        self.files = list(self.find_pickle_files(dir_path))
        self.file_dict = {
            'train': self.files[:int(len(self.files) * 0.8)],
            'eval': self.files[int(len(self.files) * 0.8): int(len(self.files) * 0.9)],
            'test': self.files[int(len(self.files) * 0.9):],
        }
        self._seq_file_name_idx = 0
        self._seq_idx = 0
        self.pad_token = processor.RANGE_NOTE_ON + processor.RANGE_NOTE_OFF + processor.RANGE_TIME_SHIFT + processor.RANGE_VEL

    def batch(self, batch_size, length, mode='train'):

        batch_files = random.sample(self.file_dict[mode], k=batch_size)

        batch_data = [
            self._get_seq(file, length)
            for file in batch_files
        ]
        return np.array(batch_data)  # batch_size, seq_len

    def slide_seq2seq_batch(self, batch_size, length, mode='train'):
        data = self.batch(batch_size, length+1, mode)
        x = data[:, :-1]
        y = data[:, 1:]
        return x, y

    def _get_seq(self, fname, max_length=None):
        with open(fname, 'rb') as f:
            data = pickle.load(f)
        if max_length is not None:
            if max_length <= len(data):
                start = random.randrange(0,len(data) - max_length)
                data = data[start:start + max_length]
            else:
                data = np.append(data, self.pad_token+2)
                while len(data) < max_length:
                    data = np.append(data, self.pad_token)
        return data

    def find_pickle_files(self, root):

        for path, _, files in os.walk(root):
            for name in files:
                if name.endswith('pickle'):
                    yield os.path.join(path, name)


# Review of lists. You work at Len’s Slice, a new pizza joint in the neighborhood. You are going to use your knowledge of Python lists to organize some of your sales data.

# Make a list of pizza toppings
toppings = ["pepperoni", "pineapple", "cheese", "sausage", "olives", "anchovies", "mushrooms"]
print(toppings)

# To keep track of how much each kind of pizza slice costs, create a list called prices that holds the following integer values:
prices = [2, 6, 1, 3, 2, 7, 2]
print(prices)

# How many items are 2 bucks?
num_two_dollar_slices = prices.count(2)
print(f"There are {num_two_dollar_slices} menu items costing $2")

num_of_pizzas = len(toppings)
print(f"We sell {num_of_pizzas} different kinds of pizza!")

pizza_and_prices = [
  [2, "pepperoni"],
  [6, "pineapple"],
  [1, "cheese"],
  [3, "sausage"],
  [2, "olives"],
  [7, "anchovies"],
  [2, "mushrooms"]
]

print(pizza_and_prices)

# Sort pizza prices by cheapest to most expensive
pizza_and_prices.sort()

print(pizza_and_prices)

# Find the cheapest pizza
cheapest_pizza = pizza_and_prices[0][1]
print(f"The cheapest pizza is {cheapest_pizza}")

# A man walks in and asks for our most priciest pizza!
priciest_pizza = pizza_and_prices[-1][1]
print(f"The priciest pizza is {priciest_pizza}")

# That's it, we're out of anchovies, remove it from the list
pizza_and_prices.pop(-1)
print(pizza_and_prices)

# Add new topping for peppers
pizza_and_prices.insert(-2, [2.5, "peppers"])
print(f"The new menu is {pizza_and_prices}")

# Store three cheapest pizzas in a list
three_cheapest = pizza_and_prices[:3]
print(f"The three cheapest pizzas are {three_cheapest}")


import matplotlib.pyplot as plt
import pandas as pd

plt.style.use("fast")

data = pd.read_csv("subplot.csv")
age = data["Age"]
python = data["Python"]
js = data["JavaScript"]
all_devs = data["All_Devs"]

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, sharex=True)

ax1.plot(age, python, label="Python")
ax1.plot(age, js, label="JavaScript")
ax1.set_ylabel("Median Salary (USD)")
ax1.set_title("Median Salary by age")
ax1.legend()

ax2.plot(age, all_devs, label="All Developers")
ax2.set_xlabel("Age")
ax2.set_ylabel("Median Salary (USD)")
ax2.legend()

plt.tight_layout()
plt.show()

fig.savefig("subplot.png")


from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from scapy.all import *
from app.network_functions import *
from socket import *

class ScapyArpSnifferWorker(QObject):
    send_list_signal = pyqtSignal(str, str)
    send_ip_signal = pyqtSignal(str)
    finished = pyqtSignal(name="done")

    def __init__(self):
        super().__init__()
        # self.live_hosts = live_hosts
        # print("Received list: {}".format(self.live_hosts))

    @pyqtSlot()
    def task(self):

        def arp_display(pkt):
            if pkt[0][1].op == 1:
                print("{} with MAC {} is asking where {} is".format(pkt[ARP].psrc, pkt[ARP].hwsrc, pkt[ARP].pdst))
            elif pkt[0][1].op == 2:
                print("{} is at {}".format(pkt[ARP].psrc, pkt[ARP].hwsrc))
            ip_mac_list = [
                pkt[ARP].psrc,
                pkt[ARP].hwsrc
            ]
            # host_dict = [[
            #     pkt[ARP].psrc,
            #     pkt[ARP].hwsrc
            # ]]
            # Check if IP Address is in our list:
            # Also eliminate the special '0.0.0.0' case (Host without IP address yet):
            if not (pkt[ARP].psrc == '0.0.0.0'):
                print("Worker sending {}".format(ip_mac_list))
                # print("Worker sending ip signal {}".format(pkt[ARP].psrc))
                # self.send_ip_signal.emit(pkt[ARP].psrc)
                self.send_list_signal.emit(pkt[ARP].psrc, pkt[ARP].hwsrc)

            # If an IP Address has been allocated to another host, update the MAC address:
            # elif any(d.get('IP Address') == pkt[ARP].psrc and not d.get('MAC Address') == pkt[ARP].hwsrc for d in
            #          self.live_hosts):
            #     for d in self.live_hosts:
            #         if d['IP Address'] == pkt[ARP].psrc:
            #             d['MAC Address'] = pkt[ARP].hwsrc
            #     print("Host {} updated with new MAC Address {}".format(pkt[ARP].psrc, pkt[ARP].hwsrc))
            #     print(self.live_hosts)
            #     self.send_list_signal.emit(self.live_hosts)

        print(sniff(prn=arp_display, filter="arp"))
        # self.send_list_signal.emit(self.live_hosts)

class ScapyArpQueryWorker(QObject):
    str_signal = pyqtSignal(str)
    finished = pyqtSignal(name="done")

    def __init__(self, iface, first_ip,last_ip):
        super().__init__()
        self.iface = iface
        self.first_ip = first_ip
        self.last_ip = last_ip
        self._is_running = True

    @pyqtSlot()
    def stop_worker(self):
        print("Worker received the Stop signal")
        self._is_running = False
        print("_is_running is {}".format(self._is_running))

    @pyqtSlot()
    def task(self):

        for i in range(self.first_ip, self.last_ip + 1):
            dotted_ip = integer_to_dotted_decimal_ip(i)
            print("Sending packet to", dotted_ip)
            self.str_signal.emit(dotted_ip)
            pkt = sendp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=dotted_ip), verbose=False)
            # if pkt[0][0][1]:
            #     print("{} is at {}".format(dotted_ip, pkt[0][0][1].hwsrc))
            QThread.msleep(10)
            QApplication.processEvents()
            if not self._is_running:
                break

        self._is_running = False
        self.finished.emit()

class FqdnWorker(QObject):
    send_fqdn_signal = pyqtSignal(str, str)
    # finished = pyqtSignal(name="done")

    def __init__(self):
        super().__init__()

    # @pyqtSlot() # Again, @pyqtSlot seems to break things. Avoid it in this case.
    def task(self, ip):

        fqdn = getfqdn(ip)
        self.send_fqdn_signal.emit(ip, fqdn)


#simulador de dado de 1 a 6
#sem interface (incompleto)
import random
import PySimpleGUI as sg
class SimuladorDeDado:
    def __init__(self):
        self.valor_minimo = 1
        self.valor_maximo = 6
        #layout
        self.layout = [
            [sg.Text('Jogar o dado?')],
            [sg.Button('sim'),sg.Button('Não')]
        ]

    def Iniciar(self):
        #criar a janela
        self.janela = sg.Window('simulador de dado',layout=self.layout)
        #ler eventos
        self.eventos, self.valores = self.janela.Read()
        #fazer algo com os valores
        try:
            if self.eventos == 'sim' or self.eventos == 's':
                self.GerarValorDoDado()
            elif self.eventos == 'Não' or self.eventos == 'n':
                print('Agradecemos sua participação!!!')
            else:
                print('favor digitar sim,s,não ou n')
        except:
            print('Ocorreu um erro durante sua operação!')
    def GerarValorDoDado(self):
        print(random.randint(self.valor_minimo,self.valor_maximo))

simulador = SimuladorDeDado()
simulador.Iniciar()


"""It contains  DoctorProfile."""

from django.contrib.auth.models import User
from django.db import models

class DoctorProfile(models.Model):
    """Defines Doctor model."""

    KIND_CHOICES = (
        ('Urologist', 'Urologist'),
        ('Gynecologist', 'Gynecologist'),
        ('Dermatologist', 'Dermatologist'),
        ('Nephrologist', 'Nephrologist'),
        ('Endocrinologist', 'Endocrinologist'),
        ('Cardiologist', 'Cardiologist'),
        ('Virologist', 'Virologist'),
        ('Surgeon', 'Surgeon'),
    )

    profile_picture = models.ImageField(
        upload_to='users',
        blank=True
    )
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length=30)
    specialty = models.CharField(max_length=20, choices=KIND_CHOICES)

    def __str__(self):
        return f"{self.name}"


import arcade
import arcade.gui

import constants

class MainMenu(arcade.View):
    def __init__(self, window: arcade.Window = None, game_loop = None):
            super().__init__(window)

            self._game_loop = game_loop

            self._ui_manager = arcade.gui.UIManager()
            self._ui_manager.enable()

            self.v_box = arcade.gui.UIBoxLayout(space_between=5)

            self._create_battle_button = arcade.gui.UIFlatButton(text="Battle!", width=constants.MAIN_MENU_BUTTON_WIDTH)

            self._create_battle_button.on_click = self._create_battle

            self.v_box.add(self._create_battle_button)

            self._create_unit_button = arcade.gui.UIFlatButton(text= "Create Units [Disabled]", width=constants.MAIN_MENU_BUTTON_WIDTH)

            self._create_unit_button.on_click = self._create_unit

            self.v_box.add(self._create_unit_button)

            self._exit_button = arcade.gui.UIFlatButton(text="Exit", width=constants.MAIN_MENU_BUTTON_WIDTH)

            self._exit_button.on_click = self._exit_game

            self.v_box.add(self._exit_button)

            self._ui_manager.add(
                arcade.gui.UIAnchorWidget(
                    anchor_x="center_x",
                    anchor_y="center_y",
                    child=self.v_box)
            )

    def on_show_view(self):
        """ Called when switching to this view"""
        arcade.set_background_color(arcade.color.GRAY)

    def on_hide_view(self):
        self._ui_manager.disable()
        return super().on_hide_view()

    def on_draw(self):
        """ Draw the menu """
        self.clear()

        self._ui_manager.draw()

    def _create_battle(self, event):
        self._game_loop.create_battle()

    def _create_unit(self, event):
        pass
        #self._game_loop.create_unit()

    def _exit_game(self, event):
        self._game_loop.exit()


'''
< 가르침 >
https://www.acmicpc.net/problem/1062
문제)
- K개의 글자만 안다.
- 남극의 단어는 "anta"으로 시작 ~ "tica"로 끝난다.
- 남극언어에 단어는 N개 밖에 없다고 가정
- 학생들이 읽을 수 있는 단어의 최댓값?
입력)
- 1     : 단어의 개수N 아는 글자K
- 2[N]  : N개의 남극단어
출력)
K개의 글자를 알때 읽을수 있는 단어 개수의 최댓값?
'''
from itertools import combinations
import sys
N, K = map(int, input().split())
first = set(['a', 'c', 'i', 'n', 't'])
words = [set(input().rstrip()[4:-4]) - first for _ in range(N)]
remain = set(chr(i) for i in range(ord('a'), ord('z') + 1)) - first

if K < 5: print(0); exit()
elif K == 26: print(N); exit()

ans = 0
for i in combinations(remain, K-5):
    readcnt = 0
    for word in words:
        # 단어 - 읽을수있는 단어 : 0이아니면 못읽음
        if word - set(i): continue
        readcnt += 1
    ans = max(readcnt, ans)

print(ans)

'''
pypy ❌
python ❌

실수 set(i) : combinations을 set해도 내부 원소는 tuple이다
'''