# coding: utf-8

# In[1]:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend('agg')
import seaborn as sns
from matplotlib.animation import ArtistAnimation

import os

MACHINE_EPSILON = np.finfo(np.double).eps

#adapted from  https://nlml.github.io/in-raw-numpy/in-raw-numpy-t-sne/

#https://towardsdatascience.com/implementing-t-sne-in-tensorflow-manual-back-prop-in-tf-b08c21ee8e06
#https://nlml.github.io/in-raw-numpy/in-raw-numpy-t-sne/
#https://towardsdatascience.com/t-sne-python-example-1ded9953f26
#https://towardsdatascience.com/kl-divergence-python-example-b87069e4b810

# SNE
#
# \begin{equation}
# q_{j|i}=\frac{exp(-||y_i-y_j||^2)}{\sum_{kdifi}exp(-||y_i-y_j||^2)}
# \end{equation}
#
# \begin{equation}
# C = \sum_{i}KL(P_i||Q_i)=\sum_i\sum_jp_{j|i}log\frac{p_{j|i}}{q_{j|i}}
# \end{equation}
#
# Symmetric SNE
#
# \begin{equation}
# q_{ij}=\frac{exp(-||y_i-y_j||^2)}{\sum_{kdifl}exp(-||y_k-y_l||^2)}
# \end{equation}

# # SNE

# In[2]:

def neg_squared_euc_dists(X):
    """Compute matrix containing negative squared euclidean
    distance for all pairs of points in input matrix X

    # Arguments:
        X: matrix of size NxD
    # Returns:
        NxN matrix D, with entry D_ij = negative squared
        euclidean distance between rows X_i and X_j
    """
    # Math? See https://stackoverflow.com/questions/37009647
    sum_X = tf.reduce_sum(X**2, 1)
    D = tf.transpose(-2 * tf.matmul(X, tf.transpose(X)) + sum_X) + sum_X
    return -D

def softmax(X, diag_zero=True):
    """Take softmax of each row of matrix X."""

    # We usually want diagonal probailities to be 0.
    if diag_zero:
        X = tf.matrix_set_diag(X,tf.zeros([X.shape[0].value],dtype=tf.float64))

    return tf.nn.softmax(X)

def calc_prob_matrix(distances, sigmas=None):
    """Convert a distances matrix to a matrix of probabilities."""
    if sigmas is not None:
        two_sig_sq = 2. * tf.reshape(sigmas,(-1, 1))**2
        return softmax(distances / two_sig_sq)
    else:
        return softmax(distances)

# # Symmetric SNE

# \begin{equation}
# p_{ij} = \frac{P + P^T}{2N}
# \end{equation}
#
# \begin{equation}
# q_{ij}=\frac{exp(-||y_i-y_j||^2)}{\sum_{kdifl}exp(-||y_k-y_l||^2)}
# \end{equation}

# In[3]:

def binary_search(eval_fn, target, tol=1e-10, max_iter=10000,
                  lower=1e-20, upper=1000.):
    """Perform a binary search over input values to eval_fn.

    # Arguments
        eval_fn: Function that we are optimising over.
        target: Target value we want the function to output.
        tol: Float, once our guess is this close to target, stop.
        max_iter: Integer, maximum num. iterations to search for.
        lower: Float, lower bound of search range.
        upper: Float, upper bound of search range.
    # Returns:
        Float, best input value to function found during search.
    """
    for i in range(max_iter):
        guess = (lower + upper) / 2.
        val = eval_fn(guess)
        if val > target:
            upper = guess
        else:
            lower = guess
        if np.abs(val - target) <= tol:
            break
    return guess

def calc_perplexity(prob_matrix):
    """Calculate the perplexity of each row
    of a matrix of probabilities."""
    entropy = -np.sum(prob_matrix * np.log2(prob_matrix), 1)
    perplexity = 2 ** entropy
    return perplexity

def perplexity(distances, sigmas):
    """Wrapper function for quick calculation of
    perplexity over a distance matrix."""
    return calc_perplexity(numpy_calc_prob_matrix(distances, sigmas))

