from copy import deepcopy

import numpy as np
import torch
import torch.autograd

from ppo_pytorch.algs.impala import IMPALA, AttrDict, copy_state_dict

class GuidedES(IMPALA):
    def __init__(self, *args,
                 grad_buffer_len=32,
                 steps_per_update=1,
                 es_lr=0.01,
                 es_std=0.01,
                 es_blend=0.0,
                 **kwargs):
        super().__init__(*args, **kwargs)
        assert self.num_actors == 1
        assert steps_per_update == 1
        self.grad_buffer_len = grad_buffer_len
        self.steps_per_update = steps_per_update
        self.es_lr = es_lr
        self.es_std = es_std
        self.es_blend = es_blend

        # (k, n) grad history matrix
        self._grad_buffer = None
        self._grad_buffer_index = 0
        # up to 2 * P weight sets
        self._weights_to_test = []
        # up to 2 * P rewards
        self._es_rewards = []
        # (P, n) noise matrix
        self._noise = None
        self._orig_model_weights = deepcopy(list(self._train_model.state_dict().values()))

        # self.value_loss_scale = 0

    def _step(self, rewards, dones, states) -> torch.Tensor:
        reward = rewards[0] if rewards is not None else 0
        done = dones[0] if dones is not None else False

        actions = super()._step(rewards, dones, states)

        if len(self._es_rewards) != 0:
            # ent = self._eval_model.heads.logits.pd.entropy(torch.tensor(self._steps_processor.data.logits[-1])).item() if len(self._steps_processor.data.logits) != 0 else 0
            self._es_rewards[-1] += reward * self.reward_scale #+ self.entropy_reward_scale * ent

        if self._grad_buffer is not None and done:
            if len(self._weights_to_test) == 0:
                if len(self._es_rewards) != 0:
                    self._es_update()
                    self._es_rewards.clear()
                self._generate_weights_to_test()
            self._es_rewards.append(0)
            self._set_next_model_weights()

        return actions

    def _es_update(self):
        fitness = torch.tensor(self._es_rewards).view(self.steps_per_update, 2)
        fitness = fitness[:, 0] - fitness[:, 1]
        # (n) vector
        grad = self.es_lr / (2 * self.es_std * self.steps_per_update) * (fitness @ self._noise)
        # print('es grad', grad.pow(2).mean().sqrt(), fitness)
        weights = list(self._train_model.state_dict().values())
        w_lens = [w.numel() for w in weights]
        for curw, g, origw in zip(weights, grad.split(w_lens, 0), self._orig_model_weights):
            origw += g.view_as(origw)
            curw.copy_(origw)
        copy_state_dict(self._train_model, self._eval_model)

        if self._do_log:
            self.logger.add_scalar('es grad rms', grad.pow(2).mean().sqrt(), self.frame_train)
            self.logger.add_scalar('es buffer rms', self._grad_buffer.pow(2).mean().sqrt(), self.frame_train)
            self.logger.add_scalar('es fitness rms', fitness.pow(2).mean().sqrt(), self.frame_train)

    def _create_data(self):
        data = super()._create_data()
        data.state_values.fill_(0)
        return data

    def _impala_update(self, data: AttrDict):
        cur_weights = deepcopy(list(self._train_model.state_dict().values()))
        for src, dst in zip(self._orig_model_weights, self._train_model.state_dict().values()):
            dst.copy_(src)
        super()._impala_update(data)
        grads = []
        for prev, new in zip(self._orig_model_weights, self._train_model.state_dict().values()):
            grads.append(new - prev)
            new.copy_(prev)
        for src, dst in zip(cur_weights, self._train_model.state_dict().values()):
            dst.copy_(src)
        self._update_grad_buffer(grads)

    def _generate_weights_to_test(self):
        assert len(self._weights_to_test) == 0

        weights = list(self._orig_model_weights)
        w_sizes = [w.numel() for w in weights]

        self._noise = self._get_noise()

        for noises in zip(*self._noise.split(w_sizes, dim=1)):
            self._weights_to_test.append([w + e.view_as(w) for (e, w) in zip(noises, weights)])
            self._weights_to_test.append([w - e.view_as(w) for (e, w) in zip(noises, weights)])

    def _get_noise(self):
        """Generates (P, n) noise matrix"""
        buf_len, grad_len = self._grad_buffer.shape
        full_space_noise = self._grad_buffer.new_zeros(self.steps_per_update, grad_len).normal_()
        subspace_noise = self._grad_buffer.new_zeros(self.steps_per_update, buf_len).normal_() @ self._grad_buffer
        subspace_noise /= subspace_noise.pow(2).mean().sqrt()
        full_space_noise *= self.es_std * self.es_blend
        subspace_noise *= self.es_std * (1 - self.es_blend)
        # print('noises full', full_space_noise.pow(2).mean().sqrt(), 'ss', subspace_noise.pow(2).mean().sqrt())
        noise = full_space_noise + subspace_noise
        return noise

    def _set_next_model_weights(self):
        weights = self._weights_to_test.pop(0)
        for src, dst in zip(weights, self._train_model.state_dict().values()):
            dst.copy_(src)
        copy_state_dict(self._train_model, self._eval_model)

    def _update_grad_buffer(self, new_grads):
        new_grads = torch.cat([g.view(-1) for g in new_grads], dim=0)
        # print('algs grad', new_grads.pow(2).mean().sqrt())
        if self._grad_buffer is None:
            self._grad_buffer = new_grads.new_zeros((self.grad_buffer_len, new_grads.numel()))
        self._grad_buffer[self._grad_buffer_index] = new_grads
        self._grad_buffer_index += 1
        if self._grad_buffer_index >= self.grad_buffer_len:
            self._grad_buffer_index = 0


import numpy as np
import torch
from torch.utils.data.sampler import Sampler
import copy

def ActualSequentialLoader(subsetRandomLoader, indices=None, transform=None, batch_size=None):
    indices = indices if indices is not None else subsetRandomLoader.sampler.indices
    train_sampler = ActualSequentialSampler(indices)
    dataset = copy.deepcopy(subsetRandomLoader.dataset)
    if transform is not None:
        dataset.transform = transform

    batch_size = batch_size if batch_size is not None else subsetRandomLoader.batch_size
    actualSequentialLoader = torch.utils.data.DataLoader(dataset, sampler=train_sampler,
                                                         num_workers=subsetRandomLoader.num_workers,
                                                         batch_size=batch_size, drop_last=False)
    return actualSequentialLoader

class ActualSequentialSampler(Sampler):
    r"""Samples elements sequentially, always in the same order.

    Arguments:
        data_source (Dataset): dataset to sample from
    """

    def __init__(self, data_source):
        self.data_source = data_source

    def __iter__(self):
        return iter(self.data_source)

    def __len__(self):
        return len(self.data_source)

def row_norms(X, squared=False):
    """Row-wise (squared) Euclidean norm of X.
    Equivalent to np.sqrt((X * X).sum(axis=1)), but also supports sparse
    matrices and does not create an X.shape-sized temporary.
    Performs no input validation.
    Parameters
    ----------
    X : array_like
        The input array
    squared : bool, optional (default = False)
        If True, return squared norms.
    Returns
    -------
    array_like
        The row-wise (squared) Euclidean norm of X.
    """
    norms = np.einsum('ij,ij->i', X, X)

    if not squared:
        np.sqrt(norms, norms)
    return norms

def outer_product_opt(c1, d1, c2, d2):
    """Computes euclidean distance between a1xb1 and a2xb2 without evaluating / storing cross products
    """
    B1, B2 = c1.shape[0], c2.shape[0]
    t1 = np.matmul(np.matmul(c1[:, None, :], c1[:, None, :].swapaxes(2, 1)), np.matmul(d1[:, None, :], d1[:, None, :].swapaxes(2, 1)))
    t2 = np.matmul(np.matmul(c2[:, None, :], c2[:, None, :].swapaxes(2, 1)), np.matmul(d2[:, None, :], d2[:, None, :].swapaxes(2, 1)))
    t3 = np.matmul(c1, c2.T) * np.matmul(d1, d2.T)
    t1 = t1.reshape(B1, 1).repeat(B2, axis=1)
    t2 = t2.reshape(1, B2).repeat(B1, axis=0)
    return t1 + t2 - 2*t3

def kmeans_plus_plus_opt(X1, X2, n_clusters, init=[0], random_state=np.random.RandomState(1234), n_local_trials=None):
    """Init n_clusters seeds according to k-means++ (adapted from scikit-learn source code)
    Parameters
    ----------
    X1, X2 : array or sparse matrix
        The data to pick seeds for. To avoid memory copy, the input data
        should be double precision (dtype=np.float64).
    n_clusters : integer
        The number of seeds to choose
    init : list
        List of points already picked
    random_state : int, RandomState instance
        The generator used to initialize the centers. Use an int to make the
        randomness deterministic.
        See :term:`Glossary <random_state>`.
    n_local_trials : integer, optional
        The number of seeding trials for each center (except the first),
        of which the one reducing inertia the most is greedily chosen.
        Set to None to make the number of trials depend logarithmically
        on the number of seeds (2+log(k)); this is the default.
    Notes
    -----
    Selects initial cluster centers for k-mean clustering in a smart way
    to speed up convergence. see: Arthur, D. and Vassilvitskii, S.
    "k-means++: the advantages of careful seeding". ACM-SIAM symposium
    on Discrete algorithms. 2007
    Version ported from http://www.stanford.edu/~darthur/kMeansppTest.zip,
    which is the implementation used in the aforementioned paper.
    """

    n_samples, n_feat1 = X1.shape
    _, n_feat2 = X2.shape
    # x_squared_norms = row_norms(X, squared=True)
    centers1 = np.empty((n_clusters+len(init)-1, n_feat1), dtype=X1.dtype)
    centers2 = np.empty((n_clusters+len(init)-1, n_feat2), dtype=X1.dtype)

    idxs = np.empty((n_clusters+len(init)-1,), dtype=np.long)

    # Set the number of local seeding trials if none is given
    if n_local_trials is None:
        # This is what Arthur/Vassilvitskii tried, but did not report
        # specific results for other than mentioning in the conclusion
        # that it helped.
        n_local_trials = 2 + int(np.log(n_clusters))

    # Pick first center randomly
    center_id = init

    centers1[:len(init)] = X1[center_id]
    centers2[:len(init)] = X2[center_id]
    idxs[:len(init)] = center_id

    # Initialize list of closest distances and calculate current potential
    distance_to_candidates = outer_product_opt(centers1[:len(init)], centers2[:len(init)], X1, X2).reshape(len(init), -1)

    candidates_pot = distance_to_candidates.sum(axis=1)
    best_candidate = np.argmin(candidates_pot)
    current_pot = candidates_pot[best_candidate]
    closest_dist_sq = distance_to_candidates[best_candidate]

    # Pick the remaining n_clusters-1 points
    for c in range(len(init), len(init)+n_clusters-1):
        # Choose center candidates by sampling with probability proportional
        # to the squared distance to the closest existing center
        rand_vals = random_state.random_sample(n_local_trials) * current_pot
        candidate_ids = np.searchsorted(closest_dist_sq.cumsum(),
                                        rand_vals)
        # XXX: numerical imprecision can result in a candidate_id out of range
        np.clip(candidate_ids, None, closest_dist_sq.size - 1,
                out=candidate_ids)

        # Compute distances to center candidates
        distance_to_candidates = outer_product_opt(X1[candidate_ids], X2[candidate_ids], X1, X2).reshape(len(candidate_ids), -1)

        # update closest distances squared and potential for each candidate
        np.minimum(closest_dist_sq, distance_to_candidates,
                   out=distance_to_candidates)
        candidates_pot = distance_to_candidates.sum(axis=1)

        # Decide which candidate is the best
        best_candidate = np.argmin(candidates_pot)
        current_pot = candidates_pot[best_candidate]
        closest_dist_sq = distance_to_candidates[best_candidate]
        best_candidate = candidate_ids[best_candidate]

        idxs[c] = best_candidate

    return None, idxs[len(init)-1:]

def get_embedding(model, loader, device, num_classes, cfg, with_emb=False, emb_dim=512):
    model.eval()
    embedding = torch.zeros([len(loader.sampler), num_classes])
    embedding_pen = torch.zeros([len(loader.sampler), emb_dim])
    labels = torch.zeros(len(loader.sampler))
    preds = torch.zeros(len(loader.sampler))
    batch_sz = cfg.DATALOADER.BATCH_SIZE
    with torch.no_grad():
        for batch_idx, (data, target, _) in enumerate(loader):
            data, target = data.to(device), target.to(device)
            if with_emb:
                e1, e2 = model(data, with_emb=True)
                embedding_pen[batch_idx*batch_sz:batch_idx*batch_sz + min(batch_sz, e2.shape[0]), :] = e2.cpu()
            else:
                e1 = model(data, with_emb=False)

            embedding[batch_idx*batch_sz:batch_idx*batch_sz + min(batch_sz, e1.shape[0]), :] = e1.cpu()
            labels[batch_idx*batch_sz:batch_idx*batch_sz + min(batch_sz, e1.shape[0])] = target
            preds[batch_idx*batch_sz:batch_idx*batch_sz + min(batch_sz, e1.shape[0])] = e1.argmax(dim=1, keepdim=True).squeeze()

    return embedding, labels, preds, embedding_pen