def find_optimal_sigmas(distances, target_perplexity):
    """For each row of distances matrix, find sigma that results
    in target perplexity for that role."""
    sigmas = []
    # For each row of the matrix (each point in our dataset)
    for i in range(distances.shape[0]):
        # Make fn that returns perplexity of this row given sigma
        eval_fn = lambda sigma:             perplexity(distances[i:i+1, :], np.array(sigma))
        # Binary search over sigmas to achieve target perplexity
        correct_sigma = binary_search(eval_fn, target_perplexity)
        # Append the resulting sigma to our output array
        sigmas.append(correct_sigma)
    return np.array(sigmas)

# In[4]:

def numpy_neg_squared_euc_dists(X):
    """Compute matrix containing negative squared euclidean
    distance for all pairs of points in input matrix X

    # Arguments:
        X: matrix of size NxD
    # Returns:
        NxN matrix D, with entry D_ij = negative squared
        euclidean distance between rows X_i and X_j
    """
    # Math? See https://stackoverflow.com/questions/37009647
    sum_X = np.sum(np.square(X), 1)
    D = np.add(np.add(-2 * np.dot(X, X.T), sum_X).T, sum_X)
    return -D

def numpy_softmax(X, diag_zero=True):
    """Take softmax of each row of matrix X."""

    # Subtract max for numerical stability
    e_x = np.exp(X - np.max(X, axis=1).reshape([-1, 1]))

    # We usually want diagonal probailities to be 0.
    if diag_zero:
        np.fill_diagonal(e_x, 0.)

    # Add a tiny constant for stability of log we take later
    e_x = e_x + 1e-8  # numerical stability

    return e_x / e_x.sum(axis=1).reshape([-1, 1])

def numpy_calc_prob_matrix(distances, sigmas=None):
    """Convert a distances matrix to a matrix of probabilities."""
    if sigmas is not None:
        two_sig_sq = 2. * np.square(sigmas.reshape((-1, 1)))
        return numpy_softmax(distances / two_sig_sq)
    else:
        return numpy_softmax(distances)

def numpy_neg_squared_euc_dists(X):
    """Compute matrix containing negative squared euclidean
    distance for all pairs of points in input matrix X

    # Arguments:
        X: matrix of size NxD
    # Returns:
        NxN matrix D, with entry D_ij = negative squared
        euclidean distance between rows X_i and X_j
    """
    # Math? See https://stackoverflow.com/questions/37009647
    sum_X = np.sum(np.square(X), 1)
    D = np.add(np.add(-2 * np.dot(X, X.T), sum_X).T, sum_X)
    return -D

def numpy_calc_prob_matrix(distances, sigmas=None):
    """Convert a distances matrix to a matrix of probabilities."""
    if sigmas is not None:
        two_sig_sq = 2. * np.square(sigmas.reshape((-1, 1)))
        return numpy_softmax(distances / two_sig_sq)
    else:
        return numpy_softmax(distances)

def numpy_p_conditional_to_joint(P):
    """Given conditional probabilities matrix P, return
    approximation of joint distribution probabilities."""
    return (P + P.T) / (2. * P.shape[0])

# In[5]:

def q_joint(Y):
    """Given low-dimensional representations Y, compute
    matrix of joint probabilities with entries q_ij."""
    # Get the distances from every point to every other
    distances = neg_squared_euc_dists(Y)
    # Take the elementwise exponent
    exp_distances = tf.math.exp(distances)
    # Fill diagonal with zeroes so q_ii = 0
    exp_distances = tf.matrix_set_diag(exp_distances,tf.zeros([exp_distances.shape[0].value],dtype=tf.float64))

    # Divide by the sum of the entire exponentiated matrix
    return exp_distances / tf.reduce_sum(exp_distances), None

def p_conditional_to_joint(P):
    """Given conditional probabilities matrix P, return
    approximation of joint distribution probabilities."""
    return (P + tf.transpose(P)) / (2. * P.shape[0].value)

def p_joint(X, target_perplexity):
    """Given a data matrix X, gives joint probabilities matrix.

    # Arguments
        X: Input data matrix.
    # Returns:
        P: Matrix with entries p_ij = joint probabilities.
    """
    # Get the negative euclidian distances matrix for our data
    distances = numpy_neg_squared_euc_dists(X)
    # Find optimal sigma for each row of this distances matrix
    sigmas = find_optimal_sigmas(distances, target_perplexity)
    # Calculate the probabilities based on these optimal sigmas
    p_conditional = numpy_calc_prob_matrix(distances, sigmas)
    # Go from conditional to joint probabilities matrix
    P = numpy_p_conditional_to_joint(p_conditional)
    return P

def q_tsne(Y):
    """t-SNE: Given low-dimensional representations Y, compute
    matrix of joint probabilities with entries q_ij."""
    distances = neg_squared_euc_dists(Y)
    inv_distances = (1. - distances)**(-1)
    inv_distances = tf.matrix_set_diag(inv_distances,tf.zeros([inv_distances.shape[0].value],dtype=tf.float64))
    return inv_distances / tf.reduce_sum(inv_distances), inv_distances

# In[6]:

def tsne_grad(P, Q, Y, inv_distances):
    """Estimate the gradient of t-SNE cost with respect to Y."""
    pq_diff = P - Q
    pq_expanded = tf.expand_dims(pq_diff, 2)
    y_diffs = tf.expand_dims(Y, 1) - tf.expand_dims(Y, 0)

    # Expand our inv_distances matrix so can multiply by y_diffs
    distances_expanded = tf.expand_dims(inv_distances, 2)

    # Multiply this by inverse distances matrix
    y_diffs_wt = y_diffs * distances_expanded

    # Multiply then sum over j's
    grad = 4. * tf.reduce_sum(pq_expanded * y_diffs_wt,1)
    return grad

def backprop(C,P,Q,Y,Inv_distances):
    grad = tf.gradients(C,Y)#grad_fn(P, Q, Y, inv_distances)#
    update_Y = []
    update_Y.append(tf.assign(m,m*beta1 + (1-beta1)*(grad)))
    update_Y.append(tf.assign(v,v*beta1 + (1-beta1)*(grad**2)))
    m_hat = m/(1-beta1)
    v_hat = v/(1-beta2)
    adam = lr/(tf.sqrt(v_hat)+adam_e)
    update_Y.append(tf.assign(Y,Y-adam*m_hat))
    return grad,update_Y
# In[7]:

def parse_function(filename, label):
    image_string = tf.read_file(filename)

    # Don't use tf.image.decode_image, or the output shape will be undefined
    image = tf.image.decode_jpeg(image_string, channels=3)

    # This will convert to float values in [0, 1]
    image = tf.image.convert_image_dtype(image, tf.float32)

    image = tf.image.resize_images(image, [224, 224])

    return tf.squeeze(tf.reshape(image,[-1,224*224*3])), label

def get_dataset(root):
    classes = os.listdir(root)
    class_dict = {k:v for v,k in enumerate(classes)}
    print(class_dict)
    filenames = []
    labels = []
    for i in classes:
        path = os.path.join(root,i)
        files = os.listdir(path)
        for ii in files:
            filenames.append(os.path.join(path,ii))
            labels.append(class_dict[i])
            #print(path,i,class_dict[i])

    batch_size = len(filenames)
    dataset = tf.data.Dataset.from_tensor_slices((np.array(filenames),                                                  np.array(labels)))
    dataset = dataset.shuffle(len(filenames))
    dataset = dataset.map(parse_function, num_parallel_calls=4)
    dataset = dataset.batch(batch_size)
    dataset = dataset.prefetch(1)

    iterator = dataset.make_initializable_iterator()
    next_element = iterator.get_next()
    init_op = iterator.initializer
    with tf.Session() as sess:
        sess.run(init_op)
        for i in range(1):
            X,y = sess.run(next_element)
            print(X.shape)
            X_shape = X.shape
        sess.run(init_op)
    return dataset,classes,next_element,init_op,X_shape

import os
from PIL import Image
"""
root ='tsne/images'
images = os.listdir(root)
X = []
for i in images:
    img = os.path.join(root,i)
    img = Image.open(img).resize((200,200))
    img = np.array(img).flatten()
    X.append(img)
X = np.vstack(X)
"""