root_name = 'tnseq_adaptation'
import os, sys  ## make sure path to root of project directory
cwd = os.getcwd(); directory_root = cwd[:cwd.index(root_name)+len(root_name)]
sys.path.insert(0, directory_root)
from methods.config import * #includes matplotlib, matplotlib.pyplot, numpy, pickle
import methods.ax_methods as ax_methods
import methods.shared as shared

def find_large_freqs(bac, mouse, day, cutoff=1e-2, min_init_freq=0):
    row_ids = shared.bac_row_ids[bac]
    notWu = shared.bac_nonwu_indices[bac]
    read_array = shared.bac_read_arrays[bac][:, notWu]
    day0_freqs = read_array[0] / read_array[0].sum()

    if min_init_freq:
        day0_valid = day0_freqs > min_init_freq

    mouse_reads = read_array[ row_ids[(mouse, day)] ]
    mouse_freqs = mouse_reads / mouse_reads.sum()
    large_indices = np.where((mouse_freqs >= cutoff)*(mouse_freqs > day0_freqs)*day0_valid)[0]

    large_freqs = mouse_freqs[large_indices]

    return large_freqs, large_indices

def find_largest_freqs_across_mice(bac, mice, day, indices):
    row_ids = shared.bac_row_ids[bac]
    notWu = shared.bac_nonwu_indices[bac]
    read_array = shared.bac_read_arrays[bac][:, notWu]

    mice_reads = read_array[ [ row_ids[(mouse, day)] for mouse in mice ] ]
    mice_freqs = np.einsum('ij, i->ij', mice_reads, mice_reads.sum(axis=1)**-1.)[:, indices]
    largest_freqs_across_mice = np.max( mice_freqs, axis=0 )
    large_mouse_indices = np.argmax( mice_freqs, axis=0)

    return largest_freqs_across_mice, large_mouse_indices

def plot_large_barcode_fc_vs_largest(ax, bac, focal_mouse, comparison_mice, day,
                                     cutoff=1e-2, min_init_freq=0,  plot=True):
    row_ids = shared.bac_row_ids[bac]
    notWu = shared.bac_nonwu_indices[bac]
    read_array = shared.bac_read_arrays[bac][:, notWu]

    # find large lineages in one mouse
    large_freqs, large_indices = find_large_freqs(bac, focal_mouse, day,
                                                  cutoff=cutoff, min_init_freq=min_init_freq)

    # what are these lineages doing in other mice
    largest_freqs_comparison_mice, large_mouse_indices = find_largest_freqs_across_mice(bac, comparison_mice, day, large_indices)
    day0_freqs = read_array[0] / read_array[0].sum()

    with np.errstate(divide='ignore'):
        greater_one = large_freqs/largest_freqs_comparison_mice > 1
    if plot:
        ax.scatter( (large_freqs/day0_freqs)[greater_one], (large_freqs/largest_freqs_comparison_mice)[greater_one], color='green', s=3)
        ax.set_yscale('log')
        ax.set_xscale('log')

    with np.errstate(divide='ignore'):
        return (large_freqs/largest_freqs_comparison_mice)[greater_one], large_indices

def find_ratio_first_second_largest_across_mice(bac, mice, day):
    row_ids = shared.bac_row_ids[bac]
    notWu = shared.bac_nonwu_indices[bac]
    read_array = shared.bac_read_arrays[bac][:, notWu]

    mice_reads = read_array[ [row_ids[(mouse, day)] for mouse in mice] ]
    freqs = np.einsum('ij, i->ij', mice_reads, mice_reads.sum(axis=1)**-1.)

    greater_zero = ((freqs > 0).sum(axis=0) > 1)
    freqs = np.ma.masked_array(freqs, np.tile(~greater_zero, (freqs.shape[0], 1)))

    ratios = []
    for i in range(freqs.shape[-1]):
        if freqs.mask[0, i] == True:
            ratios.append(np.nan)
            continue
        lineage_freqs = freqs[:, i][np.argsort(freqs[:, i])]
        ratios.append( lineage_freqs[-1]/lineage_freqs[-2] )
    return ratios

def make_sfig1(fig, outer):
    diet = 'HF'
    mice = diet_mice_map[diet]
    min_init_freq = 1e-6
    cutoff = 1e-3
    day = 16

    if 'outer ax format':
        outer_ax = fig.add_subplot(outer[:, 0])
        outer_ax.set_xlabel('Lineage max fold change', fontsize=10, labelpad=15)
        outer_ax.set_ylabel(r'Lineage divergence, $\Delta_\ell$', labelpad=18, fontsize=10)
        outer_ax.set_xticks([])
        outer_ax.set_yticks([])
        ax_methods.turn_off_ax(outer_ax)

        survival_ax = fig.add_subplot(outer[:, 1])
        survival_ax.set_xlabel(r'Frac. lineages > $\Delta_\ell$', fontsize=10, labelpad=7.5)
        ax_methods.turn_off_ax(survival_ax)

    bac_large_divergence_indices = {bac: [] for bac in shared.BACTERIA}

    for b, bac in enumerate(shared.BACTERIA):
        row_ids = shared.bac_row_ids[bac]
        notWu = shared.bac_nonwu_indices[bac]
        read_array = shared.bac_read_arrays[bac][:, notWu]
        day0_freqs = read_array[0] / read_array[0].sum()

        ax = fig.add_subplot(outer[2 * b, 0])
        ax.axhline(1, color='black', linestyle='dashed', zorder=1)

        all_focal_ratios = []
        all_large_indices = set()
        for f, focal_mouse in enumerate(mice):
            comparison_mice = mice[:f] + mice[f + 1:]
            ratios, large_indices = plot_large_barcode_fc_vs_largest(ax, bac, focal_mouse, comparison_mice, day,
                                                                     cutoff=cutoff, min_init_freq=min_init_freq,
                                                                     plot=False)

            all_focal_ratios.extend(ratios)
            all_large_indices.update(large_indices)

        reads10 = read_array[[row_ids[(mouse, 10)] for mouse in mice]]
        freqs10 = np.einsum('ij, i->ij', reads10, reads10.sum(axis=1) ** -1.)
        reads16 = read_array[[row_ids[(mouse, 16)] for mouse in mice]]
        freqs16 = np.einsum('ij, i->ij', reads16, reads16.sum(axis=1) ** -1.)

        large = ((freqs16 > 1e-3)).sum(axis=0) > 0
        # large = ((freqs10 > 1e-3) + (freqs16 > 1e-3)).sum(axis=0) > 0
        all_large_indices = np.arange(large.shape[-1])[large]

        ratios16 = find_ratio_first_second_largest_across_mice(bac, mice, 16)
        ratios10 = find_ratio_first_second_largest_across_mice(bac, mice, 10)
        focal_ratios16 = np.array(ratios16)[list(all_large_indices)]
        focal_ratios10 = np.array(ratios10)[list(all_large_indices)]

        focal_freqs0 = read_array[0][list(all_large_indices)] / read_array[0].sum()

        max_focal_freqs10 = np.max(freqs10[:, list(all_large_indices)], axis=0)
        max_focal_freqs16 = np.max(freqs16[:, list(all_large_indices)], axis=0)

        # ax.scatter( max_focal_freqs10/focal_freqs0, max_focal_freqs16/max_focal_freqs10)
        bac_large_divergence_indices[bac] = all_large_indices[focal_ratios16 > 10]
        ax.set_xscale('log')
        ax.set_yscale('log')
        ax.set_xlim(1, 9 * 10 ** 5)
        ax.set_xticks([1, 10, 10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5])
        ax.set_yticks([1, 10, 10 ** 2, 10 ** 3])
        ax.tick_params(axis='both', labelsize=8)

        with np.errstate(divide='ignore'):
            max16_init_ratio = max_focal_freqs16 / focal_freqs0
            max16_init_ratio[ np.isinf(max16_init_ratio) ] = 100

        ax.scatter(max16_init_ratio, focal_ratios16, s=3, color='green')
        ax.text(0.98, 0.8, BAC_FORMAL_NAMES[bac],
                transform=ax.transAxes, fontsize=10, horizontalalignment='right')
        ax.set_ylim(1, 3 * 10 ** 3)
        ylim = ax.get_ylim()
        ax.axhspan(10, ax.get_ylim()[1], color='blue', alpha=0.2, zorder=0, linewidth=0)
        if bac == 'BWH2':
            ax.text(0.99, 0.32, '$putative~\\it{de~novo}$', color='blue',
                    transform=ax.transAxes, horizontalalignment='right', fontsize=8)
            ax.text(0.99, 0.15, '$preexisting$', color='grey',
                    transform=ax.transAxes, horizontalalignment='right', fontsize=8)

        ax = fig.add_subplot(outer[2 * b, 1])
        ax.set_yscale('log')
        ax.set_yticks([1, 10, 10 ** 2, 10 ** 3])
        ax.set_xticks([0, 0.5, 1])
        ax.set_xticklabels(['0', '0.5', '1'])
        ax.tick_params(axis='both', labelsize=8)
        ax.hist(ratios16, bins=np.logspace(0, 4, 41), density=True, color='grey', orientation='horizontal',
                cumulative=-1, histtype='step')
        ax.hist(focal_ratios16, bins=np.logspace(0, 4, 41), density=True, color='green', orientation='horizontal',
                cumulative=-1, histtype='step')
        ax.axhspan(10, ax.get_ylim()[1], color='blue', alpha=0.2, zorder=0, linewidth=0)
        ax.set_ylim(ylim)

    return bac_large_divergence_indices

def make_sfig2(fig, outer, bac_large_divergence_indices):
    ax = fig.add_subplot(outer[:, :])
    ax_methods.turn_off_ax(ax)
    ax.set_xlabel('Days', fontsize=10, labelpad=8)
    ax.set_ylabel('Lineage frequency', fontsize=10, labelpad=10)

    mice = [6, 7, 8, 9, 10]

    rnd = np.random.default_rng(seed=16)
    for b, bac in enumerate(['BWH2', 'Bovatus', 'BtVPI', 'Bt7330']):
        row_ids = shared.bac_row_ids[bac]
        notWu = shared.bac_nonwu_indices[bac]
        read_array = shared.bac_read_arrays[bac][:, notWu]
        day0_freqs = read_array[0] / read_array[0].sum()

        example_indices = rnd.choice(bac_large_divergence_indices[bac], num_examples, replace=False)
        # example_indices = [1,2,3,4]

        mice_freqs = np.zeros((4, len(mice), num_examples))
        mice_depths = np.zeros((4, len(mice), num_examples))
        mice_freqs[0] = np.tile(day0_freqs[example_indices], (len(mice), 1))
        mice_depths[0] = np.tile(read_array[0].sum(), (len(mice), 1))
        for d, day in enumerate(days):
            reads = read_array[[row_ids[(mouse, day)] for mouse in mice]]
            freqs = np.einsum('ij, i->ij', reads, reads.sum(axis=1) ** -1.)[:, example_indices]
            mice_freqs[d + 1] = freqs
            mice_depths[d + 1] = reads.sum(axis=1)

        for j in range(num_examples):
            ax = fig.add_subplot(outer[j, b])

            example_trajectories = mice_freqs[:, :, j]
            min_freq = np.min(example_trajectories[example_trajectories > 0])

            # for m in range(len(mice)):
            #     mouse_trajectory = example_trajectories[:, m]
            #     if mouse_trajectory[1] == 0:
            #         if mouse_trajectory[2:].sum() == 0:
            #             mouse_trajectory[1:] = 10**-7
            #         else:
            #             mouse_trajectory[1] = 10**-7
            #     elif mouse_trajectory[2] == 0:
            #         if mouse_trajectory[3] == 0:
            #             mouse_trajectory[2:] = 10**-7
            #         else:

            where_zero = np.where(example_trajectories == 0)
            for (t, m) in zip(*where_zero):
                if t < 3 and example_trajectories[t - 1, m] < 1 / mice_depths[t, m, 0] and example_trajectories[
                    t + 1, m] > 0:
                    example_trajectories[t, m] = example_trajectories[t - 1, m]
                elif t == 3 and example_trajectories[t - 1, m] < 1 / mice_depths[t, m, 0]:
                    example_trajectories[t, m] = example_trajectories[t - 1, m]
                else:
                    example_trajectories[t, m] = 3 * 10 ** -7

            # where_zero = example_trajectories == 0
            # example_trajectories[ where_zero ] = 3*10**-7

            ax.plot([0] + days, example_trajectories)

            ax.set_yscale('log')
            ax.set_ylim(2 * 10 ** -6, 10 ** -1)
            ax.set_xlim(0, 1)
            ax.set_xticks([0, 4, 10, 16])
            # yticks = [10**-6, 10**-5, 10**-4, 10**-3, 10**-2, 10**-1]
            # ytick_labels = ['0', r'$10^{-5}$', r'$10^{-4}$', r'$10^{-3}$', r'$10^{-2}$', r'$10^{-1}$']
            yticks = [10 ** -5, 10 ** -4, 10 ** -3, 10 ** -2, 10 ** -1, 1]
            ytick_labels = [r'$10^{-5}$', r'$10^{-4}$', r'$10^{-3}$', r'$10^{-2}$', r'$10^{-1}$', 1]

            ax.set_yticks(yticks, labels=ytick_labels)

            if b != 0:
                ax.set_yticklabels([])

            if j == 0:
                ax.set_title(f'{BAC_FORMAL_NAMES[bac]}')

if __name__ == '__main__':
    fig = plt.figure(figsize=(4, 6))
    outer = mpl.gridspec.GridSpec(nrows=7, ncols=2, height_ratios=[1, 0.1, 1, 0.1, 1, 0.1, 1], width_ratios=[1, 0.5],
                                  figure=fig)

    bac_large_divergence_indices = make_sfig1(fig, outer)
    fig.savefig(f'{plot_dir}/sfig13_de_novo_distribution.pdf')

    fig = plt.figure(figsize=(5, 6))
    num_examples = 5
    outer = mpl.gridspec.GridSpec(nrows=num_examples, ncols=4)

    make_sfig2(fig, outer, bac_large_divergence_indices)
    fig.savefig(f'{plot_dir}/sfig16_de_novo_examples.pdf')


from latinwordnet import LatinWordNet
from latinwordnet.latinwordnet import Lemmas, Synsets

class Synset:
	def __init__(self, synset_dict: dict):
		self.gloss: str = synset_dict["gloss"]
		self.offset: str = synset_dict["offset"]
		self.pos: str = synset_dict["pos"]

lwn: LatinWordNet = LatinWordNet()
lemmas: Lemmas = lwn.lemmas(lemma="suprascriptio", pos="n")  # uerbum sermo scriba publicanus ethnicus
synset_results: list[dict] = lemmas.synsets
target_synset: Synset = Synset(synset_results[0]["synsets"]["literal"][0])
print(f"Synset: {target_synset.gloss}")
print(f"{[x['lemma'] for x in lwn.synsets(**target_synset.__dict__).lemmas[0]['lemmas']['literal']]}")
relations: dict[str, list] = lwn.synsets(**target_synset.__dict__).relations
hypernym_synsets: list[dict] = relations["@"]

def process_hypernyms(hypernym_synsets: list[dict]):
	""" Cycles through all hypernym synsets and lists the corresponding lemmata for each one. """
	for hs in hypernym_synsets:
		synset: Synsets = lwn.synsets(**Synset(hs).__dict__)
		print(f"Synset: {synset.gloss}")
		lemma: dict = synset.lemmas[0]["lemmas"]
		lemmata: list[str] = [x['lemma'] for x in lemma['literal']]
		print(f"{lemmata}")
		new_hypernym_synsets: list[dict] = synset.relations["@"]
		if new_hypernym_synsets:
			process_hypernyms(new_hypernym_synsets)

process_hypernyms(hypernym_synsets)
a = 0


import math
n = int(input())
mxd = len(str(n))