def update(i):
    label = 'timestep {0}'.format(i)
    print(label)
    sc.set_offsets(np.c_[b[-1][:,0]                                 ,b[-1][:,1]])
    return sc

from sklearn.datasets import load_digits
#X, y = load_digits(return_X_y=True)

# In[ ]:
flags = tf.app.flags
FLAGS = flags.FLAGS

flags.DEFINE_integer('num_dimensions', 2, 'Number of dimensions in P_i.')
flags.DEFINE_integer('perplexity', 50, 'Perplexity.')
flags.DEFINE_float('learning_rate', .1,
                   'Learning rate.')
flags.DEFINE_string('root', 'dataset/train',
                    'Path to the folder containing the image data separated by classes in subfolders.')

if __name__ == "__main__":
    root = FLAGS.root
    dataset,classes,next_element,init_op,X_shape = get_dataset(root)

    q_fn = q_tsne
    grad_fn = tsne_grad
    N,D = X_shape
    d = FLAGS.num_dimensions
    PERPLEXITY = FLAGS.perplexity

    beta1 = 0.9
    beta2 = 0.99
    adam_e = MACHINE_EPSILON
    lr = FLAGS.learning_rate
    grad_fn = tsne_grad

    P = tf.placeholder(tf.float64,shape=[N,N])

    Y = tf.get_variable("Y",shape=[N,d],dtype=tf.float64,initializer=tf.random_normal_initializer())

    m,v = tf.Variable(tf.zeros_like(Y)),tf.Variable(tf.zeros_like(Y))

    Q, inv_distances = q_fn(Y)

    Q= tf.maximum(Q,MACHINE_EPSILON)

    C = tf.reduce_sum( tf.reshape(P,[-1]) * tf.log(tf.maximum(tf.reshape(P,[-1]),MACHINE_EPSILON)                                               / tf.reshape(Q,[-1])))#(tf.reshape(Q,[-1])+tf.constant(1.e-10,dtype=tf.float64))))

    grad = grad_fn(P, Q, Y, inv_distances)
    update_Y = []
    update_Y.append(tf.assign(m,m*beta1 + (1-beta1)*(grad)))
    update_Y.append(tf.assign(v,v*beta1 + (1-beta1)*(grad**2)))
    m_hat = m/(1-beta1)
    v_hat = v/(1-beta2)
    adam = lr/(tf.sqrt(v_hat)+adam_e)
    update_Y.append(tf.assign(Y,Y-adam*m_hat))

    with tf.Session() as sess:
        fig = plt.figure(figsize=(8,8))
        images = []
        sess.run(init_op)
        X,y = sess.run(next_element)
        pp=p_joint(X,PERPLEXITY)
        sess.run(tf.initialize_all_variables())

        for i in range(1000):
            sess.run(init_op)
            X,y = sess.run(next_element)
            b,c,qq = sess.run([update_Y,C,Q],feed_dict={P:pp})
            xx,yy=b[-1].T
            print("iteration %d -- KL_Loss %f"%(i,c))

            if i%10==0:
                plt.title('KL_loss: {}'.format(c))
                img = [plt.scatter(b[-1][:,0],                            b[-1][:,1],                            c=y)]

                images.append(img)

        ani = ArtistAnimation(fig, images,interval=500)
        ani.save("class.mp4")
        plt.close('all')


from flask import Flask, jsonify, make_response, redirect, request

from services.translator import shorten, lookup_url

app = Flask(__name__)

# Ideally, this route would return 201 if it actually created a new URL, and 200 only if it didn't create a new one.
@app.route('/', methods=['POST'])
def create_short_url():
    try:
        u = shorten(request.json['url'])
    except (TypeError, KeyError):
        return make_response(jsonify({
            'error': 'Request body malformed',
            }),
            400,
        )

    return jsonify({
        'full_url': u.full_url,
        'short_url': u.short_url,
        'created': u.created,
    })