a = 0
for i in range(1, int(math.sqrt(n))+1):
  if n%i == 0:
    ad = len(str(i))
    bd = len(str(n//i))
    mx = max(ad, bd)
    if mx < mxd:
      mxd = mx
print(mxd)


""" Build Cleaned CSV files -- Logic to:
    - create cleaned season inventories based on multiple data sources
        * LILA/Dryad data (S1-S6)
        * MSI SQL DB data
        * MSI SX_cleaned.csv files
    - specifically:
        * ensure valid/invalid flags are consistent
        * ensure timestamps are consistent and as correct as possible
        * generally, ensure consistency with LILA/Dryad
    - general procedure:
        1. Base files are SX_cleaned.csv for S9-S10, df_db for S1-S8
        2. Ensure everything S1-S6 in LILA dataset is contained
        (3. Add additional captures from S1-S6 if 'include'
            status but not in LILA)
        (4. verify with DB export valid flags)
        (5. verify timestamps LILA and DB export / SX_cleaned)
    - required files:
        1. LILA export (SnapshotSerengeti.json)
        2. DB export (timestamps_db.csv)
        3. SX_cleaned.csv files
"""
import os
import pandas as pd

from utils.utils import sort_df, sort_df_by_capture_id
from zooniverse_exports.legacy.legacy_utils import (
    read_lila, read_db, read_cleaned, print_stats, create_capture_id,
    create_date, create_time, _get_datetime_obj
)

#################################
# Parameters
#################################

root_data_path = '/home/packerc/shared/season_captures/SER/legacy_S1_S10_data/'

path_cleaned = root_data_path
lila_path = os.path.join(root_data_path, 'lila.json')
path_images_db = os.path.join(root_data_path, 'df_db_S1_S8.csv')
path_output = os.path.join(root_data_path, 'cleaned/')

col_mapper = {
    'ZoonID': 'subject_id',
    'idCaptureEvent': 'CaptureEventId',
    'Zooniverse_ID': 'subject_id',
    'ZooniverseIdentifier': 'subject_id',
    'Roll': 'roll_db_id',
    'Season': 'season',
    'Site': 'site_db_id',
    'Capture': 'capture_db_id',
    'GridCell': 'site',
    'RollNumber': 'roll',
    'CaptureEventNum': 'capture'
    }

#################################
# Read Data
#################################

# Read Base Data -- LILA / DB Exports / CLEANED CSV Files
df_lila = read_lila(lila_path)
df_db = read_db(path_images_db, col_mapper)
df_cleaned = read_cleaned(path_cleaned)

#################################
# Look at Stats
#################################

# investigate data
df_db.columns
print_stats(df_db['season'].value_counts())
print_stats(df_db['site'].value_counts())
print_stats(df_db['roll'].value_counts())
print_stats(df_db['SequenceNum'].value_counts())
print_stats(df_db['ZooniverseStatus'].value_counts())
print_stats(df_db['idTimestampStatuses'].value_counts())
print_stats(df_db['StatusDescription'].value_counts())
print_stats(df_db['Invalid'].value_counts())
df_db.groupby(['Invalid', 'season']).size()
df_cleaned.groupby(['include', 'season']).size()
df_cleaned.groupby(['include', 'invalid']).size()
df_db.groupby(['idTimestampStatuses', 'StatusDescription']).size()

# idTimestampStatuses  StatusDescription
# 0                    OK                                        5011159
# 1                    Not recoverable                             25836
# 2                    Fix is hard                                 36392
# 3                    Fix is hard but timestamp likely close      14555

# -> include only for invalid=0

#################################
# Select relevant columns
#################################

df_lila = df_lila[[
    'image_path', 'subject_id', 'season',
    'site', 'datetime', 'species']]

df_db = df_db[[
    'subject_id', 'image_path', 'season',
    'site', 'roll', 'capture', 'SequenceNum',
    'TimestampJPG', 'TimestampFile', 'TimestampAccepted',
    'Invalid', 'idTimestampStatuses', 'StatusDescription']]

df_cleaned = df_cleaned[[
    'newtime', 'oldtime', 'PathFilename', 'roll', 'season', 'site',
    'capture', 'image', 'include', 'invalid']]

# Export to Disk
df_lila.to_csv(os.path.join(root_data_path, 'df_lila.csv'), index=False)
df_db.to_csv(os.path.join(root_data_path, 'df_db.csv'), index=False)
df_cleaned.to_csv(os.path.join(root_data_path, 'df_cleaned.csv'), index=False)

# Read from disk
# df_lila = pd.read_csv(os.path.join(root_data_path, 'df_lila.csv'), index_col=False, dtype=str)
# df_db = pd.read_csv(os.path.join(root_data_path, 'df_db.csv'), index_col=False, dtype=str)
# df_cleaned = pd.read_csv(os.path.join(root_data_path, 'df_cleaned.csv'), index_col=False, dtype=str)
# df_lila.fillna('', inplace=True)
# df_db.fillna('', inplace=True)
# df_cleaned.fillna('', inplace=True)

# create flags
df_cleaned['in_clean'] = '1'
df_db['in_db'] = '1'
df_lila['in_lila'] = '1'

# remove specific images that don't actually exist
def remove_non_existing(df):
    """ Remove images that dont exist """
    i_subset = ~df['image_path'].isin([
        'S1/L10/L10_R1/S1_L10_R1_PICT0004 (2).JPG',
        'S1/L10/L10_R1/S1_L10_R1_PICT0023 (2).JPG',
        'S1/L10/L10_R1/S1_L10_R1_PICT0024 (2).JPG',
        'S1/L10/L10_R1/S1_L10_R1_PICT0025 (2).JPG',
        'S7/G09/G09_R2/S7_G09_R2_P1060627.JPG'])
    return df.loc[i_subset]

df_lila = remove_non_existing(df_lila)
df_db = remove_non_existing(df_db)

#################################
# Merge Data
#################################

# build full data frame
df_merged = pd.merge(
    left=df_db, how='outer',
    right=df_cleaned,
    left_on=['image_path', 'season', 'site', 'roll'],
    right_on=['PathFilename', 'season', 'site', 'roll'],
    suffixes=('_db', '_cleaned'))
df_merged['image_path'] = df_merged['image_path'].fillna(df_merged['PathFilename'])

# create 'capture' column, take 'capture_db', substitute with 'capture_cleaned' if empty
df_merged['capture'] = df_merged['capture_db']
i_replace = (df_merged['capture_db'] == '') | (df_merged['capture_db'].isna())
df_merged['capture'].loc[i_replace] = df_merged['capture_cleaned'].loc[i_replace]

# merge lila data
df_merged2 = pd.merge(
    left=df_merged, how='outer',
    right=df_lila,
    left_on=['image_path', 'season', 'site'],
    right_on=['image_path', 'season', 'site'],
    suffixes=('', '_lila'))
df_merged2.fillna('', inplace=True)

#################################
# Clean Invalid Image Names
#################################

# remove images with either '(' or ')' in image name
i_special_char = df_merged2['image_path'].apply(lambda x: (')' in x) | ('(' in x))

bad_names = list(df_merged2['image_path'].loc[i_special_char])
good_names = [x.replace(' (2)', '') for x in bad_names]
df_merged2['image_path'].loc[i_special_char] = good_names

# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# RENAMING DONE!
# for old, new in zip(bad_names, good_names):
#     root_path = '/home/packerc/shared/albums/SER/'
#     old_path = os.path.join(root_path, old)
#     new_path = os.path.join(root_path, new)
#     try:
#         os.rename(old_path, new_path)
#         print("mv {} to {}".format(old_path, new_path))
#     except:
#         raise

#################################
# Some checks
#################################

df_db.groupby(['season', 'Invalid']).size().to_frame('count').reset_index()
df_merged2.groupby(['season', 'Invalid']).size().to_frame('count').reset_index()
df_merged2.groupby(['season', 'in_clean', 'in_db', 'in_lila']).size().to_frame('count').reset_index()
df_merged2.iloc[0]

# some random checks / comments

# # check investigate discrepancies
# img = 'S1/E02/E02_R5/S1_E02_R5_PICT0148.JPG'
# img = 'S4/B12/B12_R2/S4_B12_R2_IMAG0100.JPG'
# img = 'S5/G07/G07_R4/S5_G07_R4_IMAG0219.JPG'
# df_merged2[df_merged2['image_path'] == img]

# # complete roll missing
# # S4/B12/B12_R2/S4_B12_R2_
# # ASG000euyt, https://static.zooniverse.org/www.snapshotserengeti.org/subjects/standard/50dcd6e6a2fc8e37891057fa_0.jpg
# # SER_S4#B12#2#1,S4,B12,R2,1,
# # 4,B12,2,1
# # is in S4_cleaned.csv with correct time...
# # has invalid=3 flag, therefore, makes sense to be missing in LILA
#
#
# missing_in_cleaned.shape
# missing_in_cleaned.iloc[1]
#
# # 164 captures from S5 missing, S4/S6 are good
# # 464 images missing from S5
# missing_in_cleaned['season_y'].value_counts()
#
# # ASG000pf4u
# # S5/G07/G07_R4/S5_G07_R4_IMAG0219.JPG
# # entire roll S5/G07/G07_R4 missing in S5_cleaned.csv
# # however, is in LILA and DB

# df_merged2[df_merged2['image_rank_in_capture'] == '6']
# df_merged2[df_merged2['subject_id'] == 'ASG000twd2']

#################################
# Handle Different Seasons
#################################

# Build DFs for each season
col_mapper_final = {
    'SequenceNum': 'image_rank_in_capture',
    'image': 'image_rank_in_capture',
    'TimestampJPG': 'datetime_exif',
    'oldtime': 'datetime_exif',
    'TimestampFile': 'datetime_file_creation',
    'TimestampAccepted': 'datetime',
    'newtime': 'datetime',
    'Invalid': 'invalid',
    'image_path': 'image_path_rel',
    'datetime': 'datetime_lila'
    }

season_dfs = list()
for i in range(0, 10):
    season_num = i+1
    df_season = df_merged2[df_merged2['season'] == 'S{}'.format(season_num)].copy()
    # LILA and DB
    if season_num <= 6:
        df_season = df_season[[
            'image_path', 'season', 'site', 'roll', 'capture',
            'SequenceNum', 'TimestampJPG', 'TimestampFile',
            'TimestampAccepted', 'Invalid', 'datetime']]
    # DB
    elif season_num <= 8:
        df_season = df_season[[
            'image_path', 'season', 'site', 'roll', 'capture',
            'SequenceNum', 'TimestampJPG', 'TimestampFile',
            'TimestampAccepted', 'Invalid']]
    # SEASON
    else:
        df_season = df_season[[
            'image_path', 'season', 'site', 'roll', 'capture',
            'image', 'oldtime', 'newtime',
            'include', 'invalid']]
    df_season.rename(columns=col_mapper_final, inplace=True)
    season_dfs.append(df_season)

df_final = pd.concat(season_dfs)
df_final.fillna('', inplace=True)

# remove any images for S1-S8 that are not in either the db export or LILA
# it turned out that some of these are faulty captures, for security reasons
# we just remove all that are not in the DB and/or LILA
i_only_in_cleaned_s1_s8 = \
    (df_final['season'].isin(['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8'])) & \
    (df_final['invalid'] == '')

before = df_final.shape[0]
df_final = df_final.loc[~i_only_in_cleaned_s1_s8]
print("Removed {} only in cleaned for S1-S8".format(before - df_final.shape[0]))

# final checks
print_stats(df_final['season'].value_counts())
df_final.groupby(['season', 'include', 'invalid']).size().to_frame('count').reset_index()
df_final.groupby(['season', 'invalid']).size().to_frame('count').reset_index()

# 'SER_S5#G07#3#44', 'SER_S5#G07#3#129'
should_be_excluded = df_final[
    (df_final['season'] == 'S5') &
    (df_final['site'] == 'G07') &
    (df_final['roll'] == '3') &
    (df_final['capture'].isin(['44', '129']))]
assert len(should_be_excluded) == 0

##########################
# check with LILA data
##########################

tt = df_final[(df_final['datetime_lila'] != df_final['datetime']) & (df_final['datetime_lila'] != '') & (df_final['datetime'] != '') & (df_final['image_rank_in_capture'] == '1')]
tt.shape
#(62, 12) are different

# check LILA invalid flags
tt = pd.merge(
    left=df_final,
    right=df_lila,
    left_on='image_path_rel',
    right_on='image_path',
    how='left',
    suffixes=('', '_lila'))
tt = tt.loc[(tt['season'].isin(['S1', 'S2', 'S3', 'S4', 'S5', 'S6']))]
tt.groupby(['season_lila', 'include', 'invalid']).size().to_frame('count').reset_index()
tt.groupby(['season', 'include', 'invalid']).size().to_frame('count').reset_index()
# -> LILA data had only 'invalid' == 0 records

# -> newtime is mostly 'TimestampAccepted' (small discrepancies for S4, S5 and S6)
print_stats(tt['season'].value_counts())

##################################
# Create Captures DF
##################################

# Problems
# 1) some captures have no time
# 2) some captures have timestampaccepted not on first image in sequence
# 3) some captures have correct timestamp only on first image

# Logic
# 1) take timestamp of first non-empty ordered by sequence num
# 2) leave blank if no timestamp found

# Create capture to timestamp df
# take lila datetime and substitute with 'datetime' if not available
print_stats(df_final['season'].value_counts())
df_capture = df_final.copy()
df_capture['datetime_clean'] = df_capture['datetime_lila']
i_no_lila = (df_capture['datetime_clean'] == '') | (df_capture['datetime_clean'].isna())
df_capture['datetime_clean'].loc[i_no_lila] = df_capture['datetime'].loc[i_no_lila]

# check
# tt = df_capture.drop_duplicates(subset=['season', 'site', 'roll', 'capture'])
# tt[tt['image_rank_in_capture'] != '1']
# df_merged2[(df_merged2['image_path'] == 'S4/B12/B12_R2/S4_B12_R2_IMAG0070.JPG') & (df_merged2['invalid'] == '0')]
#df_captures_first_image[df_captures_first_image['datetime_clean'] == ''][['image_path','SequenceNum']]

# move forward with only datetime_clean (mix of LILA and DB timestamp)
df_capture = df_capture[['season', 'site', 'roll', 'capture', 'datetime_clean']]

# get captures that have at least one non-empty datetime
df_capture_first_with_datetime = df_capture.loc[df_capture['datetime_clean'] != '']
df_capture_first_with_datetime.drop_duplicates(subset=['season', 'site', 'roll', 'capture'], inplace=True)

# get the rest of the captures
df_captures_first_image = df_capture.drop_duplicates(subset=['season', 'site', 'roll', 'capture'])

# df_captures_first_image.iloc[0]
# df_merged2[(df_merged2['season'] == 'S1') &
#  (df_merged2['site'] == 'O05') &
#  (df_merged2['roll'] == '1') &
#  (df_merged2['capture'] == '1171')]
# df_merged2[(df_merged2['season'] == 'S1') &
#  (df_merged2['site'] == 'C06') &
#  (df_merged2['roll'] == '1') &
#  (df_merged2['capture'] == '14')]

# merge both together to find captures without timestamps
df_captures_without_timestamps = pd.merge(
    left=df_captures_first_image,
    right=df_capture_first_with_datetime,
    on=['season', 'site', 'roll', 'capture'],
    how='left',
    suffixes=('', '_f'))
df_captures_without_timestamps = df_captures_without_timestamps[df_captures_without_timestamps['datetime_clean_f'].isna()]

df_captures_without_timestamps.shape[0]
# 6 captures without timestamps

# combine
df_captures_all = df_capture_first_with_datetime.append(df_captures_without_timestamps[['season', 'site', 'roll', 'capture', 'datetime_clean']])
df_captures_all.shape
df_captures_all.columns

# check
tt = df_captures_all.drop_duplicates(subset=['season', 'site', 'roll', 'capture'])
assert tt.shape[0] == df_captures_all.shape[0]

df_captures_all['capture_id'] = df_captures_all[['season', 'site', 'roll', 'capture']].apply(
    lambda x: create_capture_id(*x), axis=1)

# sort by capture id
sort_df_by_capture_id(df_captures_all)

###########
# checks
###########

# wrong capture from S5 should not be included
assert sum(df_captures_all['capture_id'].isin(['SER_S5#G07#3#44', 'SER_S5#G07#3#129'])) == 0

tt = pd.merge(left=df_captures_all, right=df_final[['season', 'site', 'roll', 'capture', 'datetime_lila']],
 on=['season', 'site', 'roll', 'capture'],
 how='outer')

# no diffs to LILA datetimes
tt_lila = tt[(~tt['datetime_lila'].isna()) & (tt['datetime_lila'] != '')]
tt_diff = tt_lila[tt_lila['datetime_lila'] != tt_lila['datetime_clean']]
assert tt_diff.shape[0] == 0

# no empty capture ids
assert df_captures_all[df_captures_all['capture_id'] == ''].shape[0] == 0
assert df_captures_all[df_captures_all['capture_id'].isna()].shape[0] == 0

# 6 captures without datetime_clean
no_datetime = df_captures_all[df_captures_all['datetime_clean'] == '']
no_datetime.shape

# Create capture date/time
# df_captures_all['capture_date_local'] = df_captures_all['datetime_clean'].apply(
#     lambda x: create_date(x))
#
# df_captures_all['capture_time_local'] = df_captures_all['datetime_clean'].apply(
#     lambda x: create_time(x))

df_captures_all.columns

df_captures_all.rename(columns={'datetime_clean': 'datetime'}, inplace=True)
df_captures_all = df_captures_all[[
    'capture_id', 'season', 'site', 'roll', 'capture', 'datetime']]

df_captures_all.to_csv(os.path.join(path_output, 'capture_events.csv'), index=False)

#df_captures_all = pd.read_csv(os.path.join(path_output, 'capture_events.csv'), index_col=False, dtype=str)

##################################
# Create Subject to Cap Mapper
##################################

df_base = df_merged2[['subject_id_lila', 'subject_id', 'season', 'site', 'roll', 'capture']].copy()

df_base['capture_id'] = df_base[['season', 'site', 'roll', 'capture']].apply(
    lambda x: create_capture_id(*x), axis=1)

df_subject_to_capture = pd.merge(
    left=df_captures_all,
    right=df_base,
    on=['capture_id', 'season', 'site', 'roll', 'capture'],
    how='left'
)

i_sub_missing = (df_subject_to_capture['subject_id'] == '')
df_subject_to_capture['subject_id'].loc[i_sub_missing] = df_subject_to_capture['subject_id_lila'].loc[i_sub_missing]
df_subject_to_capture = df_subject_to_capture.loc[df_subject_to_capture['subject_id'] != '']

tt = df_subject_to_capture.groupby('capture_id').subject_id.nunique()
multi_sub = tt[tt>1]
assert multi_sub.shape[0] == 0

df_subject_to_capture = df_subject_to_capture[['capture_id', 'subject_id']]
df_subject_to_capture.to_csv(os.path.join(path_output, 'capture_id_to_subject_id.csv'), index=False)

# tt = df_subject_to_capture.loc[(df_subject_to_capture['subject_id'] == '')]
# print_stats(tt['season'].value_counts())
#
# tt = df_subject_to_capture.loc[(df_subject_to_capture['subject_id'] == '') | (df_subject_to_capture['subject_id_lila'] == '')]
# print_stats(tt['season'].value_counts())
#
# tt = df_subject_to_capture.loc[(df_subject_to_capture['subject_id'] == '') & (df_subject_to_capture['subject_id_lila'] == '')]
# print_stats(tt['season'].value_counts())
#
# tt = df_subject_to_capture.loc[
#     (df_subject_to_capture['subject_id'] != df_subject_to_capture['subject_id_lila']) &
#     (df_subject_to_capture['subject_id'] != '') &
#     (df_subject_to_capture['subject_id_lila'] != '')]
# tt.shape
#
#
# tt.groupby(['season', 'include', 'invalid']).size().to_frame('count').reset_index()
# # -> LILA data had only 'invalid' == 0 records
#
# # -> newtime is mostly 'TimestampAccepted' (small discrepancies for S4, S5 and S6)
# print_stats(tt['season'].value_counts())

##################################
# Create Final DF
##################################

###########################
# Current / New Attributes
# capture_id,
# season,
# site,
# roll,
# capture,
# image_rank_in_capture,
# image_name,
# image_path,
# image_path_rel,
# datetime,
# image_rank_in_roll,
# seconds_to_next_image_taken,seconds_to_last_image_taken,
# days_to_last_image_taken,days_to_next_image_taken,
# image_name_original,image_path_original,image_path_original_rel,
# image_check__all_black,image_check__all_white,image_check__corrupt_file,image_check__corrupt_exif
# ,image_check__empty_exif,image_check__time_lapse,image_check__time_too_new,
# image_check__time_too_old,image_check__captures_with_too_many_images,
# datetime_file_creation,
# datetime_exif,
# exif__EXIF:Make,exif__EXIF:Model,exif__EXIF:Software,exif__EXIF:ModifyDate,exif__EXIF:Copyright,exif__EXIF:Orientation,exif__EXIF:XResolution,exif__EXIF:YResolution,exif__EXIF:ResolutionUnit,exif__EXIF:YCbCrPo
# sitioning,exif__EXIF:ExifVersion,exif__EXIF:DateTimeOriginal,exif__EXIF:CreateDate,exif__EXIF:ExposureTime,exif__EXIF:FNumber,exif__EXIF:MeteringMode,exif__EXIF:WhiteBalance,exif__EXIF:ColorSpace
# ,exif__EXIF:Flash,exif__EXIF:ExposureProgram,exif__EXIF:ISO,exif__EXIF:ExposureCompensation,exif__EXIF:ExifImageWidth,exif__EXIF:ExifImageHeight,exif__EXIF:UserComment,exif__Composite:Aperture,ex
# if__Composite:ImageSize,exif__Composite:Megapixels,
# exif__Composite:ShutterSpeed,exif__Composite:LightValue,
# exif__EXIF:Rating,exif__EXIF:RatingPercent,
# exif__EXIF:Padding,
# image_was_deleted,
# image_is_invalid,
# image_no_upload,
# image_uncertain_datetime,
# action_taken, action_taken_reason

# from captures_all
# 'capture_id', 'season', 'site', 'roll', 'capture', 'datetime'
# from df_final
# 'image_rank_in_capture', 'image_path_rel'
# datetime_file_creation
# datetime_exif
# image_was_deleted ? no
# image_is_invalid ? if 'invalid' = 0 then 1 else 0
# image_uncertain_datetime ? if 'invalid' 1,2,3 then 1 else 0

# final record
# 'capture_id', 'season', 'site', 'roll', 'capture', 'image_rank_in_capture',
# 'image_path_rel',
# 'datetime', 'datetime_file_creation', 'datetime_exif',
# 'invalid', 'include', 'image_is_invalid', 'image_uncertain_datetime'

df_final_cols = df_final[['image_path_rel', 'season', 'site', 'roll',
 'capture', 'image_rank_in_capture', 'datetime_exif',
 'datetime_file_creation', 'invalid', 'include']].copy()

df_final_cols.fillna('', inplace=True)
print_stats(df_final_cols['include'].value_counts())
df_final_cols.groupby(['invalid', 'include']).size().to_frame('count').reset_index()
df_final_cols.groupby(['season']).size().to_frame('count').reset_index()

# re-create 'include' flag
i_to_include = df_final_cols['invalid'].isin(['0'])
i_to_exclude = df_final_cols['invalid'].isin(['1', '2', '3'])
i_to_unknown = df_final_cols['invalid'].isin([''])
df_final_cols['include'].loc[i_to_include] = '1'
df_final_cols['include'].loc[i_to_exclude] = '0'
df_final_cols['include'].loc[i_to_unknown] = ''

# re-create 'image_is_valid' / 'image_uncertain_datetime' flags
df_final_cols['image_is_invalid'] = ''
df_final_cols['image_datetime_uncertain'] = ''
df_final_cols['image_is_invalid'].loc[i_to_exclude] = '1'
df_final_cols['image_datetime_uncertain'].loc[i_to_exclude] = '1'
df_final_cols['image_is_invalid'].loc[i_to_include] = '0'
df_final_cols['image_datetime_uncertain'].loc[i_to_include] = '0'

# re-create dummy columns
df_final_cols['image_was_deleted'] = ''
df_final_cols['image_no_upload'] = ''
df_final_cols['action_taken'] = ''
df_final_cols['action_taken_reason'] = ''

# re-create image_name  column
df_final_cols['image_name'] = [os.path.basename(x) for x in df_final_cols['image_path_rel']]

df_final_cols = pd.merge(
    left=df_final_cols, right=df_captures_all,
    how='left', on=['season', 'site', 'roll', 'capture'])

# checks
assert sum(df_final_cols['capture_id'].isna()) == 0
assert sum(df_final_cols['capture_id'] == '') == 0
assert sum(df_final_cols['capture'] == '') == 0
assert sum(df_final_cols['image_path_rel'] == '') == 0

# some missing values expected
sum(df_final_cols['datetime'].isna())
sum(df_final_cols['datetime'] == '')
sum(df_final_cols['datetime_file_creation'] == '')
sum(df_final_cols['datetime_file_creation'] != '')
sum(df_final_cols['datetime_exif'] == '')

df_final_cols.groupby(['invalid', 'image_is_invalid']).size().to_frame('count').reset_index()
df_final_cols.groupby(['invalid', 'image_datetime_uncertain']).size().to_frame('count').reset_index()

# final record
# 'capture_id', 'season', 'site', 'roll', 'capture', 'image_rank_in_capture',
# 'image_path_rel',
# 'datetime', 'datetime_exif',
# 'invalid', 'include', 'image_is_invalid', 'image_uncertain_datetime'

final_cols = [
 'capture_id',
 'season',
 'site',
 'roll',
 'capture',
 'image_rank_in_capture',
 'image_name',
 'image_path_rel',
 'datetime',
 'datetime_exif',
 'datetime_file_creation',
 'image_is_invalid',
 'image_datetime_uncertain',
 'image_no_upload',
 'image_was_deleted',
 'action_taken',
 'action_taken_reason',
 'invalid',
 'include']

df_final_cols = df_final_cols[final_cols]

df_final_cols.to_csv(os.path.join(path_output, 'captures_cleaned.csv'), index=False)

##################################
# Create Filtered / Valid DF
##################################

df_final_include = df_final_cols.loc[df_final_cols['include'].isin(['1'])]

df_final_include.to_csv(os.path.join(path_output, 'captures_include.csv'), index=False)

##################################
# Create Season Cleaned
##################################

all_seasons = df_final_cols['season'].value_counts()

# sum(df_final_cols['image_rank_in_capture'] == '')
# tt = df_final_cols[df_final_cols['image_rank_in_capture'] == '']
# tt.groupby(['invalid', 'include']).size().to_frame('count').reset_index()

for season in all_seasons.index:
    print("Starting with Season {}".format(season))
    output_path_season = os.path.join(path_output, 'SER_{}_cleaned.csv'.format(season))
    i_season = df_final_cols['season'].isin([season])
    df_season = df_final_cols.copy()
    df_season = df_season.loc[i_season]
    print("Sorting Season {}".format(season))
    sort_df(df_season)
    print("Writing Season {}".format(season))
    df_season.to_csv(output_path_season, index=False)
    print("Wrote Season {}".format(season))

##################################
# Checks
##################################

# check for duplicates
tt = df_final_include.groupby('image_path_rel').size()
assert tt[tt>1].shape[0] == 0

# check datetime available
assert sum(df_final_include['datetime'].isna()) == 0
assert sum(df_final_include['datetime'] == '') == 0
assert sum(df_final_include['datetime_exif'] == '') == 0

# some missing values expected
sum(df_final_include['datetime_file_creation'] == '')

# check lila overlap
lila_check = pd.merge(left=df_lila, right=df_final_include, how='left',
 left_on='image_path', right_on='image_path_rel')

assert sum(lila_check['capture_id'].isna()) == 0
assert sum(lila_check['capture_id'] == '') == 0
assert sum((lila_check['datetime_x'] != lila_check['datetime_y']) & (lila_check['datetime_x'] != '')) == 0

print_stats(df_final_include['season'].value_counts())
print_stats(df_lila['season'].value_counts())

# check / investigate captures previously not in LILA but now are
tt = pd.merge(
    left=df_final_include,
    right=df_lila,
    left_on='image_path_rel',
    right_on='image_path',
    how='left',
    suffixes=('', '_lila'))

tt = tt.loc[(tt['season'].isin(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])) & (tt['season_lila'].isna())]
tt.shape[0]
print_stats(tt['season'].value_counts())
print_stats(tt['invalid'].value_counts())
tt.iloc[0]
tt
# 12 new images (4 captures)

# check status of lila data
tt = pd.merge(
    left=df_final_include,
    right=df_lila,
    left_on='image_path_rel',
    right_on='image_path',
    how='left',
    suffixes=('', '_lila'))
tt = tt.loc[(tt['season'].isin(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])) & (tt['season_lila'].isna())]

# not in LILA but look ok
# 1908680  S5/B03/B03_R2/S5_B03_R2_IMAG0015.JPG  2012-08-05 07:37:08
# 1908681  S5/B03/B03_R2/S5_B03_R2_IMAG0016.JPG  2012-08-05 07:37:08
# 1908682  S5/B03/B03_R2/S5_B03_R2_IMAG0017.JPG  2012-08-05 07:37:08
# 2040775  S5/D06/D06_R1/S5_D06_R1_IMAG1601.JPG  2012-06-10 11:15:55
# 2040776  S5/D06/D06_R1/S5_D06_R1_IMAG1602.JPG  2012-06-10 11:15:55
# 2040777  S5/D06/D06_R1/S5_D06_R1_IMAG1603.JPG  2012-06-10 11:15:55
# 3157969  S6/S13/S13_R1/S6_S13_R1_IMAG5890.JPG  2013-01-17 08:08:47
# 3157970  S6/S13/S13_R1/S6_S13_R1_IMAG5891.JPG  2013-01-17 08:08:47
# 3157971  S6/S13/S13_R1/S6_S13_R1_IMAG5892.JPG  2013-01-17 08:08:47
# 3182402  S6/U10/U10_R2/S6_U10_R2_IMAG0448.JPG  2013-03-08 17:31:15
# 3182403  S6/U10/U10_R2/S6_U10_R2_IMAG0449.JPG  2013-03-08 17:31:15
# 3182404  S6/U10/U10_R2/S6_U10_R2_IMAG0450.JPG  2013-03-08 17:31:15

# check file existence
# no files missing
# all_files = set()
# for dirpath, dirnames, filenames in os.walk('/home/packerc/shared/albums/SER/'):
#     for filename in [f for f in filenames if f.endswith(".JPG")]:
#         all_files.add(os.path.join(dirpath, filename))
#
# root_path = '/home/packerc/shared/albums/SER/'
# missing = list()
# for _i, row in df_final_include.iterrows():
#     if (_i % 100000) == 0:
#         print("Processed {}".format(_i))
#     img_path = os.path.join(root_path, row.image_path_rel)
#     if not img_path in all_files:
#         missing.append(row.image_path_rel)
#         print("Did not find: {}".format(row.image_path_rel))

##################################
# Observations
##################################

"""
Rational:

To ensure consistency between how we have selected Serengeti S1-S6 for public release on LILA and
the upcoming release of newly created exports for S1-S11 I have investigated in detail how
previous seasons were selected. Main questions were to figure out which images were selected and how
the timestamps were defined. Along the way I have found some smaller inconsistencies. I think I have now found
a good way to create a consistent LILA delivery for public release. To ensure whatever I did makes sense to you,
I'd like you to validate the following points/observations/decisions:

1. Most images have an include and/or an invalid flag (some have neither)
2. The invalid flag indicates images with correct/confirmed timestamps.
3. All images released on LILA were flagged with invalid = '0'. You only want to release images with invalid = '0'.
4. 12 images with invalid = '0' were not released on LILA (unclear why -- the images look fine)
    -> I will include these images.
4. Some (~40) images from S7 have a faulty image name: * (2).JPG
    -> I will correct these image names.
5. Some (~6) images with faulty names in the db don't exist on MSI
    -> I will remove these non-existing images from the cleaned files.
9. 'newtime' corresponds to 'TimestampAccepted', 'oldtime' corresponds to 'TimestampJPG', this column name
    was changed over time.
6. 'TimestampAccepted' is the 'correct' time but is only available on 'capture' level. The LILA dateset, therefore,
   has the same time for each image of the same capture.
7. TimestampAccepted is usually defined for the first image of a capture but not always.
8. Some captures don't have a 1st, 2nd and 3rd image but a 2nd, 4th, 6th as defined by their 'SequenceNum'
    -> I will NOT correct these.
9. Empty/blanks released on LILA do not have a timestamp.
    -> I will add timestamps for blanks.

I can guarantee:

1. All images released on LILA (S1-S6) will be in the newly created reports
2. All timestamps released on LILA (S1-S6) will match with the newly created reports
3. The labels for S1-S6 may have changed because:
    1) they can not be reproduced b/c of random effects,
    2) we have gathered more annotations,
    3) small changes in how 'blanks' are defined
4. All images in the reports / cleaned.csv files actually exist on MSI

Does the following distribution per season make sense (number of images):
S9  -- counts:     983081 / 6679922 (14.72 %)
S8  -- counts:     980256 / 6679922 (14.67 %)
S7  -- counts:     832154 / 6679922 (12.46 %)
S5  -- counts:     827224 / 6679922 (12.38 %)
S10 -- counts:     685686 / 6679922 (10.26 %)
S2  -- counts:     573200 / 6679922 (8.58 %)
S4  -- counts:     531554 / 6679922 (7.96 %)
S6  -- counts:     462846 / 6679922 (6.93 %)
S1  -- counts:     411414 / 6679922 (6.16 %)
S3  -- counts:     392507 / 6679922 (5.88 %)
"""

#df_merged2.groupby(['season', 'Invalid', 'include']).size().to_frame('count').reset_index()
#   invalid include    count
# 0                    27072
# 1       0       1  6679922
# 2       1       0    32572
# 3       2       0    36773
# 4       3       0    14555