@app.route('/<short_url>', methods=['GET'])
def get_full_url(short_url):
    u = lookup_url(short_url)
    if u:
        # I use a temporary redirect here because I don't want to pollute my browser cache. Whether this should be
        # temporary or permanent depends on exactly why we'd be deploying a URL shortener and how durable we want
        # the shortened links to actually be. It could possibly be an option when creating the URL in the first place.
        return redirect(u.full_url, 307)
    else:
        return make_response(
            jsonify({'error': f'{short_url} does not map to a full url'}),
            404,
        )

if __name__ == '__main__':
    app.run()


import redis

from quart import abort

# For checking whether the credential issuer is our own.
# https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#get_your_google_api_client_id
#
# You may have multiple client ids, it's ok,
# just execute `SADD google_oauth_client_ids "..."` in redis-cli.
GOOGLE_OAUTH_CLIENT_IDS = 'google_oauth_client_ids'  # set.

# For checking whether the requests are sent from Lemon Squeezy.
# https://docs.lemonsqueezy.com/help/webhooks#signing-requests
#
# We also use this secret to generate user token with AES-128 algorithm,
# so please make sure that this secret is a **16 characters length** string,
# and do not contain any leading and trailing whitespace characters,
# for example "0123456789abcdef" (don't use it, just a example, haha).
LEMONSQUEEZY_SIGNING_SECRET = 'lemonsqueezy_signing_secret'  # string.

# Interact with the Lemon Squeezy backend.
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#api-overview
LEMONSQUEEZY_API_KEY = 'lemonsqueezy_api_key'  # string.

# Default host and port.
rds = redis.from_url('redis://localhost:6379')

def get_str_from_rds(key: str) -> str:
    value = rds.get(key)
    if not value:
        abort(500, f'"{key}" not exists')

    value = value.decode().strip()
    if not value:
        abort(500, f'"{key}" is empty')

    return value


import torch
import torch.nn as nn

class Embedder(nn.Embedding):

    def load_embeddings(self, embeds, scale=0.05):
        assert len(embeds) == self.num_embeddings

        embeds = torch.tensor(embeds)
        num_known = 0
        for i in range(len(embeds)):
            if len(embeds[i].nonzero()) == 0:
                nn.init.uniform_(embeds[i], -scale, scale)
            else:
                num_known += 1
        self.weight.data.copy_(embeds)
        print(f"{num_known} words have pretrained embeddings",
              f"(coverage: {num_known/self.num_embeddings:.3f})")


"""Methods to find information in the different pipelines of Clinica."""
import functools
import os
from os import PathLike
from pathlib import Path
from typing import List, Optional, Tuple

import pandas as pd

def _get_atlas_name(atlas_path: Path, pipeline: str) -> str:
    """Helper function for _extract_metrics_from_pipeline."""
    if pipeline == "dwi_dti":
        splitter = "_dwi_space-"
    elif pipeline in ("t1_freesurfer_longitudinal", "t1_freesurfer"):
        splitter = "_parcellation-"
    elif pipeline in ("t1-volume", "pet-volume"):
        splitter = "_space-"
    else:
        raise ValueError(f"Not supported pipeline {pipeline}.")
    try:
        atlas_name = atlas_path.stem.split(splitter)[1].split("_")[0]
    except Exception:
        raise ValueError(
            f"Unable to infer the atlas name from {atlas_path} for pipeline {pipeline}."
        )
    return atlas_name

def _get_mod_path(ses_path: Path, pipeline: str) -> Optional[Path]:
    """Helper function for _extract_metrics_from_pipeline.
    Returns the path to the modality of interest depending
    on the pipeline considered.
    """
    if pipeline == "dwi_dti":
        return ses_path / "dwi" / "dti_based_processing" / "atlas_statistics"
    if pipeline == "t1_freesurfer_longitudinal":
        mod_path = ses_path / "t1"
        long_ids = list(mod_path.glob("long*"))
        if len(long_ids) == 0:
            return None
        return (
            mod_path
            / long_ids[0].name
            / "freesurfer_longitudinal"
            / "regional_measures"
        )
    if pipeline == "t1_freesurfer":
        return ses_path / "t1" / "freesurfer_cross_sectional" / "regional_measures"
    if pipeline == "t1-volume":
        return ses_path / "t1" / "spm" / "dartel"
    if pipeline == "pet-volume":
        return ses_path / "pet" / "preprocessing"
    raise ValueError(f"Not supported pipeline {pipeline}.")