##################################
# OLD CHECKS
##################################

# # check for duplicates
# tt = df_final_include.groupby('image_path_rel').size()
# tt[tt>1]
#
# # check lila content
# lila_check = pd.merge(left=df_lila, right=df_final_include, how='left',
#  left_on='image_path', right_on='image_path_rel')
#
# tt = lila_check[lila_check['capture_id'].isna()]
#
# df_lila[df_lila['image_path'] == 'S1/L10/L10_R1/S1_L10_R1_PICT0001.JPG']
# df_export[df_export['image_path'] == 'S1/L10/L10_R1/S1_L10_R1_PICT0001.JPG']
# # 'SequenceNum' wrong, capture starts with 2, 4 6
#
#
# lila_check.shape
# df_lila.shape
#
#
# # duplicates
# # df_export[df_export['image_path']== 'S1/I07/I07_R1/S1_I07_R1_PICT0007.JPG']
# # df_merged2[df_merged2['image_path'] == 'S1/I07/I07_R1/S1_I07_R1_PICT0007.JPG']
# # df_merged2[df_merged2['image_path'] == 'S10/H06/H06_R2/S10_H06_R2_IMAG0062.JPG']
# # df_merged2[(df_merged2['season'] == 'S10') & (df_merged2['site'] == 'H06') & (df_merged2['roll'] == '2') & (df_merged2['capture_clean'] == '23')]
#
# # test timestamps
# tt = df_merged2[(df_merged2['oldtime'] != df_merged2['TimestampJPG']) & (df_merged2['oldtime'] != '') & (df_merged2['TimestampJPG'] != '')]
# tt.shape
# # -> oldtime is TimestampJPG
#
# tt = df_merged2[(df_merged2['newtime'] != df_merged2['TimestampAccepted']) & (df_merged2['newtime'] != '') & (df_merged2['TimestampAccepted'] != '')]
# tt.shape
# # -> newtime is mostly 'TimestampAccepted' (small discrepancies for S4, S5 and S6)
# print_stats(tt['season'].value_counts())
#
# tt = df_merged2[(df_merged2['oldtime'] != df_merged2['newtime']) & (df_merged2['oldtime'] != '') & (df_merged2['newtime'] != '')]
# tt.shape
# # -> oldtime is not newtime
#
# # compare lila
# tt = df_merged2[(df_merged2['datetime'] != df_merged2['TimestampAccepted']) & (df_merged2['datetime'] != '') & (df_merged2['TimestampAccepted'] != '')]
# tt.shape
# print_stats(tt['SequenceNum'].value_counts())
# tt.iloc[10]
# # wrong in db (but only for sequecenum2)
# df_merged2[df_merged2['image_path'] == 'S1/D09/D09_R2/S1_D09_R2_PICT0014.JPG']
#
# tt = df_merged2[(df_merged2['datetime'] != df_merged2['TimestampAccepted']) &
#     (df_merged2['datetime'] != '') &
#     (df_merged2['TimestampAccepted'] != '') &
#     (df_merged2['SequenceNum'] == '1')]
# tt.shape
# # 65 first images with different timestamp
# for _i, row in tt.iterrows():
#     print("cleaned: {} lila: {}".format(row.TimestampAccepted, row.datetime))
# # some significant differences - db is wrong
# tt.iloc[4]
# df_merged2[df_merged2['image_path'] == 'S1/E02/E02_R5/S1_E02_R5_PICT0274.JPG']
# # -> generally accept LILA timestamp
#
#
# df_merged2.groupby(['season', 'invalid', 'include']).size().to_frame('count').reset_index()
#
#
# # TESTS
# df_merged[(df_merged['season'] == 'S4') &
# (df_merged['site'] == 'C07') &
#  (df_merged['roll'] == '2') &
#   (df_merged['capture'] == '1234')]
#
#
# #df_capture = df_capture[df_capture['datetime_clean'] != '']
# df_capture3 = df_capture.groupby(['season', 'site', 'roll', 'capture']).first()
#
#
# # try to get datetime
# def create_datetime(date_str):
#     try:
#         tm = _get_datetime_obj(date_str)
#     except:
#         tm = None
#     return tm
# df_capture['datetime_obj'] = df_capture['datetime_clean'].apply(
#     lambda x: create_datetime(x))
# df_capture2 = df_capture.groupby(['season', 'site', 'roll', 'capture']).datetime_obj.agg(['min', 'max'])
#
# df_capture2[df_capture2['min'] !=  df_capture2['max']]
# # TESTS
# df_final[(df_final['season'] == 'S1') &
# (df_final['site'] == 'C07') &
#  (df_final['roll'] == '2') &
#   (df_final['capture'] == '1042')]
#
# df_capture.drop_duplicates(subset=['season', 'site', 'roll', 'capture'], inplace=True)
# df_capture.shape
# # (2513837, 6) without excluding 'datetime_clean' != ''
# # (2503309, 5) with excluding 'datetime_clean' != ''
#
# # TESTS
# no_datetime = df_capture[df_capture['datetime_clean'] == '']
# df_final[(df_final['season'] == 'S2') &
# (df_final['site'] == 'H07') &
#  (df_final['roll'] == '2') &
#   (df_final['capture'] == '242')]['datetime']
# df_capture[(df_capture['season'] == 'S2') &
# (df_capture['site'] == 'H07') &
#  (df_capture['roll'] == '2') &
#   (df_capture['capture'] == '242')]['datetime']
#
# tt = df_capture[(df_capture['season'] == 'S2') &
# (df_capture['site'] == 'H07') &
#  (df_capture['roll'] == '2') &
#   (df_capture['capture'] == '242')]
# tt['datetime_obj'] = tt['datetime_clean'].apply(
#     lambda x: create_datetime(x))
# tt2 = tt.groupby(['season', 'site', 'roll', 'capture']).datetime_obj.agg(['min', 'max'])
#
# # SER_S8#L11#3#1224
# # has no datetime
# df_final[(df_final['season'] == 'S8') &
# (df_final['site'] == 'L11') &
#  (df_final['roll'] == '3') &
#   (df_final['capture'] == '1224')]['datetime']
#
#
#
# def create_capture_id(season, site, roll, capture):
#     return 'SER_{}#{}#{}#{}'.format(season, site, roll, capture)
#
# df_capture['capture_id'] = df_capture[['season', 'site', 'roll', 'capture']].apply(
#     lambda x: create_capture_id(*x), axis=1)
#
# # investigate
# df_capture.shape
#
#
# # SER_S1#L10#1#9
# df_merged2[(df_merged2['season'] == 'S1') & (df_merged2['site'] == 'L10') & (df_merged2['roll'] == '1') & (df_merged2['capture_export'] == '9')]
# # remove:
# # S1/L10/L10_R1/S1_L10_R1_PICT0023 (2).JPG
# # S1/L10/L10_R1/S1_L10_R1_PICT0024 (2).JPG
# # S1/L10/L10_R1/S1_L10_R1_PICT0025 (2).JPG
#
# # SER_S1#O05#1#1171
# df_merged2[(df_merged2['season'] == 'S1') &
#     (df_merged2['site'] == 'O05') &
#     (df_merged2['roll'] == '1') &
#     (df_merged2['capture_export'] == '1171')]
# # if no lila timestamp and if no timestamp accepted, take timestamp jpg
#
# # SER_S8#L11#3#
# df_merged2[(df_merged2['season'] == 'S8') &
#  (df_merged2['site'] == 'L11') &
#   (df_merged2['roll'] == '3') &
#   (df_merged2['capture_export'] == '1225')]
# # substitute capture export with capture clean if capture export is empty
# # some captures not in db but in cleaned?
#
#
#
# S1/O05/O05_R1/S1_O05_R1_PICT3650.JPG
# S1/O05/O05_R1/S1_O05_R1_PICT3711.JPG
# df_merged2[df_merged2['image_path'] == 'S1/O05/O05_R1/S1_O05_R1_PICT3650.JPG']
# df_merged2[df_merged2['image_path'] == 'S1/O05/O05_R1/S1_O05_R1_PICT3711.JPG']


import os

filesize = []
def get_file(path):
	os.chdir(path)
	objlist = os.listdir()
	for name in objlist:
		obj = os.path.join(path,name)
		if os.path.isfile(obj):
			print('%s | 文件大小：%d' % (obj, os.path.getsize(obj)))
			filesize.append(os.path.getsize(obj))
		else:
			get_file(obj)

def dir_sum():
	sig = {
		4:'TB',
		3:'GB',
		2:'MB',
		1:'KB',
		0:'byte'
	}
	dirsize = sum(filesize)

	for i in reversed(range(1,5)):
		#i=4 3 2 1
		if dirsize > 1024 ** i:
			res = dirsize / (1024 ** i)
			break
		else:
			i = 0
			res = dirsize
	print('文件夹大小:%.2f %s' % (res,sig[i]))

get_file('f:\pysalt')
dir_sum()


import json

def configure():
    try:
        f = open("cemubot/misc/config.cfg", "x+", encoding="utf-8")
    except FileExistsError:
        f = open("cemubot/misc/config.cfg", "w", encoding="utf-8")
    config = {}
    config["bot_token"] = None
    while not config["bot_token"]:
        config["bot_token"] = input("Please provide a bot token: ")
    config["command_prefix"] = input("Please provide a command prefix (e.g. ^ for ^help) ")
    if not config["command_prefix"]:
        print("No prefix provided; defaulting to ^.")
        config["command_prefix"] = "^"
    print("Please provide the channel ID of the channel(s) you want the bot to parse logs in.")
    print("NOTE: A channel ID is something like 123456789123456789, not #channel-name.")
    config["parsing_channel"] = {}
    config["parsing_channel"]["preferred"] = int(input("Preferred channel ID goes here (0 to parse in all channels): "))
    config["parsing_channel"]["alternates"] = list(map(int, input("Comma-separated list of IDs goes here (leave blank to skip): ").split(',')))
    config["compatibility_colors"] = {
        "perfect": 0x3380CC,
        "playable": 0x16A689,
        "runs": 0xD9D936,
        "loads": 0xDF8D12,
        "unplayable": 0xBF3E32,
        "unknown": 0x858585
    }
    print(f"Your config file now looks like this:\n{json.dumps(config, indent=2)}")
    if input("Is this correct? (y/n) ").lower() == 'n':
        f.close()
        configure()
    else:
        json.dump(config, f, indent=4)
        print("Configuration complete. If the bot still doesn't work, try running setup.py again.")
        f.close()

try:
    open("cemubot/misc/config.cfg", "r", encoding="utf-8").close()
    if input("config.cfg already exists; would you like to re-configure the bot? (y/n) ").lower() == 'y':
        configure()
except FileNotFoundError:
    if input("config.cfg not found; would you like to configure the bot? (y/n) ").lower() == 'y':
        configure()


from util import prettify_edge_dict, get_path_edges, calculate_path_capacity, calculate_flow_value
from maximum_flow import ford_fulkerson, find_augmenting_path_bfs, find_augmenting_path_dfs, find_augmenting_path_max, find_augmenting_path_min, pretty_print_ford_fulkerson

def construct_residual_graph_min_flow(edges, lower_bounds, upper_bounds, flow):
    """
    Constructs a residual graph.

    Parameters
    ----------
    edges : list of tuple of (str, str)
        A list of edges - represented as a pair of nodes (u, v) - in the
        original graph.
    lower_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - in the original graph and values are the lower bounds on the flow
        along the edges.
    upper_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - in the original graph and values are the upper bounds on the flow
        along the edges.
    flow : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - in the original graph and values are the flow along the edges.

    Returns
    -------
    residual_edges : list of tuple of (str, str)
        A list of edges - represented as a pair of nodes (u, v) - in the
        residual graph.
    residual_capacities : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - in the residual graph and values are the residual capacities of the
        edges.
    """
    residual_capacities = {}
    for (u, v) in edges:
        residual_capacities[(u, v)] = upper_bounds[(u, v)] - flow[(u, v)]
        residual_capacities[(v, u)] = flow[(u, v)] - lower_bounds[(u, v)]
    return (list(residual_capacities.keys()), residual_capacities)