def _get_label_list(
    atlas_path: Path, metric: str, pipeline: str, group: str
) -> List[str]:
    """Helper function for _extract_metrics_from_pipeline.
    Returns the list of labels to use in the session df depending on the
    pipeline, the atlas, and the metric considered.
    """
    from clinica.iotools.converters.adni_to_bids.adni_utils import (
        replace_sequence_chars,
    )

    atlas_name = _get_atlas_name(atlas_path, pipeline)
    atlas_df = pd.read_csv(atlas_path, sep="\t")
    if pipeline == "t1-freesurfer":
        return [
            f"t1-freesurfer_atlas-{atlas_name}_ROI-{replace_sequence_chars(roi_name)}_thickness"
            for roi_name in atlas_df.label_name.values
        ]
    if pipeline in ("t1-volume", "pet-volume"):
        additional_desc = ""
        if "trc" in str(atlas_path):
            tracer = str(atlas_path).split("_trc-")[1].split("_")[0]
            additional_desc += f"_trc-{tracer}"
        if "pvc-rbv" in str(atlas_path):
            additional_desc += f"_pvc-rbv"
        return [
            f"{pipeline}_{group}_atlas-{atlas_name}{additional_desc}_ROI-{replace_sequence_chars(roi_name)}_intensity"
            for roi_name in atlas_df.label_name.values
        ]
    if pipeline == "dwi_dti":
        prefix = "dwi-dti_"
        metric = metric.rstrip("_statistics")
    elif pipeline == "t1-freesurfer_longitudinal":
        prefix = "t1-fs-long_"
    else:
        raise ValueError(f"Not supported pipeline {pipeline}.")
    return [
        prefix + metric + "_atlas-" + atlas_name + "_" + x
        for x in atlas_df.label_name.values
    ]

def _skip_atlas(
    atlas_path: Path,
    pipeline: str,
    pvc_restriction: Optional[bool] = None,
    tracers_selection: Optional[List[str]] = None,
) -> bool:
    """Helper function for _extract_metrics_from_pipeline.
    Returns whether the atlas provided through its path should be
    skipped for the provided pipeline.
    """
    if pipeline == "t1-freesurfer_longitudinal":
        return "-wm_" in str(atlas_path) or "-ba_" in str(atlas_path.stem)
    if pipeline in ("t1-volume", "pet-volume"):
        skip = []
        if pvc_restriction is not None:
            if pvc_restriction:
                skip.append("pvc-rbv" not in str(atlas_path))
            else:
                skip.append("pvc-rbv" in str(atlas_path))
        if tracers_selection:
            skip.append(
                all([tracer not in str(atlas_path) for tracer in tracers_selection])
            )
        return any(skip)
    return False