def ford_fulkerson_min_flow(edges, lower_bounds, upper_bounds, source, sink, search_method, flow = None, debug = False):
    """
    A modified version of the Ford-Fulkerson method for use in minimum flow
    graphs. The only difference is the method used to construct the residual
    graph.

    Parameters
    ----------
    edges : list of tuple of (str, str)
        A list of edges - represented as a pair of nodes (u, v) - in the graph.
    lower_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the lower bounds on the flow along the edges.
    upper_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the upper bounds on the flow along the edges.
    source : str
        The source node.
    sink : str
        The sink node.
    search_method : (list of tuple of (str, str), dict of {tuple of (str, str) :
    int}, str, str) --> list of str
        A search method that, given the edges of a graph, their capacities, the
        source node and the sink node, finds an augmenting path in a residual
        graph and returns the list of nodes on the path.
    flow : dict of {tuple of (str, str) : int}, default None, meaning no flow
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the flow along the edges. This is the initial flow.
    debug : bool, default False
        Whether to print debug information or not.

    Returns
    -------
    flow : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the flow along the edges. This is the maximum flow
        that could be found.
    flow_value : int
        The total value of the flow that was found.
    """
    # Initialise flow if none given.
    if not flow:
        flow = {}
        for edge in edges:
            flow[edge] = 0

    # Build residual graph and find augmenting path.
    (residual_edges, residual_capacities) = construct_residual_graph_min_flow(edges, lower_bounds, upper_bounds, flow)
    augmenting_path_nodes = search_method(residual_edges, residual_capacities, source, sink)

    # Keep augmenting the flow until an augmenting path cannot be found.
    counter = 0
    while (augmenting_path_nodes):
        counter += 1

        # Get the augmenting path edges and the augmenting path capacity.
        augmenting_path_edges = get_path_edges(augmenting_path_nodes)
        augmenting_path_capacity = calculate_path_capacity(residual_capacities, augmenting_path_edges)

        # Augment the current flow with the augmenting path.
        for (u, v) in edges:
            if (u, v) in augmenting_path_edges:
                flow[(u, v)] += augmenting_path_capacity
            if (v, u) in augmenting_path_edges:
                flow[(u, v)] -= augmenting_path_capacity
        if debug:
            pretty_print_ford_fulkerson("Ford-Fulkerson Min Flow (" + search_method.__name__ + "), iteration " + str(counter),
                                        flow,
                                        calculate_flow_value(flow, source),
                                        augmenting_path_nodes,
                                        augmenting_path_capacity)

        # Build residual graph and find augmenting path.
        (residual_edges, residual_capacities) = construct_residual_graph_min_flow(edges, lower_bounds, upper_bounds, flow)
        augmenting_path_nodes = search_method(residual_edges, residual_capacities, source, sink)

    # Once an augmenting path cannot be found, return the flow.
    return (flow, calculate_flow_value(flow, source))

def minimum_flow(nodes, edges, lower_bounds, upper_bounds, source, sink, search_method, feasible_flow=None, debug=None):
    """
    Attempts to find the minimum feasible flow in a flow graph.

    A feasible flow must satisfy three things: upper and lower bound constraints
    on edges, conservation of flow at nodes, and supply-demand of the nodes.

    For more information, see 25.1
    https://courses.engr.illinois.edu/cs498dl1/sp2015/notes/25-maxflowext.pdf

    Parameters
    ----------
    nodes : list of str
        A list of all the nodes in the graph.
    edges : list of tuple of (str, str)
        A list of edges in the graph, represented as a pair of nodes (u, v).
    lower_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the lower bounds on the flow along the edges.
    upper_bounds : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the upper bounds on the flow along the edges.
    source : str
        The source node.
    sink : str
        The sink node.
    search_method : (list of tuple of (str, str), dict of {tuple of (str, str) :
    int}, str, str) --> list of str
        A search method that, given the edges of a graph, their capacities, the
        source node and the sink node, finds an augmenting path in a residual
        graph and returns the list of nodes on the path.
    feasible_flow : dict of {tuple of (str, str) : int}, default None, meaning
    no flow
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the flow along the edges. This is a feasible flow,
        but not necessarily the minimum feasible flow.
    debug : bool, default False
        Whether to print debug information or not.

    Returns
    -------
    min_flow : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the flow along the edges. This is the minimum
        feasible flow that could be found.
    min_flow_value : int
        The total value of the flow that was found.
    """
    # If a feasible flow was not given, it needs to be constructed.
    if not feasible_flow:
        # Add a new source and a new sink.
        nodes_prime = nodes.copy()
        nodes_prime.append("s_prime")
        nodes_prime.append("t_prime")

        # Add new edges between s_prime and all nodes, between all nodes and t_prime, and between t and s.
        edges_prime = edges.copy()
        for node in nodes:
            edges_prime.append(("s_prime", node))
            edges_prime.append((node, "t_prime"))
        edges_prime.append((sink, source))

        # The capacities are as follows:
        # For edges (s_prime, node): sum of lower bounds of incoming edges
        # For edges (node, t_prime): sum of lower bounds of outgoing edges
        # For edges (node1, node2): upper bound of the edge minus lower bound of the edge
        # For edge (t, s): infinity
        capacities = {}
        for node in nodes:
            capacities[("s_prime", node)] = sum([lower_bounds[(u,v)] for (u,v) in edges if v == node])
            capacities[(node, "t_prime")] = sum([lower_bounds[(v,w)] for (v,w) in edges if v == node])
        for edge in edges:
            capacities[edge] = upper_bounds[edge] - lower_bounds[edge]
        capacities[(sink, source)] = float("inf")

        # Find the maximum flow from s_prime to t_prime.
        flow_prime = ford_fulkerson(edges_prime, capacities, "s_prime", "t_prime", search_method, debug=debug)[0]

        # Find the feasible flow by adding the lower bounds.
        feasible_flow = {}
        for edge in edges:
            feasible_flow[edge] = flow_prime[edge] + lower_bounds[edge]

    # Send as much flow back from the sink to the source, while respecting edge lower bounds.
    (min_flow, min_flow_value) = ford_fulkerson_min_flow(edges, lower_bounds, upper_bounds, sink, source, search_method, flow=feasible_flow, debug=debug)

    # The min_flow_value is the outgoing flow from the sink. This will be
    # negative, so flip the sign to get the real minimum flow.
    return (min_flow, -min_flow_value)

def pretty_print_minimum_flow(title, flow, flow_value):
    """
    Prints the results of finding a minimum feasible flow in a user-friendly
    manner.

    Parameters
    ----------
    title : str
        The title of the problem to be printed.
    flow : dict of {tuple of (str, str) : int}
        A dictionary where keys are edges - represented as a pair of nodes (u,
        v) - and values are the flow along the edges. This is the minimum
        feasible flow that was found.
    flow_value : int
        The total value of the flow that was found.
    """
    print ("\n" + title)
    print ("----------------------------------------")
    print ("The minimum flow, with a value of " + str(flow_value) + ", is:\n" + prettify_edge_dict(flow))
    print("")

if __name__ == "__main__":
    #''' Week 5 Slides 15-18. Min flow = 2.
    nodes = ["a", "b", "c", "d", "e", "f", "s", "g", "t"]
    lower_bounds = {
        ("a", "b"): 0, ("a", "d"): 2,
        ("b", "c"): 0, ("b", "d"): 5, ("b", "f"): 0,
        ("c", "f"): 0,
        ("d", "e"): 3, ("d", "s"): 0,
        ("e", "b"): 0, ("e", "f"): 5,
        ("f", "g"): 0, ("f", "t"): 0,
        ("s", "a"): 0, ("s", "g"): 0,
        ("g", "e"): 0, ("g", "t"): 0,
        ("t", "c"): 0
    }
    upper_bounds = {
        ("a", "b"): 5, ("a", "d"): 5,
        ("b", "c"): 3, ("b", "d"): 6, ("b", "f"): 4,
        ("c", "f"): 1,
        ("d", "e"): 4, ("d", "s"): 5,
        ("e", "b"): 2, ("e", "f"): 7,
        ("f", "g"): 8, ("f", "t"): 3,
        ("s", "a"): 8, ("s", "g"): 4,
        ("g", "e"): 3, ("g", "t"): 4,
        ("t", "c"): 2
    }
    edges = list(upper_bounds.keys())

    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_dfs)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_max)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_min)[1]

    pretty_print_minimum_flow("Minimum Flow (BFS)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs))
    pretty_print_minimum_flow("Minimum Flow (DFS)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_dfs))
    pretty_print_minimum_flow("Minimum Flow (MAX)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_max))
    pretty_print_minimum_flow("Minimum Flow (MIN)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_min))
    #'''

    #''' Week 5 SGT Question 2. Min flow = 13.
    nodes = ["s", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "t"]
    lower_bounds = {
        ("s", "a"): 0, ("s", "c"): 0, ("s", "e"): 0, ("s", "g"): 0, ("s", "i"): 0,
        ("a", "b"): 5,
        ("b", "c"): 0, ("b", "e"): 0, ("b", "t"): 0,
        ("c", "d"): 6,
        ("d", "t"): 0,
        ("e", "f"): 5, ("e", "i"): 0,
        ("f", "c"): 0, ("f", "i"): 0, ("f", "t"): 0,
        ("g", "h"): 4,
        ("h", "i"): 0, ("h", "t"): 0,
        ("i", "j"): 7,
        ("j", "t"): 0,
    }
    upper_bounds = {
        ("s", "a"): float("inf"), ("s", "c"): float("inf"), ("s", "e"): float("inf"), ("s", "g"): float("inf"), ("s", "i"): float("inf"),
        ("a", "b"): float("inf"),
        ("b", "c"): float("inf"), ("b", "e"): float("inf"), ("b", "t"): float("inf"),
        ("c", "d"): float("inf"),
        ("d", "t"): float("inf"),
        ("e", "f"): float("inf"), ("e", "i"): float("inf"),
        ("f", "c"): float("inf"), ("f", "i"): float("inf"), ("f", "t"): float("inf"),
        ("g", "h"): float("inf"),
        ("h", "i"): float("inf"), ("h", "t"): float("inf"),
        ("i", "j"): float("inf"),
        ("j", "t"): float("inf"),
    }
    edges = list(upper_bounds.keys())
    flow = {
        ("s", "a"): 5, ("s", "c"): 6, ("s", "e"): 5, ("s", "g"): 4, ("s", "i"): 7,
        ("a", "b"): 5,
        ("b", "c"): 0, ("b", "e"): 0, ("b", "t"): 5,
        ("c", "d"): 6,
        ("d", "t"): 6,
        ("e", "f"): 5, ("e", "i"): 0,
        ("f", "c"): 0, ("f", "i"): 0, ("f", "t"): 5,
        ("g", "h"): 4,
        ("h", "i"): 0, ("h", "t"): 4,
        ("i", "j"): 7,
        ("j", "t"): 7,
    }

    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_dfs)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_max)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_min)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs, feasible_flow=flow)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_dfs, feasible_flow=flow)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs, feasible_flow=flow)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_max, feasible_flow=flow)[1]
    assert minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs, feasible_flow=flow)[1] == minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_min, feasible_flow=flow)[1]

    pretty_print_minimum_flow("Minimum Flow (BFS)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_bfs, feasible_flow=flow))
    pretty_print_minimum_flow("Minimum Flow (DFS)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_dfs, feasible_flow=flow))
    pretty_print_minimum_flow("Minimum Flow (MAX)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_max, feasible_flow=flow))
    pretty_print_minimum_flow("Minimum Flow (MIN)", *minimum_flow(nodes, edges, lower_bounds, upper_bounds, "s", "t", find_augmenting_path_min, feasible_flow=flow))
    #'''


from django.urls import path
from .views import Homepage, About, Products, Services, Contact

urlpatterns = [
    path("", Homepage, name='home'), # localhost:8000 -> Homepage
    path("about/", About, name='about'),
    path("product/", Products, name='product'),
    path("service/", Services, name='service'),
    path("contact/", Contact, name='contact'),
]


import discord
import utils as ut
from random import choices

initial_betting = 1

class Hand():
    def __init__(self, player: discord.Member | discord.User | str):
        self.player = player
        self.hand = ut.Alist()
        self.bet = initial_betting  # The amount that the player betted in this hand
        self.total = 0

    def eval_hand(self):
        self.total = 0

        for card in self.hand[:]:
            if card.value is None:
                continue
            if card.value == 11:
                if self.total + 11 > 21:
                    self.total += 1
                    continue
            self.total += card.value

    def canDouble(self):
        return len(self.hand) <= 2

    def canSplit(self):
        return len(self.hand) == 2 and self.hand[0].value == self.hand[1].value

    def draw_cards(self, deck: list, amount=1):
        cards = choices(deck, k=amount)
        self.hand.extend(cards)
        for card in cards:
            deck.remove(card)
        self.eval_hand()

    async def show_hand(self, channel: discord.TextChannel, show_options=False):
        name = self.player if isinstance(
            self.player, str) else self.player.name
        cards_img = ut.generateCards(self.hand)

        msg = discord.Embed(title=f'**{name}\'s Hand:**',  colour=discord.Colour.dark_green(),
                            description=f'``Total = {self.total}``')
        msg.set_footer(text='Made By: Victório Maculan')

        await channel.send(embed=msg)
        await channel.send(file=ut.pil2discord(cards_img))

    async def show_turn(self, channel: discord.TextChannel, hand_num=1):
        name = self.player if isinstance(
            self.player, str) else self.player.name
        cards_img = ut.generateCards(self.hand)
        msg = discord.Embed(title=f'**{name}\'s Hand N°{hand_num}:**',  colour=discord.Colour.dark_green(),
                            description=f'''``Total = {self.total}``

                                            **Your options:**
                                            🎯 -> Hit
                                            🛑 -> Stand''')

        if self.canDouble():
            msg.description += '\n 💸 -> Double Down'
        if self.canSplit():
            msg.description += '\n ✂ -> Split'
        msg.set_footer(text='You have 30 seconds to choose what to do!')

        menu = await channel.send(embed=msg)
        await channel.send(file=ut.pil2discord(cards_img))

        await menu.add_reaction('🎯')
        await menu.add_reaction('🛑')
        if self.canDouble():
            await menu.add_reaction('💸')
        if self.canSplit():
            await menu.add_reaction('✂')


"""
multi_run does multiple runs with the models
OBSELETE 3/2020
"""
from __future__ import print_function

import os.path
import model_run as MR
import pprint
import json

pp = pprint.PrettyPrinter(indent=4, width=60)

# create an instance of the modelrunner for each cell configuration

# population the configuration parameters

# run the model with the configuration. Data is automatically stored
# set the "auto initialize" flag.

"""

Define run parameters with a dictionary:
common parameters: all keys correspond to the .Params of the model instance.
common = {'FO': F0, 'fmod': fmod, 'dmod': dmod, 'nrep': nrep, 'SRType': SR,
    'run_duration': dur, 'dB': level, 'type': celltype}

For each run, the following values are changed:
{'runID': {'cellID': name, 'inputPattern': inputpattername}}

Parameters:
        self.cellChoices = ['Bushy', 'TStellate', 'DStellate']
        self.modelChoices = ['XM13', 'RM03', 'XM13PasDend', 'Calyx', 'MNTB', 'L23Pyr']
        self.cmmrModeChoices = ['CM', 'CD', 'REF']  # comodulated, codeviant, reference
        self.SRChoices = ['LS', 'MS', 'HS', 'fromcell']  # AN SR groups (assigned across all inputs)
        self.protocolChoices = ['initIV', 'testIV', 'runIV', 'initAN', 'runANPSTH', 'runANSingles']
        self.soundChoices = ['tone', 'noise', 'stationaryNoise', 'SAM', 'CMMR']
        self.speciesChoices = ['mouse', 'guineapig']

        self.srname = ['**', 'LS', 'MS', 'HS']  # runs 1-3, not starting at 0
        self.cellID = None  # ID of cell (string, corresponds to directory name under VCN_Cells)
        self.Params = OrderedDict()

        self.Params['initIVStateFile'] = 'IVneuronState.dat'
        self.Params['initANStateFile'] = 'ANneuronState.dat'
        self.Params['infile ']= None

        self.Params['cellType'] = self.cellChoices[0]
        self.Params['model_type'] = self.modelChoices[0]
        self.Params['species'] = self.speciesChoices[0]
        self.Params['SRType'] = self.SRChoices[2]
        self.Params['SR'] = self.Params['SRType']  # actually used SR this might be cell-defined, rather than command defined
        self.Params['inputPattern'] = None # ID of cellinput pattern (same as cellID): for substitute input patterns.

        self.Params['runProtocol'] = self.protocolChoices[2]  # testIV is default because it is fast and should be run often

        self.Params['run_duration'] = 0.8 # in sec
        self.Params['soundtype'] = 'SAM'  # or 'tonepip'
        self.Params['pip_duration'] = 0.1
        self.Params['pip_start'] = [0.1]
        self.Params['Fs'] = 100e3
        self.Params['F0'] = 4000.
        self.Params['dB'] = 40.
        self.Params['RF'] = 2.5e-3
        self.Params['fmod'] = 20 # hz, modulation if SAM
        self.Params['dmod'] = 0 # percent if SAM
        # spontaneous rate (in spikes/s) of the fiber BEFORE refractory effects; "1" = Low; "2" = Medium; "3" = High
        self.Params['threshold'] = -20
        self.Params['plotFlag'] = False
        self.Params['auto_initialize'] = False

"""
common = {'F0': 4000., 'fmod': 20., 'dmod': 0., 'nReps': 2, 'SRType': 'MS',
    'run_duration': 2.25, 'dB': 40., 'cellType': 'Bushy', 'pip_duration': 2.0}

# For each run, the following values are changed:

runs = {'selfinputs': {'cellID': 'VCN_c18', 'inputPattern': 'VCN_c18'}}

for runid in runs.keys():
    i1 = MR.ModelRun()
    parkeys = i1.Params.keys()
    for p in common.keys():
        if p not in parkeys:
            print('parameter %s not in parkeys list' % p)
            exit()
        else:
            i1.Params[p] = common[p] # set the parameters
        # force a couple of things
    i1.Params['initANStateFile'] = 'initAN_hoc_' + runs[runid]['cellID']+'_input_' + runs[runid]['inputPattern']
    i1.Params['inputPattern'] = runs[runid]['inputPattern']
    i1.Params['cell'] = runs[runid]['cellID']
    i1.Params['infile'] = runs[runid]['cellID'] + '.hoc'

    print(json.dumps(i1.Params, indent=4))

    stateFileExists = i1.check_for_an_statefile()

    if not stateFileExists:
        i1.Params['runProtocol'] = 'initAN'
        i1.run_model()
    i1.Params['runProtocol'] = 'runANPSTH'
    i1.run_model()


def main():
    mx = [[1,12,13,40],
          [50,66,72,80],
          [9,10,22,33],
          [4,5,6,7]]
    printMatrix(mx)
    splitMxs = splitMatrix(mx)
    printMatrix((splitMxs)[0])
    printMatrix((splitMxs)[1])

# Imprime una matriz dada.
def printMatrix(mx):
    for n in mx:
        print(n)
    print("\n")

# Recibe una matriz.
def splitMatrix(mx):
    splitMx1 = [[],[],[],[]]
    splitMx2 = [[],[],[],[]]

    # Guarda los valores superiores de la diagonal en una matriz.
    for line in range(0,len(mx)):
        for col in range(0,len(mx)):
            if line <= col:
                splitMx1[line].append(mx[line][col])
            else:
                splitMx1[line].append(0)

    # Guarda los valores inferiores de la diagonal en una matriz.
    for line in range(0,len(mx)):
        for col in range(0,len(mx)):
            if line > col:
                splitMx2[line].append(mx[line][col])
            else:
                splitMx2[line].append(0)
    # Retorna las dos matrices creadas.
    return(splitMx1,splitMx2)

main()


import pickle
import os
import math

def parse_spe_txt_to_pikle(spe_txt_file, spe_path:str):
    print(f"parse spe {spe_txt_file} to pikle {spe_path}")
    results = []
    f = open(spe_txt_file, "r")
    for index, line in enumerate(f.readlines()):
        if not line.startswith("[BR]"):
            continue
        parsed_line = line.split("/")
        pc = int(parsed_line[1], 16)
        inst = parsed_line[2]
        event = parsed_line[3]
        target = int(parsed_line[4], 16)
        results.append((pc, inst, event, target))
    print(f"number of records of spe {spe_txt_file} is {len(results)}")

    chunk_count = 128
    chunk_size = math.ceil(len(results) / chunk_count)
    print(f"chunk len: {chunk_count*chunk_size}")

    for i in range(chunk_count):
        chunk = results[i * chunk_size : (i+1) * chunk_size]
        chunk_pkl_filename = os.path.join(spe_path, f"{i}.pkl")
        pickle.dump(chunk, open(chunk_pkl_filename, "wb"))

if __name__ == "__main__":
    spe_file = "/home/linwh/workspaces/light-jaco/spe/carts2-spe.txt"
    spe_path = "/home/linwh/workspaces/light-jaco/spe/carts2-spes2"
    if not os.path.exists(spe_path):
        os.mkdir(spe_path)
    parse_spe_txt_to_pikle(spe_file, spe_path)


class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class SingleLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    def append(self, data):
        # 建立 class Node 的instance(實體)
        new_node = ListNode(data)

        # 如果head == None 代表為第一個Node
        if self.head is None:
            self.head = new_node
            self.tail = new_node

        else:
            self.tail.next = new_node
            self.tail = self.tail.next

def delete_duplicates(head: ListNode) -> ListNode:
    cur = head
    while cur:
        print(cur.val)
        while cur.next and cur.next.val == cur.val:
            cur.next = cur.next.next
        cur = cur.next
    return head

if __name__ == '__main__':
    list_s = SingleLinkedList()
    for num in [1, 1, 2, 3, 3, 4]:
        list_s.append(num)

    delete_duplicates(list_s.head)
    print(list_s)

All Problems/83. Remove Duplicates from Sorted List.py


from load_input_files import load_input_files
from global_aux import input_datasets, charge_event_builder

class load_inputs_aux:

    def __init__(self, base_dir, start_simulation_unix_time):
        self.base_dir = base_dir
        self.start_simulation_unix_time = start_simulation_unix_time

        self.SE_CE_data_obj = None
        self.baseLD_data_obj = None
        self.global_parameters = None
        self.control_strategy_parameters_dict = None

    def load(self):
        inputs_dir = self.base_dir + '/inputs'

        load_obj = load_input_files(self.start_simulation_unix_time)
        self.L2_control_strategies_to_include = load_obj.get_L2_control_strategies_to_include()

        (is_successful, self.SE_CE_data_obj, self.baseLD_data_obj, self.global_parameters, self.control_strategy_parameters_dict) = load_obj.load(inputs_dir)

        if is_successful:
            SEid_to_SE_type = self.SE_CE_data_obj.SEid_to_SE_type
            SEid_to_SE_group = self.SE_CE_data_obj.SEid_to_SE_group
            self.charge_event_builder_obj = charge_event_builder(SEid_to_SE_type, SEid_to_SE_group, self.L2_control_strategies_to_include, self.control_strategy_parameters_dict)
        else:
            self.charge_event_builder_obj = None

        #-------------------------

        # Load custom inputs here
        # ...
        # ...

        return is_successful # If errors detected in input datasets return False otherwise return True

    def get_input_dataset(self, input_dataset_enum_list):
        return_val = {}

        for dataset_enum in input_dataset_enum_list:
            if dataset_enum == input_datasets.SE_CE_data_obj:
                return_val[dataset_enum] = self.SE_CE_data_obj

            elif dataset_enum == input_datasets.SE_group_configuration:
                return_val[dataset_enum] = self.SE_CE_data_obj.SE_group_configuration_list

            elif dataset_enum == input_datasets.SE_group_charge_event_data:
                return_val[dataset_enum] = self.SE_CE_data_obj.SE_group_charge_events

            elif dataset_enum == input_datasets.SEid_to_SE_type:
                return_val[dataset_enum] = self.SE_CE_data_obj.SEid_to_SE_type

            elif dataset_enum == input_datasets.SEid_to_SE_group:
                return_val[dataset_enum] = self.SE_CE_data_obj.SEid_to_SE_group

            elif dataset_enum == input_datasets.charge_event_builder:
                return_val[dataset_enum] = self.charge_event_builder_obj

            elif dataset_enum == input_datasets.Caldera_L2_ES_strategies:
                return_val[dataset_enum] = self.SE_CE_data_obj.ES_strategies

            elif dataset_enum == input_datasets.Caldera_L2_VS_strategies:
                return_val[dataset_enum] = self.SE_CE_data_obj.VS_strategies

            elif dataset_enum == input_datasets.external_strategies:
                return_val[dataset_enum] = self.SE_CE_data_obj.ext_strategies

            elif dataset_enum == input_datasets.all_caldera_node_names:
                return_val[dataset_enum] = self.SE_CE_data_obj.caldera_powerflow_nodes.all_caldera_node_names

            elif dataset_enum == input_datasets.HPSE_caldera_node_names:
                return_val[dataset_enum] = self.SE_CE_data_obj.caldera_powerflow_nodes.HPSE_caldera_node_names

            elif dataset_enum == input_datasets.baseLD_data_obj:
                return_val[dataset_enum] = self.baseLD_data_obj

            elif dataset_enum == input_datasets.Caldera_global_parameters:
                return_val[dataset_enum] = self.global_parameters

            elif dataset_enum == input_datasets.Caldera_control_strategy_parameters_dict:
                return_val[dataset_enum] = self.control_strategy_parameters_dict

            # Return custom inputs here
            # ...
            # ...

            else:
                raise ValueError('Invalid dataset_enum in load_inputs_aux:get_input_dataset.')

        return return_val