def _extract_metrics_from_pipeline(
    caps_dir: PathLike,
    df: pd.DataFrame,
    metrics: List[str],
    pipeline: str,
    atlas_selection: Optional[List[str]] = None,
    group_selection: Optional[List[str]] = None,
    pvc_restriction: Optional[bool] = None,
    tracers_selection: Optional[List[str]] = None,
) -> Tuple[pd.DataFrame, pd.DataFrame]:
    """Extract and merge the data of the provided pipeline into the
    merged dataframe already containing the BIDS information.

    Parameters
    ----------
    caps_dir : PathLike
        Path to the CAPS directory.

    df : pd.DataFrame
        DataFrame containing the BIDS information.
        New data will be merged into this dataframe.

    metrics : list of str
        List of metrics to extract from the pipeline's caps folder.

    pipeline : str
        Name of the pipeline for which to extract information.

    atlas_selection : list of str, optional
        Allows to choose the atlas to merge.
        If None all atlases are selected.

    group_selection : list of str, optional
        Allows to choose the DARTEL groups to merge.
        If None all groups are selected.

    pvc_restriction : bool, optional
        If True, only the atlases containing the label 'pvc-rbv' will be used.
        If False, the atlases containing the label won't be used.
        If None, all the atlases will be used.

    tracers_selection : list of str, optional
        Allows to choose the PET tracer to merge.
        If None, all tracers available will be used.

    Returns
    -------
    final_df : pd.DataFrame
        DataFrame containing the information from the bids dataset as well
        as the information extracted from the caps pipeline's folder.

    summary_df : pd.DataFrame
        Summary DataFrame generated by function `generate_summary`.
    """
    from clinica.utils.stream import cprint

    if df.index.names != ["participant_id", "session_id"]:
        try:
            df.set_index(
                ["participant_id", "session_id"], inplace=True, verify_integrity=True
            )
        except KeyError:
            raise KeyError("Fields `participant_id` and `session_id` are required.")

    if group_selection is None:
        try:
            group_selection = os.listdir(caps_dir / "groups")
        except FileNotFoundError:
            return df, None
    else:
        group_selection = [f"group-{group}" for group in group_selection]
    ignore_groups = group_selection == [""]

    subjects_dir = Path(caps_dir) / "subjects"
    pipeline_df = pd.DataFrame()
    for participant_id, session_id in df.index.values:
        ses_path = subjects_dir / participant_id / session_id
        mod_path = _get_mod_path(ses_path, pipeline)
        if mod_path is None:
            cprint(
                f"Could not find a longitudinal dataset for participant {participant_id} {session_id}",
                lvl="warning",
            )
            continue
        ses_df = pd.DataFrame(
            [[participant_id, session_id]], columns=["participant_id", "session_id"]
        )
        ses_df.set_index(["participant_id", "session_id"], inplace=True, drop=True)
        if mod_path.exists():
            for group in group_selection:
                group_path = mod_path / group
                if group_path.exists():
                    for metric in metrics:
                        atlas_paths = sorted(
                            group_path.glob(
                                f"{participant_id}_{session_id}_*{metric}.tsv"
                            )
                        )
                        if len(atlas_paths) == 0:
                            atlas_paths = sorted(
                                (group_path / "atlas_statistics").glob(
                                    f"{participant_id}_{session_id}_*{metric}.tsv"
                                )
                            )
                        for atlas_path in atlas_paths:
                            if not _skip_atlas(
                                atlas_path, pipeline, pvc_restriction, tracers_selection
                            ):
                                atlas_name = _get_atlas_name(atlas_path, pipeline)
                                if atlas_path.exists():
                                    if not (
                                        atlas_selection
                                        or (
                                            atlas_selection
                                            and atlas_name in atlas_selection
                                        )
                                    ):
                                        atlas_df = pd.read_csv(atlas_path, sep="\t")
                                        label_list = _get_label_list(
                                            atlas_path, metric, pipeline, group
                                        )
                                        key = (
                                            "label_value"
                                            if "freesurfer" in pipeline
                                            else "mean_scalar"
                                        )
                                        ses_df[label_list] = atlas_df[key].to_numpy()
        pipeline_df = pd.concat([pipeline_df, ses_df])
    summary_df = generate_summary(pipeline_df, pipeline, ignore_groups=ignore_groups)
    final_df = pd.concat([df, pipeline_df], axis=1)
    final_df.reset_index(inplace=True)
    return final_df, summary_df

dwi_dti_pipeline = functools.partial(
    _extract_metrics_from_pipeline,
    metrics=["FA_statistics", "MD_statistics", "RD_statistics", "AD_statistics"],
    pipeline="dwi_dti",
    group_selection=[""],
)

t1_freesurfer_longitudinal_pipeline = functools.partial(
    _extract_metrics_from_pipeline,
    metrics=["volume", "thickness", "segmentationVolumes"],
    pipeline="t1_freesurfer_longitudinal",
    group_selection=[""],
)

t1_freesurfer_pipeline = functools.partial(
    _extract_metrics_from_pipeline,
    metrics=["thickness", "segmentationVolumes"],
    pipeline="t1_freesurfer",
    group_selection=[""],
)

t1_volume_pipeline = functools.partial(
    _extract_metrics_from_pipeline,
    metrics=["statistics"],
    pipeline="t1-volume",
)

pet_volume_pipeline = functools.partial(
    _extract_metrics_from_pipeline,
    metrics=["statistics"],
    pipeline="pet-volume",
)

def generate_summary(
    pipeline_df: pd.DataFrame, pipeline_name: str, ignore_groups: bool = False
):

    columns = [
        "pipeline_name",
        "group_id",
        "atlas_id",
        "tracer",
        "pvc",
        "regions_number",
        "first_column_name",
        "last_column_name",
    ]
    summary_df = pd.DataFrame(columns=columns)

    if ignore_groups:
        groups = ["_"]
        atlases = list({column.split("_")[1] for column in pipeline_df.columns.values})
    else:
        groups = list({column.split("_")[1] for column in pipeline_df.columns.values})
        atlases = list({column.split("_")[2] for column in pipeline_df.columns.values})
    pvc_restrictions = list(
        {"pvc-rbv" in column for column in pipeline_df.columns.values}
    )
    tracers = list(
        {
            column.split("_trc-")[1].split("_")[0]
            for column in pipeline_df.columns.values
            if "trc" in column
        }
    )
    if len(tracers) == 0:
        tracers.append("_")
        pvc_restrictions = ["_"]
    groups.sort()
    atlases.sort()

    for group in groups:
        group_id = group.split("-")[-1]
        for atlas in atlases:
            atlas_id = atlas.split("-")[-1]
            for tracer in tracers:
                for pvc_restriction in pvc_restrictions:
                    if pvc_restriction and pvc_restriction != "_":
                        regions = [
                            column
                            for column in pipeline_df.columns.values
                            if group in column
                            and atlas in column
                            and "pvc-rbv" in column
                            and tracer in column
                        ]
                    else:
                        regions = [
                            column
                            for column in pipeline_df.columns.values
                            if group in column
                            and atlas in column
                            and "pvc-rbv" not in column
                            and tracer in column
                        ]

                    if len(regions) > 0:
                        row_df = pd.DataFrame(
                            [
                                [
                                    pipeline_name,
                                    group_id,
                                    atlas_id,
                                    tracer,
                                    pvc_restriction,
                                    len(regions),
                                    regions[0],
                                    regions[-1],
                                ]
                            ],
                            columns=columns,
                        )
                        summary_df = pd.concat([summary_df, row_df])

    summary_df = summary_df.replace("_", "n/a")
    return summary_df

class DatasetError(Exception):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return repr("Bad format for the sessions: " + self.name)


from itertools import combinations
from collections import deque

n, m = map(int, input().split())
graph = []
virus_index = []
target = 0

for i in range(n):
    graph.append(list(map(int, input().split())))
    for j in range(n):
        if graph[i][j] == 2:
            virus_index.append((i, j))
        if graph[i][j] == 0:
            target += 1 # 전부 감염시켜야 함

dx = [1, -1, 0, 0]
dy = [0, 0, 1, -1]

def bfs(combination: list) -> int:
    global answer
    queue = deque()
    visited = [([-1] * n) for _ in range(n)]
    infected = 0 # 감염시킨 횟수
    for i in combination:
        queue.append(i)
        visited[i[0]][i[1]] += 1
    while queue:
        x, y = queue.popleft()
        for i in range(4):
            cx = x + dx[i]
            cy = y + dy[i]
            if cx < 0 or cx >= n or cy < 0 or cy >= n:
                continue
            if graph[cx][cy] != 1 and visited[cx][cy] == -1:
                visited[cx][cy] = visited[x][y] + 1
                queue.append((cx, cy))
                if graph[cx][cy] == 0:
                    infected += 1
    max_duration = 0
    for i in range(n):
        for j in range(n):
            if graph[i][j] == 0:
                max_duration = max(max_duration, visited[i][j])
    if infected == target:
        answer = min(answer, max_duration)

# 바이러스를 활성시키는 경우의 수 담기
c_virus = list(combinations(virus_index, m))

# 모든 경우의 수 다 세어보기
result = []
answer = int(1e9)
for combination in c_virus:
    a = bfs(combination)

if answer == int(1e9):
    print(-1)
else:
    print(answer)