import math
import logging
from simulation.units import *
from simulation.profiles import MassProfileNFW, MassProfileSIE
from simulation.lensing_sim import LensingSim
from astropy.cosmology import Planck15
from astropy.convolution import convolve, Gaussian2DKernel
from autograd import make_jvp

# from tqdm import *

logger = logging.getLogger(__name__)

class LensingObservationWithSubhalos:
    def __init__(
        self,
        mag_zero=25.5,
        mag_iso=22.5,
        exposure=1610.0,
        fwhm_psf=0.18,
        pixel_size=0.1,
        n_xy=64,
        f_sub=0.05,
        beta=-1.9,
        m_min_calib=1e6 * M_s,
        m_max_sub_div_M_hst_calib=0.01,
        m_200_min_sub=1e7 * M_s,
        m_200_max_sub_div_M_hst=0.01,
        M_200_sigma_v_scatter=False,
        params_eval=None,
        calculate_joint_score=False,
        calculate_msub_derivatives=False,
        calculate_sub_residuals=False,
        draw_host_mass=True,
        draw_host_redshift=True,
        draw_alignment=True,
        roi_size=2.,
    ):
        """
        Class to simulation an observation strong lensing image, with substructure sprinkled in.

        :param mag_zero: Zero-point magnitude of observation
        :param mag_iso: Magnitude of isotropic sky brightness
        :param exposure: Exposure time of observation, in seconds (including gain)
        :param fwhm_psf: FWHM of Gaussian PSF, in arcsecs
        :param pixel_size: Pixel side size, in arcsecs
        :param n_xy: Number of pixels (along x and y) of observation

        :param f_sub: Fraction of total contained mass in substructure
        :param beta: Slope in the subhaalo mass function
        :param m_min_calib: Minimum mass above which subhalo mass fraction is `f_sub`
        :param m_max_sub_div_M_hst_calib: Maximum mass below which subhalo mass fraction is `f_sub`,
            in units of the host halo mass
        :param m_200_min_sub: Lowest mass of subhalos to draw
        :param m_200_max_sub_div_M_hst: Maximum mass of subhalos to draw, in units of host halo mass

        :param M_200_sigma_v_scatter: Whether to apply lognormal scatter in sigma_v to M_200_host mapping

        :param params_eval: Parameters (f_sub, beta) for which p(x,z|params) will be calculated
        :param calculate_joint_score: Whether grad_params log p(x,z|params) will be calculated
        :param calculate_msub_derivatives: Whether to calculate derivatives of image wrt subhalos masses
        :param calculate_residuals: Whether to calculate residual images wrt subhalos
        """

        # beta = -2.0 is forbidden!
        if np.abs((beta + 2.)) < 1.e-3:
            beta = -2.001

        # Store input
        self.mag_zero = mag_zero
        self.mag_iso = mag_iso
        self.exposure = exposure
        self.fwhm_psf = fwhm_psf
        self.pixel_size = pixel_size
        self.n_xy = n_xy
        self.f_sub = f_sub
        self.beta = beta
        self.m_min_calib = m_min_calib
        self.m_max_sub_div_M_hst_calib = m_max_sub_div_M_hst_calib
        self.m_200_min_sub = m_200_min_sub
        self.m_200_max_sub_div_M_hst = m_200_max_sub_div_M_hst
        self.M_200_sigma_v_scatter = M_200_sigma_v_scatter
        self.params_eval = params_eval
        self.calculate_joint_score = calculate_joint_score
        self.draw_host_mass = draw_host_mass
        self.draw_host_redshift = draw_host_redshift
        self.draw_alignment = draw_alignment

        self.coordinate_limit = pixel_size * n_xy / 2.0

        # Draw lens properties consistent with Collett et al [1507.02657]

        # Clip lens redshift `z_l` to be less than 1; high-redshift lenses no good for our purposes!

        if self.draw_host_redshift:
            self.z_l = 2.0
            while self.z_l > 1.0:
                self.z_l = 10 ** np.random.normal(-0.25, 0.25)
        else:
            self.z_l = 10.0 ** -0.25

        if self.draw_host_mass:
            self.sigma_v = np.random.normal(225, 50)
        else:
            self.sigma_v = 225.0

        if self.draw_alignment:
            self.theta_x_0 = np.random.normal(0, 0.2)
            self.theta_y_0 = np.random.normal(0, 0.2)
        else:
            self.theta_x_0 = 0.0
            self.theta_y_0 = 0.0

        q = 1  # For now, hard-code host to be spherical

        # Fix the source properties to reasonable mean-ish values
        self.theta_s_e = 0.2
        self.z_s = 1.5
        self.mag_s = 23.0

        # Get relevant distances
        self.D_l = Planck15.angular_diameter_distance(z=self.z_l).value * Mpc
        D_s = Planck15.angular_diameter_distance(z=self.z_s).value * Mpc
        D_ls = Planck15.angular_diameter_distance_z1z2(z1=self.z_l, z2=self.z_s).value * Mpc

        # Get properties for NFW host DM halo
        if draw_host_mass:
            self.M_200_hst = self.M_200_sigma_v(self.sigma_v * Kmps, scatter=M_200_sigma_v_scatter)
        else:
            self.M_200_hst = self.M_200_sigma_v(self.sigma_v * Kmps, scatter=0.0)

        c_200_hst = MassProfileNFW.c_200_SCP(self.M_200_hst)
        r_s_hst, rho_s_hst = MassProfileNFW.get_r_s_rho_s_NFW(self.M_200_hst, c_200_hst)

        # Get properties for SIE host
        self.theta_E = MassProfileSIE.theta_E(self.sigma_v * Kmps, D_ls, D_s)

        # Don't consider configuration with subhalo fraction > 1!
        self.f_sub_realiz = 2.0
        while self.f_sub_realiz > 1.0:

            # Generate a subhalo population...
            ps = SubhaloPopulation(
                f_sub=f_sub,
                beta=beta,
                M_hst=self.M_200_hst,
                c_hst=c_200_hst,
                m_min=m_200_min_sub,
                m_max=m_200_max_sub_div_M_hst * self.M_200_hst,
                m_min_calib=m_min_calib,
                m_max_calib=m_max_sub_div_M_hst_calib * self.M_200_hst,
                theta_s=r_s_hst / self.D_l,
                theta_roi=roi_size * self.theta_E,
                theta_E=self.theta_E,
                params_eval=params_eval,
                calculate_joint_score=calculate_joint_score,
            )

            # ... and grab its properties
            self.m_subs = ps.m_sample
            self.n_sub_roi = ps.n_sub_roi
            self.theta_xs = ps.theta_x_sample
            self.theta_ys = ps.theta_y_sample
            self.f_sub_realiz = ps.f_sub_realiz
            self.n_sub_in_ring = ps.n_sub_in_ring
            self.f_sub_in_ring = ps.f_sub_in_ring
            self.n_sub_near_ring = ps.n_sub_near_ring
            self.f_sub_near_ring = ps.f_sub_near_ring

        # Convert magnitude for source and isotropic component to expected counts
        self.S_tot = self._mag_to_flux(self.mag_s, self.mag_zero)
        self.f_iso = self._mag_to_flux(self.mag_iso, self.mag_zero)

        # Set host properties. Host assumed to be at the center of the image.
        self.hst_param_dict = {"profile": "SIE", "theta_x_0": 0.0, "theta_y_0": 0.0, "theta_E": self.theta_E, "q": q}

        lens_list = [self.hst_param_dict]

        # Set subhalo properties

        for i_sub, (m, theta_x, theta_y) in enumerate(zip(self.m_subs, self.theta_xs, self.theta_ys)):
            c = MassProfileNFW.c_200_SCP(m)
            r_s, rho_s = MassProfileNFW.get_r_s_rho_s_NFW(m, c)
            sub_param_dict = {"profile": "NFW", "theta_x_0": theta_x, "theta_y_0": theta_y, "M_200": m, "r_s": r_s, "rho_s": rho_s}
            lens_list.append(sub_param_dict)

        # Set source properties
        src_param_dict = {"profile": "Sersic", "theta_x_0": self.theta_x_0, "theta_y_0": self.theta_y_0, "S_tot": self.S_tot, "theta_e": self.theta_s_e, "n_srsc": 1}

        # Set observation and global properties
        observation_dict = {
            "n_x": n_xy,
            "n_y": n_xy,
            "theta_x_lims": (-self.coordinate_limit, self.coordinate_limit),
            "theta_y_lims": (-self.coordinate_limit, self.coordinate_limit),
            "exposure": exposure,
            "f_iso": self.f_iso,
        }

        global_dict = {"z_s": self.z_s, "z_l": self.z_l}

        # Inititalize lensing class and produce lensed image
        lsi = LensingSim(lens_list, [src_param_dict], global_dict, observation_dict)

        self.image = lsi.lensed_image()
        self.image_poiss = np.random.poisson(self.image)  # Poisson fluctuate
        self.image_poiss_psf = self._convolve_psf(self.image_poiss, fwhm_psf, pixel_size)  # Convolve with PSF

        # Augmented data
        self.joint_log_probs = ps.joint_log_probs
        self.joint_scores = ps.joint_scores

        # Optionally, compute derivatives of image wrt each subahlo mass (takes ~1s/subhalo)
        if calculate_msub_derivatives:
            self._calculate_derivs()

        # Optionally, compute derivatives of image wrt each subahlo mass (takes ~1s/subhalo)
        if calculate_sub_residuals:
            self._calculate_residuals()

    def _calculate_residuals(self):
        """
        Compute residual images wrt each subhalo
        """

        # Recompute base image (mostly for debugging, but doesn't take much time)
        self.image_0 = self._deriv_helper_function(self.m_subs)

        # Initialize subhalo properties
        self.theta_xs_0 = self.theta_xs
        self.theta_ys_0 = self.theta_ys
        self.m_subs_0 = self.m_subs

        # Residual image array
        self.resid_sub_image = np.zeros((self.n_sub_roi, self.n_xy, self.n_xy))

        # Loop over subhalos, deleting corresponding subhalo properties andc computing residual image
        for i_sub in range(self.n_sub_roi):
            self.theta_xs = np.delete(self.theta_xs_0, i_sub)
            self.theta_ys = np.delete(self.theta_ys_0, i_sub)
            self.m_subs = np.delete(self.m_subs_0, i_sub)
            self.resid_sub_image[i_sub] = self.image_0 - self._deriv_helper_function(self.m_subs)

        # Reset subhalo properties to the original ones
        self.theta_xs = self.theta_xs_0
        self.theta_ys = self.theta_ys_0
        self.m_subs = self.m_subs_0

    def _calculate_derivs(self):
        """
        Compute derivatives of the lensing image wrt the mass of each subhalo, evaluated at the given mass
        """

        # Initialize subhalo properties
        self.theta_xs_0 = self.theta_xs
        self.theta_ys_0 = self.theta_ys
        self.m_subs_0 = self.m_subs

        # Gradient image array
        self.grad_msub_image = np.zeros((self.n_sub_roi, self.n_xy, self.n_xy))

        # Loop over subhalos, setting the first element of subhalo properties arrays to a given subhalo,
        # then compute Jacobian (hacky, but seems to be fastest way currently with forward-pass Jacobian
        # vector product implementation in autograd...
        for i_sub in range(self.n_sub_roi):
            self.theta_xs[[0, i_sub]] = self.theta_xs[[i_sub, 0]]
            self.theta_ys[[0, i_sub]] = self.theta_ys[[i_sub, 0]]
            self.m_subs[[0, i_sub]] = self.m_subs[[i_sub, 0]]

            self.grad_msub_image[i_sub] = make_jvp(self._deriv_helper_function)(self.m_subs)([1.])[1]

        # Reset subhalo properties to the original ones
        self.theta_xs = self.theta_xs_0
        self.theta_ys = self.theta_ys_0
        self.m_subs = self.m_subs_0

    def _deriv_helper_function(self, m_subs):
        """
        Helper function for autograd to compute gradients and residuals
        """

        lens_list = [self.hst_param_dict]

        # Set subhalo properties
        for m, theta_x, theta_y in zip(m_subs, self.theta_xs, self.theta_ys):
            c = MassProfileNFW.c_200_SCP(m)
            r_s, rho_s = MassProfileNFW.get_r_s_rho_s_NFW(m, c)
            sub_param_dict = {"profile": "NFW", "theta_x_0": theta_x, "theta_y_0": theta_y, "M_200": m, "r_s": r_s, "rho_s": rho_s}
            lens_list.append(sub_param_dict)

        # Set source properties
        src_param_dict = {"profile": "Sersic", "theta_x_0": self.theta_x_0, "theta_y_0": self.theta_y_0, "S_tot": self.S_tot, "theta_e": self.theta_s_e, "n_srsc": 1}

        # Set observation and global properties
        observation_dict = {
            "n_x": self.n_xy,
            "n_y": self.n_xy,
            "theta_x_lims": (-self.coordinate_limit, self.coordinate_limit),
            "theta_y_lims": (-self.coordinate_limit, self.coordinate_limit),
            "exposure": self.exposure,
            "f_iso": self.f_iso,
        }

        global_dict = {"z_s": self.z_s, "z_l": self.z_l}

        # Inititalize lensing class and produce lensed image
        lsi = LensingSim(lens_list, [src_param_dict], global_dict, observation_dict)

        return lsi.lensed_image()

    def _convolve_psf(self, image, fwhm_psf=0.18, pixel_size=0.1):
        """
        Convolve input map of pixel_size with Gaussian PSF of with FWHM `fwhm_psf`
        """
        sigma_psf = fwhm_psf / 2 ** 1.5 * np.sqrt(np.log(2))  # Convert FWHM to standard deviation
        kernel = Gaussian2DKernel(x_stddev=1.0 * sigma_psf / pixel_size)

        return convolve(image, kernel)

    def _mag_to_flux(self, mag, mag_zp):
        """
        Returns total flux of the integrated profile corresponding to magnitude `mag`, in ADU relative to `mag_zp`
        """
        return 10 ** (-0.4 * (mag - mag_zp))

    @classmethod
    def M_200_sigma_v(cls, sigma_v, scatter=False):
        """
        Relate central velocity dispersion to halo virial mass
        From https://arxiv.org/pdf/1804.04492.pdf
        """
        a = 0.09
        b = 3.48
        if scatter:
            sigma_log10_M_200 = 0.13  # Lognormal scatter
            log10_M_200 = np.random.normal(a + b * np.log10(sigma_v / (100 * Kmps)), sigma_log10_M_200)
        else:
            log10_M_200 = a + b * np.log10(sigma_v / (100 * Kmps))
        return (10 ** log10_M_200) * 1e12 * M_s

class SubhaloPopulation:
    def __init__(
        self,
        f_sub=0.15,
        beta=-1.9,
        m_min=1e7 * M_s,
        m_max=1e11 * M_s,
        m_min_calib=1e7 * M_s,
        m_max_calib=1e11 * M_s,
        theta_roi=2.5,
        M_hst=1e14 * M_s,
        theta_s=1e-4,
        c_hst=6.0,
        theta_E=1.0,
        params_eval=None,
        calculate_joint_score=False,
    ):
        """
        Calibrate number of subhalos and generate a mass sample within lensing ROI

        SHMF is assumed to have the form
        dn/dm =  M_hst/M_0 * alpha * (m / m_0) ^ beta
        with M_0 = M_MW and m_0 = 1e9 * M_s. Note that this is slightly different
        from what's in the draft at the moment.

        :param f_sub: Fraction of mass contained in substructure
        :param beta: Slope of subhalo mass function
        :param m_min: Minimum mass of subhalos
        :param m_max: Maximum mass of subhalos
        :param theta_roi: Radius of lensing ROI, in arcsecs
        :param M_hst: Host halo mass
        :param theta_s: Angular scale radius of host halo, in rad
        :param c_hst: Concentration parameter of host halo
        :param params_eval: Parameters (f_sub, beta) for which p(x,z|params) will be calculated
        :param calculate_joint_score: Whether grad_params log p(x,z|params) will be calculated
        """

        # Store settings
        self.f_sub = f_sub
        self.beta = beta
        self.m_min = m_min
        self.m_max = m_max
        self.m_min_calib = m_min_calib
        self.m_max_calib = m_max_calib
        self.theta_roi = theta_roi
        self.M_hst = M_hst
        self.theta_s = theta_s
        self.theta_E = theta_E
        self.c_hst = c_hst

        # Alpha corresponding to calibration configuration
        self.alpha = self._alpha_f_sub(f_sub, beta, m_min_calib, m_max_calib)

        # Total expected number of subhalos within virial radius of host halo
        self.n_sub_tot = self._n_sub(m_min, m_max, M_hst, self.alpha, self.beta)

        # Fraction and number of subhalos within lensing region of interest specified by theta_roi
        self.f_sub_roi = max(MassProfileNFW.M_cyl_div_M0(self.theta_roi * asctorad / theta_s), 0.0)
        self.n_sub_roi = np.random.poisson(self.f_sub_roi * self.n_sub_tot)
        logger.debug("%s subhalos (%s expected)", self.n_sub_roi, self.f_sub_roi * self.n_sub_tot)

        # Sample of subhalo masses drawn from subhalo mass function
        self.m_sample = self._draw_m_sub(self.n_sub_roi, self.m_min, self.m_max, self.beta)

        # Fraction of halo mass in subhalos, for diagnostic purposes
        self.M_hst_roi = M_hst * MassProfileNFW.M_cyl_div_M0(self.theta_roi * asctorad / self.theta_s)
        self.f_sub_realiz = np.sum(self.m_sample) / self.M_hst_roi
        logger.debug("%s substructure fraction (%s expected)", self.f_sub_realiz, self.f_sub)

        # Sample subhalo positions uniformly within ROI
        self.theta_x_sample, self.theta_y_sample = self._draw_sub_coordinates(self.n_sub_roi, r_max=self.theta_roi)

        # For debugging: subhalos within Einstein ring and near it
        self.n_sub_in_ring, self.f_sub_in_ring = self._count_subhalos_in_radius_range(0.0, 0.9 * self.theta_E)
        self.n_sub_near_ring, self.f_sub_near_ring = self._count_subhalos_in_radius_range(0.9 * self.theta_E, 1.1 * self.theta_E)

        # Calculate augmented data
        self.joint_log_probs = self._calculate_joint_log_probs(params_eval)
        if calculate_joint_score:
            self.joint_scores = self._calculate_joint_scores(params_eval[:2, :])
        else:
            self.joint_scores = None

    @staticmethod
    def _alpha_calib(m_min_calib, m_max_calib, n_calib, M_calib, beta, M_0=M_MW, m_0=1e9 * M_s):
        """
        Get normalization alpha corresponding calibration configuration specified by {n_calib, beta}
        """
        alpha = (n_calib * (-1 - beta) * M_0 / M_calib * m_0 ** beta) / (-m_max_calib ** (1.0 + beta) + m_min_calib ** (1.0 + beta))
        return alpha

    @staticmethod
    def _alpha_f_sub(f_sub, beta, m_min, m_max, M_0=M_MW, m_0=1e9 * M_s):
        """
        Get normalization alpha corresponding calibration configuration specified by {f_sub, beta}
        """
        alpha = f_sub * ((2 + beta) * M_0 * m_0 ** beta) / (m_max ** (beta + 2) - m_min ** (beta + 2))
        return alpha

    @staticmethod
    def _m_in_sub(M_hst, alpha, beta, m_min, m_max, M_0=M_MW, m_0=1e9 * M_s):
        return M_hst * alpha * (m_max ** (beta + 2) - m_min ** (beta + 2)) / ((2 + beta) * M_0 * m_0 ** beta)

    @staticmethod
    def _n_sub(m_min, m_max, M, alpha, beta, M_0=M_MW, m_0=1e9 * M_s):
        """
        Get (expected) number of subhalos between m_min, m_max
        """

        # n_sub = alpha * M * (m_max * m_min / m_0) ** beta * (m_max ** -beta * m_min - m_max * m_min ** -beta)
        # / (M_0 * (-1 + -beta))
        # ... overflows sometimes

        n_sub = alpha / (-beta - 1.0) * m_0 * M / M_0
        n_sub *= (m_min / m_0) ** (beta + 1.0) - (m_max / m_0) ** (beta + 1.0)
        return max(n_sub, 0.0)

    @staticmethod
    def _draw_m_sub(n_sub, m_sub_min, m_sub_max, beta):
        """
        Draw subhalo masses from SHMF with slope `beta` and min/max masses `m_sub_min` and `m_sub_max` . Stolen from:
        https://stackoverflow.com/questions/31114330/python-generating-random-numbers-from-a-power-law-distribution
        """
        u = np.random.uniform(0, 1, size=n_sub)
        m_low_u, m_high_u = m_sub_min ** (beta + 1), m_sub_max ** (beta + 1)
        return (m_low_u + (m_high_u - m_low_u) * u) ** (1.0 / (beta + 1.0))

    @staticmethod
    def _draw_sub_coordinates(n_sub, r_min=0.0, r_max=2.5):
        """
        Draw subhalo n_sub coordinates uniformly within a ring r_min < r < r_max
        """
        x_sub = []
        y_sub = []
        while len(x_sub) < n_sub:
            x_candidates = np.random.uniform(low=-r_max, high=r_max, size=n_sub - len(x_sub))
            y_candidates = np.random.uniform(low=-r_max, high=r_max, size=n_sub - len(x_sub))
            r2 = x_candidates ** 2 + y_candidates ** 2
            good = (r2 <= r_max ** 2) * (r2 >= r_min ** 2)
            x_sub += list(x_candidates[good])
            y_sub += list(y_candidates[good])

        return np.array(x_sub), np.array(y_sub)

    def _count_subhalos_in_radius_range(self, min_r, max_r):
        r = ((self.theta_x_sample ** 2 + self.theta_y_sample ** 2) ** 0.5).flatten()
        filter = (r >= min_r) * (r < max_r)
        n_sub = np.sum(filter.astype(np.int))
        m_sub = np.sum(self.m_sample[filter])
        f_sub = m_sub / self.M_hst_roi
        return n_sub, f_sub

    def _calculate_joint_log_probs(self, params_eval):
        """
        Calculates log p(self.n_sub_roi, self.m_sample | f_sub, beta)
        """
        if params_eval is None:
            params_eval = []

        log_probs = [0.0 for _ in params_eval]

        for i_eval, (f_sub, beta) in enumerate(params_eval):
            # Poisson term
            log_probs[i_eval] += self._log_p_n_sub(self.n_sub_roi, f_sub, beta)

            # Power law for subhalo masses
            for m_sub in self.m_sample:
                log_probs[i_eval] += self._log_p_m_sub(m_sub, beta)

        return np.array(log_probs)

    def _calculate_joint_scores(self, params, eps0=1.0e-5, eps1=1.0e-3):
        """
        Calculates grad_(f_sub, beta) log p(self.n_sub_roi, self.m_sample | f_sub, beta)
        """
        joint_scores = []

        for param in params:
            eps_vec0 = np.asarray(param).flatten() + np.array([eps0, 0.0]).reshape(1, 2)
            eps_vec1 = np.asarray(param).flatten() + np.array([0.0, eps1]).reshape(1, 2)
            param = np.asarray(param).reshape(1, 2)
            all_params = np.vstack([param, eps_vec0, eps_vec1])
            log_probs = self._calculate_joint_log_probs(all_params)

            score0 = (log_probs[1] - log_probs[0]) / eps0
            score1 = (log_probs[2] - log_probs[0]) / eps1

            joint_scores.append([score0, score1])

        return np.array(joint_scores)

    def _log_p_n_sub(self, n_sub, f_sub, beta, include_constant=False, eps=1.0e-6):
        """
        Calculates log p(n_sub | f_sub, beta)
        """
        alpha = self._alpha_f_sub(f_sub, beta, self.m_min_calib, self.m_max_calib)
        expected_n_sub = self.f_sub_roi * self._n_sub(self.m_min, self.m_max, self.M_hst, alpha, beta)
        if expected_n_sub <= eps:
            logger.warning("Expected number of subs in RoI for f_sub = %s and beta = %s is %s, setting to %s", f_sub, beta, expected_n_sub, eps)
            expected_n_sub = eps

        log_p_poisson = n_sub * np.log(expected_n_sub) - expected_n_sub
        if include_constant:
            log_p_poisson = log_p_poisson - np.log(math.factorial(n_sub))
        return log_p_poisson

    def _log_p_m_sub(self, m, beta, m_0=1e9 * M_s):
        """
        Calculates log p(m_i | beta)
        """
        if m < self.m_min or m > self.m_max:
            logger.warning("Calculating probability for subhalo mass out of bounds -- this should not happen")
            return 0.0

        log_p = beta * np.log(m / m_0)
        log_p += np.log(-beta - 1.0)
        log_p -= np.log(m_0)
        log_p -= np.log((self.m_min / m_0) ** (beta + 1.0) - (self.m_max / m_0) ** (beta + 1.0))

        if not np.isfinite(log_p):
            logger.warning("Infinite log p(m_sub | beta):")
            logger.warning("  m       = %s", m)
            logger.warning("  beta    = %s", beta)
            logger.warning("  m_0     = %s", m_0)
            logger.warning("  m_min   = %s", self.m_min)
            logger.warning("  m_max   = %s", self.m_max)
            logger.warning("  term 1  = %s", beta * np.log(m / m_0))
            logger.warning("  term 2  = %s", np.log(-beta - 1.0))
            logger.warning("  term 3  = %s", -np.log(m_0))
            logger.warning("  term 4  = %s", -np.log((self.m_min / m_0) ** (beta + 1.0) - (self.m_max / m_0) ** (beta + 1.0)))
            logger.warning("  term 4a = %s", (self.m_min / m_0) ** (beta + 1.0))
            logger.warning("  term 4b = %s", -(self.m_max / m_0) ** (beta + 1.0))

        return log_p


import argparse
from os import path
import warnings
import glob
import os

from PIL import Image, ImageDraw
from tqdm import tqdm

from segmentation.model_builder import ModelBuilderLoad
from segmentation.network import EnsemblePredictor
from segmentation.network_postprocessor import NetworkMaskPostProcessor, NetworkBaselinePostProcessor
from segmentation.preprocessing.source_image import SourceImage
from segmentation.scripts.train import get_default_device

warnings.simplefilter(action='ignore', category=FutureWarning)
import itertools
import numpy as np

def dir_path(string):
    if path.isdir(string):
        return string
    else:
        raise NotADirectoryError(string)

def scale_baselines(baselines, scale_factor=1.0):
    if baselines is not None:
        for b_idx, bline in enumerate(baselines):
            for c_idx, coord in enumerate(bline):
                coord = (int(coord[0] * scale_factor), int(coord[1] * scale_factor))
                baselines[b_idx][c_idx] = coord

class Ensemble:
    def __init__(self, models):
        self.models = models

    def __call__(self, x, scale_area, additional_scale_factor=None):
        res = []
        scale_factor = None
        for m in self.models:
            p_map, s_factor = m.predict_single_image_by_path(x, rgb=True, preprocessing=True, scale_area=scale_area,
                                                             additional_scale_factor=additional_scale_factor)
            scale_factor = s_factor
            res.append(p_map)
        if len(res) == 1:
            return res[0], scale_factor
        res = np.stack(res, axis=0)
        return np.mean(res, axis=0), scale_factor

def main():
    colors = [(255, 0, 0),
              (0, 255, 0),
              (0, 0, 255),
              (255, 255, 0),
              (0, 255, 255),
              (255, 0, 255)]
    parser = argparse.ArgumentParser()
    parser.add_argument("--load", type=str, nargs="*", default=[],
                        help="load models and use it for inference (specify .torch file)")
    parser.add_argument("--image_path", type=str, nargs="*", default=[], help="load models and use it for inference")
    parser.add_argument("--scale_area", action="store_true", help="Enable image scaling")
    parser.add_argument("--scale_area_size", type=int, help="max pixel amount of an image")

    parser.add_argument("--output_xml", action="store_true", help="Outputs Xml Files")
    parser.add_argument("--output_xml_path", type=str, default=None, help="Directory of the XML output")
    parser.add_argument("--mode", choices=["xml_baseline", "xml_region", "mask"], required=True)

    parser.add_argument("--max_line_height", type=int, default=None,
                        help="If the average line_height of an document is bigger then the specified value, "
                             "the document is scaled down an processed again on the new resolution. "
                             "Proposed Value == 22")
    parser.add_argument("--min_line_height", type=int, default=None,
                        help="If the average line_height of an document is smaller then the specified value, "
                             "the document is scaled up an processed again on the new resolution")
    parser.add_argument("-d", "--device", type=str, default=get_default_device())
    parser.add_argument("--tta", action="store_true", help="Use predefined Tta-pipeline")
    parser.add_argument("--dewarp", action="store_true", help="Dewarp image using the detected baselines")
    parser.add_argument("--show_result", action="store_true")
    parser.add_argument("--output_path_debug_images", type=str, default=None, help="Directory of the debug images")

    parser.add_argument("--processes", type=int, default=8)

    args = parser.parse_args()
    image_list = sorted(itertools.chain.from_iterable([glob.glob(x) for x in args.image_path]))
    base_model_files = [ModelBuilderLoad.from_disk(model_weights=i, device=args.device) for i in args.load]
    base_models = [i.get_model() for i in base_model_files]
    base_configs = [i.get_model_configuration() for i in base_model_files]
    preprocessing_settings = [i.get_model_configuration().preprocessing_settings for i in base_model_files]
    if args.scale_area:
        for i in preprocessing_settings:
            i.scale_max_area = args.scale_area_size
            i.scale_predict = args.scale_area

    predictor = EnsemblePredictor(base_models, preprocessing_settings)
    config = base_configs[0]

    if args.mode == "mask" or args.mode == "xml_region":
        nmaskpred = NetworkMaskPostProcessor(predictor, config.color_map)
        for img_path in tqdm(image_list):
            simg = SourceImage.load(img_path)
            mask = nmaskpred.predict_image(simg)
            if args.show_result:
                mask.generated_mask.show()
            if args.output_path_debug_images:
                basename = "debug_" + os.path.basename(img_path)
                file_path = os.path.join(args.output_path_debug_images, basename)
                mask.generated_mask.save(file_path)

    if args.mode == "xml_baseline":
        nbaselinepred = NetworkBaselinePostProcessor(predictor, config.color_map)
        for img_path in tqdm(image_list):
            simg = SourceImage.load(img_path)
            simg.pil_image = simg.pil_image.convert('RGB')
            draw = ImageDraw.Draw(simg.pil_image)

            result = nbaselinepred.predict_image(simg)
            for ind, x in enumerate(result.base_lines):

                t = list(itertools.chain.from_iterable(x))
                a = t[::]
                if args.show_result:
                    draw.line(a, fill=colors[ind % len(colors)], width=4)
            if args.output_path_debug_images:
                basename = "debug_" + os.path.basename(img_path)
                file_path = os.path.join(args.output_path_debug_images, basename)
                simg.pil_image.save(file_path)

            if args.output_xml and args.output_xml_path is not None:
                from segmentation.gui.xml_util import TextRegion, BaseLine, TextLine, XMLGenerator
                regions = []
                if result.base_lines is not None:
                    text_lines = []
                    for b_line in result.base_lines:
                        text_lines.append(TextLine(coords=b_line + list(reversed(b_line)), baseline=BaseLine(b_line)))
                    regions.append(TextRegion(text_lines, coords=b_line + list(reversed(b_line))))

                xml_gen = XMLGenerator(simg.get_width(),simg.get_height(), os.path.basename(img_path), regions=regions)
                xml_gen.save_textregions_as_xml(args.output_xml_path)

if __name__ == "__main__":
    main()


#===== Блюпринт loader view =====#

from flask import Blueprint, render_template, request
from loader.utils import save_picture, add_new_post
from exceptions import WrongImgType
import logging

# Подключаем блюпринт для loader view
loader_blueprint = Blueprint("loader_blueprint", __name__, template_folder="templates")

# Включаем логирование
logging.basicConfig(filename="logger.log", level=logging.INFO, encoding="utf-8")

#===== Основные методы блюпринта =====#

@loader_blueprint.route("/post", methods=["GET"])
def create_new_post_page():
    """ Метод для отображения формы с созданием поста"""
    return render_template("post_form.html")

@loader_blueprint.route("/post", methods=["POST"])
def create_new_post_by_user():
    """ Метод на создание нового поста """
    # Запросы на данные получаемые в форме
    picture = request.files.get("picture")
    content = request.form.get("content")
    # Проверка контента на наличие данных
    if not picture or not content:
        logging.info("Данные не загружены, отсутствует часть данных")
        return "Отсутствует часть данных"
    # Сохранение изображения и проверка на формат файла
    try:
        picture_path = save_picture(picture)
    except WrongImgType:
        return "Неверный формат изображения"
    # Создание нового поста
    new_post = add_new_post(picture_path, content)
    # Генерируем страничку с результатами загрузки поста
    logging.info("Добавлен новый пост")
    return render_template("post_uploaded.html", new_post=new_post)


import os.path
import os
import shutil
import time
from pathlib import Path
import subprocess as sub

#file path windows
class path_window:
    def __init__(self):
        layout = [
            [sg.Push(), sg.Text("CTF INSTALLER", font="Arial 16 bold", text_color="#000000"), sg.Push()],
            [sg.VPush()],
            [sg.Text("select your desktop file: ", font="Arial 12"), sg.FolderBrowse(initial_folder=HOME, key="-DESKTOP-")],
            [sg.Push(), sg.Button("Submit", key="-SUBMIT-"), sg.Push()]
        ]

        self.window = sg.Window("CTF INSTALLER").layout(layout)

    def init(self):
        while True:
            event, value = self.window.read()

            if event == sg.WIN_CLOSED:
                exit()
            elif event == "-SUBMIT-":
                self.window.close()
                return value["-DESKTOP-"]

import PySimpleGUI as sg

#track the executable file
HOME = str(Path.home())
HERE = os.getcwd()
filename = ".AUTOCTF"

def FindTheFlag():
    if os.path.exists(HERE+"/"+filename):
        return True
    else:
        return False

#run a first time
def TouchTheFlag():
    pathwindow = path_window()
    desktop = pathwindow.init()
    if os.path.exists(desktop):
        pass
    else:
        raise FileExistsError

    shutil.move(HERE+"/"+filename, desktop)
    return desktop

def CaptureTheFlagOunce(DESKTOP:str):
    print(DESKTOP)
    sub.run([f"{DESKTOP}/{filename}"])
    if not os.path.exists(DESKTOP+"/..tags.ini"):
        if os.path.exists("..tags.ini"): #in INSTALLER FOLDER
            shutil.move("..tags.ini", DESKTOP)
    sg.popup("Now, for you to automate the process, read the instructions in the README.md file.\n\n Thank you for the patience!", font="Arial 16")

#make sure the files were created sucessfully
#create an standar GUI for the user to decide what will be the basics folders

if __name__ == "__main__":
    FlagFound = FindTheFlag()
    if FlagFound:
        DESKTOP = TouchTheFlag()
        CaptureTheFlagOunce(DESKTOP)


from pytube import YouTube

url = input("Enter Link: ")
youtube_1 = YouTube(url)

video = youtube_1.streams.all()
vid = list(enumerate(video))

for i in vid:
    print(i)
print()
stream = int(input("Enter ur selected quality: "))
print(f"Downloading Video: {youtube_1.title}")
video[stream].download()
print("Done!")

# *****FOR PLAYLISTS(144P)*****
# from pytube import Playlist
#
# url = input("Enter Link: ")
# py = Playlist(url)
# print(f"Downloading: {py.title}")
# for video in py.videos:
#     video.streams.first().download()
# print("Done!")


from django.shortcuts import render, render_to_response
from blog.models import Article
from markdown2 import markdown

# Create your views here.
def blog_main(request):
    blog_list = Article.objects.filter(author=1).order_by('-timestamp')
    # 将markdown内容转化为html格式
    for i in blog_list:
        i.content = markdown(i.content, extras=['fenced-code-blocks'], )

    c = {
        'blog_list': blog_list,
    }
    return render_to_response('blog.html', c)

def blog_article(request):
    article_num = request.GET['id']
    article = Article.objects.get(id=article_num)
    # 将markdown内容转化为html格式
    article.content = markdown(article.content, extras=['fenced-code-blocks'], )

    c = {
        'article': article,
    }
    return render_to_response('article.html', c)


from collections import defaultdict
import re
import joblib

_SMALLEST = 10e32

def argmin(x: list, minval=_SMALLEST):
    """Find the smallest index from a list"""
    idx = None
    for i, xi in enumerate(x):
        if xi < minval:
            minval = xi
            idx = i
    return idx

def read_file(filename):
    """Read the input file, nothing fancy."""
    with open(filename, "r", encoding="utf-8") as f:
        commands = []
        while True:
            n = f.readline()
            if len(n) > 0:
                commands.append(n.strip("\n"))
            else:
                break
    return commands

class File:
    def __init__(
        self,
        name: str,
        id: int,
        parent: str = None,
        type: str = "file",
        size: int = 0,
        is_dir: bool = None,
        depth: int = None,
        n_sib: int = None,
    ) -> None:
        """A simple class that holds the several attributes of each file or
        directory.

        Parameters
        ----------
        name : str
            Name of the item.
        id : int
            Identifier for the item, used sometimes to check absolute distance.
        parent : str, optional
            Parent name of the item, by default None
        type : str, optional
            Indicates the type of the item, can be "file" for a file or
            "dir" for a folder, by default "file"
        size : int, optional
            Size of the item, by default 0
        is_dir : bool, optional
            Indicates if the item is a folder, by default None
        depth : int, optional
            Indicates at what depth from the root directory "/" the item is
            located, by default None
        n_sib : int, optional
            Number of siblings (items in the same folder includng self),
            by default None
        """

        self.name = name
        self.id = id
        self.type = type
        self.size = size
        self.is_dir = is_dir
        self.parent = parent
        self.depth = depth
        self.n_sib = n_sib

    def __iter__(self):
        return iter([self])

    def __repr__(self) -> str:
        return f"File(id={self.id} p={self.parent}, n={self.name}, t={self.type}, s={self.size}, d={self.depth}, sib={self.n_sib})"
        # return f"{self.parent}/{self.name}"

    def __str__(self) -> str:
        return self.__repr__()

class System:
    """
    Class that reads the input file and parses the commands, files, folders,
    subdirectories and their attributes into a plain list of File() objects.
    """

    def __init__(self, console_log: list, commands: list = ["$ cd", "$ ls"]) -> None:
        """Initialize the System() class.

        Parameters
        ----------
        console_log : list
            Whole input from the AOC2022/7 input file
        commands : list, optional
            Commands accepted by the System, by default ["$ cd", "$ ls"]
        """
        self.commands = commands
        self.console_log = console_log
        self.log = defaultdict(lambda: [])
        self.counter = 0
        self.depth = 0
        self._prev_dirs = []
        self.n_items = 0
        self.id = 1

    def run(self, command):
        """Run each command iteratively.

        Separates the command from the arguments.

        Parameters
        ----------
        command : str
            Command as a string.
        """
        command_arg = None
        operation = command[:4]
        command_arg = command[5:]
        self.counter += 1
        if operation.startswith("$ cd"):
            if not command_arg == "..":
                # store history of directories to id parents
                self._prev_dirs.append(command_arg)
            self.cd(command_arg)

        elif operation.startswith("$ ls"):
            self.ls(command_arg)
        else:
            # print(f"skipping non-commands {command}")
            pass

    def cd(self, arg):
        print(f"executing cd {arg}")
        self.curr_path = arg
        # add/sub depth with each call to cd()
        self.depth += -1 if arg == ".." else 1

    def ls(self, arg):
        print(f"executing ls {arg}")
        self.capture_out()
        self.parse_out()

    def capture_out(self):
        """
        Capture the output from the console_log.

        Using the counter as a slicer variable, read all of the
        console_log up until the next comamnd.
        """
        self.out = []
        for out in self.console_log[self.counter :]:
            if out.startswith("$"):
                break
            self.out.append(out)
        self.n_items = len(self.out)

    def parse_out(self):
        """
        Parse the captured output as File() objects.

        If the item is a file, the size, name attributes are read from the
        captured out. If the item is a directory, the name is read.

        The parent and depth attributes are set by corresponding tracker
        variables from run() and cd().
        """
        for item in self.out:
            is_dir = item.startswith("dir")
            if is_dir:
                type = "dir"
                size = 0
                name = item[4:]
            else:
                type = "file"
                size = int(re.findall("[(0-9)]+", item)[0])
                # some files do not have .ext
                name = re.findall("[a-zA-Z].+|[a-zA-Z]+", item)[0]
            file = File(
                size=size,
                id=self.id,
                name=name,
                type=type,
                is_dir=is_dir,
                parent=self._prev_dirs[-1],
                depth=self.depth,
                n_sib=self.n_items,
            )
            print(f"found {file} in {self._prev_dirs[-1]}")
            self.log[self.depth].append(file)
            self.id += 1

def traverse_from_bottom(log: list):
    """Traverse the File() list collected in the log attribute of System().

    Read from the deepest node to the root node.

    Parameters
    ----------
    log : list
        Collection of File()'s

    Returns
    -------
    list
        A list of File() with the parents assigned as File() objects.
    """
    max_depth = max(log)  # key to access deepest node
    tree = []
    reverse_order = list(range(0, max_depth + 1))[::-1]
    for depth in reverse_order:
        parent = None
        for contents in log[depth]:
            if depth > 0:
                # find parent
                candidates = []
                for prev_depth in log[depth - 1]:
                    if (prev_depth.name == contents.parent) and prev_depth.is_dir:
                        # it should be a dir to be a parent
                        parent = prev_depth
                        candidates.append(parent)
                # if duplicate file names are used at the same depth, multiple
                # parent candidates can arise!
                if len(candidates) > 1:
                    print(contents, candidates, contents.id)
                    print(f"Warning: found {len(candidates)} parents!")
                    # Check for the closest node using absolute distance
                    # of their ids.
                    diffs = []
                    for c in candidates:
                        diff = abs(contents.id - c.id)
                        print(f"{c}: {c.id}, diff:{contents}-{c}={diff}")
                        diffs.append(diff)
                    min_idx = argmin(diffs)
                    parent = candidates[min_idx]
                    print(f"Select {parent}")
            contents.parent = parent
            tree.append(contents)
    return tree

def find_common_parent(t, other_paths):
    """
    Take a path `t` and compare iteratively with other paths `other_path` to
    find if they are in the same directory (under the same parent).

    Will return self if no other paths with same parent.
    """
    items = []
    for op in other_paths:
        conditions = (
            (t.parent is not None) and (t.parent == op.parent) and (t.depth == op.depth)
        )
        if conditions:
            items.append(op)
    return items

def assign_dir_size(t, other_paths):
    """
    Find parent directories and calculate the size attribute of
    each parent directory by summing up all of its child attribute (size),
    starting from the deepest File.
    """
    siblings = find_common_parent(t, other_paths)
    print(f"sib: {len(siblings)}, d={t.depth}, name={t.name}", end=" ")
    dir_size = 0
    for s in siblings:
        dir_size += s.size
    if (t.parent) and (t.parent.size == 0):
        # only assign oif not already assigned by other paths.
        print(f"assigned {t.parent.size} -> {dir_size}")
        t.parent.size = dir_size

if __name__ == "__main__":

    _depth = 1
    console_log = read_file("dec7/input.txt")
    # print(console_log)
    os = System(console_log)
    for c in console_log:
        os.run(c)
    # save
    tmp_log = os.log.copy()
    root_folder = File(
        id=0, parent=None, name="/", size=0, depth=0, type="dir", is_dir=True
    )
    tmp_log[0] = [root_folder]
    joblib.dump(dict(tmp_log), "dec7/inp.joblib")
    # print(f"\nbuilding tree")
    print(tmp_log)

    tree = traverse_from_bottom(tmp_log)

    for t in tree:
        assign_dir_size(t, tree)

    # save tree
    joblib.dump(tree, "dec7/tree.joblib")
    # Part 1:
    dirs = []
    sum_ = 0
    dirs_sum = []

    for t in tree:
        if t.is_dir and t.size <= 100000:
            sum_ += t.size
            # name, size, cumulative sum
            dirs_sum.append((t.name, t.size, sum_))

    print(f"\n\nSum of dirs with size <100000: {sum_}")


from collections import deque
import sys

n = int(sys.stdin.readline())
arr = []
x = 0
y = 0
for i in range(n):
    arr.append(list(map(int, sys.stdin.readline().split(" "))))
    if 9 in arr[i]:
        x = i
        y = arr[i].index(9)

q = deque()
chk = [[0] * n for _ in range(n)]
size = 2
exp = 0
q.append((x, y, 0))
arr[x][y] = 0
ret = 0
chk[x][y] = 1
stack = 0
slist = []
while True:
    if not q and stack != 0:
        slist.sort(key=lambda x: (x[0], x[1]))
        curr = slist[0]
        arr[curr[0]][curr[1]] = 0
        exp += 1
        if exp == size:
            exp = 0
            size += 1
        q.clear()
        chk = [[0] * n for _ in range(n)]
        chk[curr[0]][curr[1]] = 1
        stack = 0
        slist.clear()
        q.append((curr[0], curr[1], curr[2]))
        ret = max(ret, curr[2])
        continue
    elif not q:
        break
    curr = q.popleft()
    if arr[curr[0]][curr[1]] != 0 and arr[curr[0]][curr[1]] < size:
        if stack == 0:
            stack = curr[2]
            slist.append(curr)
            continue
        elif stack == curr[2]:
            slist.append(curr)
            continue
        elif stack < curr[2]:
            slist.sort(key=lambda x: (x[0], x[1]))
            curr = slist[0]
            arr[curr[0]][curr[1]] = 0
            exp += 1
            if exp == size:
                exp = 0
                size += 1
            q.clear()
            chk = [[0] * n for _ in range(n)]
            chk[curr[0]][curr[1]] = 1
            stack = 0
            slist.clear()
            q.append((curr[0], curr[1], curr[2]))
            ret = max(ret, curr[2])
            continue
    dx = [-1, 0, 0, 1]
    dy = [0, -1, 1, 0]
    for i in range(4):
        nx = dx[i] + curr[0]
        ny = dy[i] + curr[1]
        if (
            nx >= 0
            and ny >= 0
            and nx < n
            and ny < n
            and chk[nx][ny] == 0
            and arr[nx][ny] <= size
        ):
            chk[nx][ny] = 1
            q.append((nx, ny, curr[2] + 1))
print(ret)


from dataclasses import dataclass
from typing import List

import torch
from torch import Tensor

from .model import Model
from ...extension.data_parallel import BunchDataParallel, Bunch

class Inferer:

    @dataclass
    class Inference:
        resized_roi1_batch: List[Tensor]
        resized_roi2_batch: List[Tensor]
        final_pred_class_batch: List[Tensor]
        final_pred_prob_batch: List[Tensor]

    def __init__(self, model: Model, device_ids: List[int] = None):
        super().__init__()
        self._model = BunchDataParallel(model, device_ids)

    @torch.no_grad()
    def infer(self, image_batch: List[Tensor],
              lower_prob_thresh: float, upper_prob_thresh: float) -> Inference:
        image_batch = Bunch(image_batch)

        pred_prob_batch, pred_class_batch, resized_roi1_batch, resized_roi2_batch = \
            self._model.eval().forward(image_batch)

        final_pred_class_batch = []
        for pred_prob, pred_class in zip(pred_prob_batch, pred_class_batch):
            if (pred_prob >= lower_prob_thresh) & (pred_prob <= upper_prob_thresh):
                final_pred_class = pred_class
            else:
                final_pred_class = torch.tensor(0).to(pred_class)
            final_pred_class_batch.append(final_pred_class)

        final_pred_prob_batch = pred_prob_batch

        inference = Inferer.Inference(resized_roi1_batch, resized_roi2_batch, final_pred_class_batch, final_pred_prob_batch)
        return inference


"""
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

"""
from typing import List
def permute(nums: List[int]) -> List[List[int]]:

    def permutations(arr):
        if len(arr) == 0:
            return []

        if len(arr) == 1:
            return [arr]

        possible_perms = []

        for a in range(len(arr)):
            #Ref first elem
            first = arr[a]

            #Extract the other elems
            splits = arr[:a] + arr[a+1:]

            for p in permutations(splits):
                possible_perms.append([first]+p)
        return possible_perms

    all_perms = permutations(nums)
    return all_perms

Case1 = [1,2,3]
print(permute(Case1))

"""
Time Complexity: O(N)
Space: O(N)

"""


import sys
sys.stdin = open("baek1926.txt")
from collections import deque

def bfs(start_row, start_col):

    cnt = 1
    visited[start_row][start_col] = True
    que = deque()
    que.append((start_row, start_col))

    while que:
        row, col = que.popleft()

        for d in range(0, 4):
            next_row = row + dr[d]
            next_col = col + dc[d]

            if next_row < 0 or next_col < 0 or next_row >= N or next_col >= M:
                continue
            if visited[next_row][next_col]:
                continue
            if MAP[next_row][next_col]:
                cnt += 1
                visited[next_row][next_col] = True
                que.append((next_row, next_col))
    return cnt

dr = [-1, 1, 0, 0]
dc = [0, 0, -1, 1]
N, M = map(int, input().split())
MAP = [0] * N
visited = [[False] * M for _ in range(0, N)]
area_cnt = 0
max_area = 0
for i in range(0, N):
    MAP[i] = list(map(int, input().split()))

for i in range(0, N):
    for j in range(0, M):
        if not visited[i][j] and MAP[i][j]:
            now = bfs(i, j)
            area_cnt += 1
            max_area = max(max_area, now)
print(area_cnt)
print(max_area)


#!/usr/bin/env python3
# coding: utf-8

#-:-:-:-:-:-:-:-:-:-:-:-:#
#    TIDoS Framework     #
#-:-:-:-:-:-:-:-:-:-:-:-:#

#This module requires TIDoS Framework
#https://github.com/0xInfection/TIDoS-Framework

import nmap
import time
from core.Core.colors import *
import socket
from core.database.database_module import save_data
from core.variables import database
from core.methods.cache import targetname
import inspect

info = "This module discovers possible Telnet misconfiguration."
searchinfo = "Telnet Checker"
properties = {}

def netmisc0x00(web):

    web = web.replace('http://','')
    web = web.replace('https://','')
    ip = socket.gethostbyname(web)
    port = 23 # telnet port
    try:
        print(O+' [!] Configuring scanner for telnet ports...')
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        print(C+"\n [*] Connecting to '%s' via port %s" % (ip, port))
        r = sock.connect_ex((ip, port))
        time.sleep(0.1)
        if r == 0:
            print(G+' [+] Telnet port detected open!')
            time.sleep(0.5)
            print(O+' [*] Confirming...')
            nmScan = nmap.PortScanner()
            nmScan.scan(ip, '23')
            port = 23
            thisDict = nmScan[ip]['tcp'][port]
            print(G+' [+] Telnet network misconfiguration confirmed!')
            save_data(database, module, lvl1, lvl2, lvl3, name, "Telnet misconfiguration confirmed!")
            time.sleep(0.5)
            if thisDict['version']:
                print(G+' [+] Port ' + str(port) + ': ' +C+ thisDict['product'] +GR+ ', v' + thisDict['version'])
                data = "Port " + str(port) + ": " + thisDict["product"] + ", v" + thisDict["version"]
                save_data(database, module, lvl1, lvl2, lvl3, name, data)
            else:
                print(G+' [+] Port ' + str(port) + ': ' +C+ thisDict['product'])
                data = "Port " + str(port) + ": " + thisDict["product"]
                save_data(database, module, lvl1, lvl2, lvl3, name, data)
        else:
            print(R+' [-] Telnet Disabled!')
            save_data(database, module, lvl1, lvl2, lvl3, name, "Telnet disabled.")
        sock.close()

    except Exception as e:
        print('\n'+R+' [!] Exception detected at port %s !' % port)
        print(R+' [-] Error : '+str(e))

def netmisc(web):
    global name
    name = targetname(web)
    global lvl2
    lvl2 = inspect.stack()[0][3]
    global module
    module = "VulnAnalysis"
    global lvl1
    lvl1 = "Basic Bugs & Misconfigurations"
    global lvl3
    lvl3 = ""
    #print(R+'\n    ===================================')
    #print(R+'\n     TELNET ENABLED (Network Misconf.)')
    #print(R+'    ---<>----<>----<>----<>----<>----<>\n')

    from core.methods.print import pvln
    pvln("network misconf.")

    print(GR+' [*] Loading up port scanner...')
    netmisc0x00(web)
    time.sleep(0.5)

def attack(web):
    web = web.fullurl
    netmisc(web)


import math

import cv2
from absl import logging

from src.helper.image_helper import ImageHelper

class Preprocessor(object):
    def __init__(self, flags):
        self.__flags = flags
        self.filter_strength = 15
        self.canny_lower_threshold = 50

    def run(self, image):
        if image is None:
            logging.warning("Image for preprocessor is not defined")
            return

        image = self.__remove_noise(image, "02_noise_removal")
        image = self.__align_image(image, "03_aligning")
        image = self.__binarize_image(image, "04_binarization")
        return image

    def __remove_noise(self, image, title: str):
        logging.debug("Remove noise for image")
        cv2.fastNlMeansDenoising(image, image, self.filter_strength)
        return ImageHelper.write_image(image, title, self.__flags)

    def __align_image(self, image, title: str):
        logging.debug("Align image")
        angle = self.__compute_angle(image.copy())
        logging.debug("Rotating image by {} degrees".format(angle))

        if not math.isclose(angle, 0.0):
            image = self.__deskew(image.copy(), angle)
        return ImageHelper.write_image(image, title, self.__flags)

    def __compute_angle(self, image) -> float:
        height, width = image.shape

        edges = cv2.Canny(image, self.canny_lower_threshold, self.canny_lower_threshold * 3)
        lines = cv2.HoughLinesP(edges, 1, math.pi / 180, 200, minLineLength=width / 8, maxLineGap=width / 128)

        angle = 0.0
        if lines.any():
            for points in lines:
                x1, y1, x2, y2 = points[0]
                angle += math.atan2(y2 - y1, x2 - x1)
        return angle * 180 / math.pi

    def __deskew(self, image, angle: float):
        non_zero_pixels = cv2.findNonZero(image)
        center, wh, theta = cv2.minAreaRect(non_zero_pixels)

        root_mat = cv2.getRotationMatrix2D(center, angle, 1)
        rows, cols = image.shape
        rotated = cv2.warpAffine(image, root_mat, (cols, rows), cv2.INTER_CUBIC, borderValue=(255, 255, 255))
        return cv2.getRectSubPix(rotated, (cols, rows), center)

    def __binarize_image(self, image, title: str):
        logging.debug("Binarize image")
        _, image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        return ImageHelper.write_image(image, title, self.__flags)


import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
import numpy as np
import sympy as sp
dateDiabet=load_diabetes(as_frame=True)
yDiabet=np.array(dateDiabet.target)
xBmi=dateDiabet.data['bmi']
numeFeatures=np.array(dateDiabet.feature_names)
a,b=sp.symbols(['a','b'])
mse=float(0)
for x,y in zip(xBmi,yDiabet):
    mse=mse+(a*x+b-y)**2
derivataA = sp.diff(mse, a)
derivataB = sp.diff(mse, b)
eq1 = sp.Eq(derivataA, 0)
eq2 = sp.Eq(derivataB, 0)
solution = sp.solve((eq1, eq2), (a, b))
print(solution)
print("The equation of the regression line is "+str(solution[a])+"*x+"+str(solution[b]))
xLinie=[-1,0,1]
yLinie=[-solution[a]+solution[b],solution[b],solution[a]*1+solution[b]]
plt.plot(xLinie,yLinie)
plt.scatter(xBmi,yDiabet)
plt.show()

if __name__ == '__main__':
    pass

# See PyCharm help at https://www.jetbrains.com/help/pycharm/


from abc import ABC, abstractmethod
from turtle import color
from typing import Union
import os, pickle
from skimage.measure import block_reduce
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from interpretable_ts_clustering.models.classification import DecisionTreeWrapper, XGBWrapper
import tensorflow as tf
from interpretable_ts_clustering.visualization.shap_features import shap_method_dict

def visualize_explanations(config, model, X, y_true, y_pred, wandb):
    """ Saves visualizations of local explanations """
    if config['CLASSIFICATION_MODEL'] == 'fcn':
        cam_explainer = CAMExplainer(config, model)
        cam_explainer.compute_contributions(X)
        cam_explainer.visualize_contributions(y_true, y_pred, wandb)

    elif config['CLASSIFICATION_MODEL'] == 'xgb':
        shap_explainer = SHAPExplainer(config, model)
        shap_explainer.compute_contributions(X, method_name='treeshap')
        shap_explainer.visualize_contributions(y_true, y_pred, wandb)
        feat_explainer = FeatImpExplainer(config, model)
        feat_explainer.compute_contributions()
        feat_explainer.visualize_contributions(wandb)

class Explainer(ABC):
    def __init__(self, config: dict, model) -> None:
        super().__init__()
        self.config = config
        self.model = model

    @abstractmethod
    def compute_contributions(self, X):
        pass

    def visualize_global_feat_imp(self, feat_imps, title, wandb=None):
        history_size = len(feat_imps)
        x_range = list(range(0, history_size))
        # x_ticks = list(range(-history_size, 0))
        x_ticks = list(range(0, history_size))
        fig, ax = plt.subplots()
        ax.plot(x_range, feat_imps)
        ax.plot(x_range, self.aggregate_windows(feat_imps), 'r-')
        ax.set_ylabel(f'{title} Feature Importance')
        ax.set_xlabel('History')
        plt.xticks(x_range, x_ticks)
        plt.locator_params(axis='x', nbins=10)
        plt.title(f'{title} Feature Importance')
        if wandb is not None:
            save_dir = os.path.join(wandb.run.dir, 'global_explanations')
            os.makedirs(save_dir, exist_ok=True)
            fig.savefig(os.path.join(save_dir, f'{title}_feature_importance.png'))
        else:
            plt.show()

    def save(self, SAVE_DIR: str, file_name='contributions.pickle'):
        file_path = os.path.join(SAVE_DIR, file_name)
        pickle.dump(self.config, open(file_path, 'wb'))

    def load(self, SAVE_DIR: str, file_name='contributions.pickle'):
        file_path = os.path.join(SAVE_DIR, file_name)
        self.contributions = pickle.load(open(file_path, 'rb'))

    @staticmethod
    def aggregate_windows(x: np.ndarray, split_size=10):
        """ Creates windows of mean values
        x (np.ndarray): 1D array
        split_size (int): split window size
        """
        total_window_size = len(x)
        # https://stackoverflow.com/questions/15956309/averaging-over-every-n-elements-of-a-numpy-array
        x_reduced = block_reduce(x, block_size=(split_size,),
                                    func=np.mean,
                                    cval=np.mean(x))

        x_repeat = np.repeat(x_reduced, split_size)
        x_trimmed = x_repeat[:total_window_size]
        return x_trimmed

class GiniTreeExplainer(Explainer):
    def __init__(self, config: dict, model: Union[XGBWrapper, DecisionTreeWrapper]) -> None:
        super().__init__(config, model)

    def compute_contributions(self, ):
        # contributions = (features,)
        self.contributions = self.model.model.feature_importances_

    def visualize_contributions(self, wandb=None):
        self.visualize_global_feat_imp(self.contributions, 'gini', wandb)

class CAMExplainer(Explainer):
    def __init__(self, config: dict, model) -> None:
        super().__init__(config, model)

    def compute_contributions(self, X):
        grad_model = tf.keras.Model(
        inputs=[self.model.model.inputs],
        outputs=[self.model.model.layers[10].output,
        self.model.model.output])

        with tf.GradientTape() as tape:
            (conv_outputs, predictions) = grad_model(X)
            loss = predictions[:,0]
            grads = tape.gradient(loss, conv_outputs)
            casted_conv_outputs = tf.cast(conv_outputs > 0, "float32")
            casted_grads = tf.cast(grads > 0, "float32")

            guided_grads = casted_conv_outputs * casted_grads * grads

            weights = tf.reduce_mean(guided_grads, axis=(0, 1))
            cam = tf.reduce_sum(tf.multiply(weights, conv_outputs), axis=-1)

        # samples = X
        samples =  np.squeeze(X)
        self.contributions = (cam, samples)

    def visualize_contributions(self, y_true, y_pred, wandb=None):
        labels = np.unique(y_true)
        cam, samples = self.contributions

        history_size = samples.shape[1]
        x_range = list(range(0, history_size))
        # x_ticks = list(range(-history_size, 0))
        x_ticks = list(range(0, history_size))

        # Visualize single sample contributions
        for i in range(20):
            if y_true[i] != y_pred[i]:
                continue

            sample_contributions = np.abs(cam.numpy()[i])
            y_class = y_true[i]

            y_range_series = [np.min(samples) - 0.1, np.max(samples) + 0.1]
            y_range_contrib = [np.min(cam), np.max(cam)]
            fig, ax1 = plt.subplots()
            ax1.plot(x_range, samples[i], color='blue')
            ax1.set_ylabel('Time Series')
            ax1.set_ylim(y_range_series)

            plt.xticks(x_range, x_ticks)
            plt.locator_params(axis='x', nbins=10)
            plt.xlabel('History')
            plt.title(f'Sample: {i} from Cluster: {y_class}')

            ax2 = ax1.twinx()
            ax2.fill_between(x_range, [0 for _ in range(len(x_range))],
                sample_contributions,
                alpha=0.5, edgecolor='red', facecolor='red')
            ax2.set_ylabel('Contributions')
            ax2.set_ylim(y_range_contrib)
            fig.tight_layout()

            if wandb is not None:
                save_dir = os.path.join(wandb.run.dir, 'cam_local_explanations')
                os.makedirs(save_dir, exist_ok=True)
                fig.savefig(os.path.join(save_dir,
                         f'sample_{i}_cluster_{y_class}.png'))
            else:
                plt.show()

        # aggregate local explanations
        n_samples = self.config["PLOT_MAX_SAMPLES"]

        for label in labels:
            label_samples_idxs = np.where(y_pred == label)[0][:n_samples]
            sample_contributions = np.abs(cam.numpy()[label_samples_idxs])
            mean_absolute_contributions = np.mean((sample_contributions), axis=0)
            fig, axs = plt.subplots(2, 1, sharex=True)
            plt.xticks(x_range, x_ticks)
            plt.locator_params(axis='x', nbins=10)

            for sample_contrib in sample_contributions:
                axs[0].plot(sample_contrib, "k-", alpha=.2)
            axs[0].plot(self.aggregate_windows(mean_absolute_contributions), "r-")
            # ax.plot(mean_absolute_contributions, "r-")
            axs[0].set_ylabel('CAM values')
            axs[0].set_title(f'Mean feature importance for cluster: {label}')

            for sample in samples[label_samples_idxs]:
                axs[1].plot(sample, "k-", alpha=.2)
            axs[1].set_ylabel('Feature value')
            axs[1].set_title(f'Samples from cluster: {label}')

            if wandb is not None:
                save_dir = os.path.join(wandb.run.dir, 'cam_aggregate_local_explanations')
                os.makedirs(save_dir, exist_ok=True)
                fig.savefig(os.path.join(save_dir,
                         f'cluster_{label}.png'))
            else:
                plt.show()

class FeatImpExplainer(Explainer):
    def __init__(self, config: dict, model) -> None:
        super().__init__(config, model)
    def compute_contributions(self):
        self.feat_imps = self.model.model.feature_importances_
    def visualize_contributions(self, wandb=None):
        plt.plot(self.feat_imps, color="r")
        plt.xlabel("Timestep")
        plt.xlabel("Feature Importance")

        if wandb is not None:
            save_dir = os.path.join(wandb.run.dir, 'feature_importances')
            os.makedirs(save_dir, exist_ok=True)
            plt.savefig(os.path.join(save_dir,
                        f'feat_imp.png'))
        else:
            plt.show()

class SHAPExplainer(Explainer):
    def __init__(self, config: dict, model) -> None:
        super().__init__(config, model)

    def compute_contributions(self, X, method_name: str,
                              nsamples=100000, random_state=0):
        shap_val, samples = shap_method_dict[method_name](self.model.model, X,
                                                          nsamples,
                                                          random_state)
        self.contributions = (shap_val, samples)

    def visualize_contributions(self, y_true, y_pred,wandb=None,  nsamples=None):
        shap_val, samples = self.contributions
        history_size = samples.shape[1]
        x_range = list(range(0, history_size))
        # x_ticks = list(range(-history_size, 0))
        x_ticks = list(range(0, history_size))
        # Visualize global explanation
        global_shap_vals = np.abs(shap_val).sum(axis=0).sum(axis=0)
        # taking sum over all samples
        self.visualize_global_feat_imp(global_shap_vals, 'shap', wandb)
        # Visualize single sample contributions

        for i in range(20):
            if y_true[i] != y_pred[i]:
                continue

            sample_contributions = shap_val[y_true[i], i, :]
            y_class = y_true[i]

            y_range_series = [np.min(samples) - 0.1, np.max(samples) + 0.1]
            y_range_contrib = [np.min(shap_val), np.max(shap_val)]
            fig, ax1 = plt.subplots()
            ax1.plot(x_range, samples[i], color='blue')
            ax1.set_ylabel('Time Series')
            ax1.set_ylim(y_range_series)

            plt.xticks(x_range, x_ticks)
            plt.locator_params(axis='x', nbins=10)
            plt.xlabel('History')
            plt.title(f'Sample: {i} from Cluster: {y_class}')

            ax2 = ax1.twinx()
            ax2.fill_between(x_range, [0 for _ in range(len(x_range))],
                sample_contributions,
                alpha=0.5, edgecolor='blue', facecolor='blue')
            ax2.set_ylabel('Contributions')
            ax2.set_ylim(y_range_contrib)
            fig.tight_layout()

            if wandb is not None:
                save_dir = os.path.join(wandb.run.dir, 'local_explanations')
                os.makedirs(save_dir, exist_ok=True)
                fig.savefig(os.path.join(save_dir,
                         f'sample_{i}_cluster_{y_class}.png'))
            else:
                plt.show()

        # Visualize window aggregate contributions

        labels = np.unique(y_true)
        for label in labels:
            # go by cluster
            label_samples_idxs = np.where(y_pred == label)[0]
            sample_contributions = np.abs(shap_val[label, label_samples_idxs])
            mean_absolute_contributions = np.mean(np.abs(sample_contributions), axis=0)

            fig, ax = plt.subplots()
            plt.xticks(x_range, x_ticks)
            plt.locator_params(axis='x', nbins=10) # is 10 good for any scenario

            for sample_contrib in sample_contributions:
                ax.plot(sample_contrib, "k-", alpha=.2)
            ax.plot(self.aggregate_windows(mean_absolute_contributions), "r-")
            # ax.plot(mean_absolute_contributions, "r-")
            ax.set_ylabel('Feature Importances')
            ax.set_title(f'Mean feature importance for cluster: {label}')

            if wandb is not None:
                save_dir = os.path.join(wandb.run.dir, 'aggregate_local_explanations')
                os.makedirs(save_dir, exist_ok=True)
                fig.savefig(os.path.join(save_dir,
                         f'cluster_{label}.png'))
            else:
                plt.show()

        # Side by side aggregate figures
        labels = np.unique(y_true)
        for label in labels:
            label_samples_idxs = np.where(y_pred == label)[0]
            sample_contributions = np.abs(shap_val[label, label_samples_idxs])
            mean_absolute_contributions = np.mean(np.abs(sample_contributions), axis=0)

            fig, axs = plt.subplots(2, 1, sharex=True)
            plt.xticks(x_range, x_ticks)
            plt.locator_params(axis='x', nbins=10)

            for sample_contrib in sample_contributions:
                axs[0].plot(sample_contrib, "k-", alpha=.2)
            axs[0].plot(self.aggregate_windows(mean_absolute_contributions), "r-")
            # ax.plot(mean_absolute_contributions, "r-")
            axs[0].set_ylabel('Feature Importances')
            axs[0].set_title(f'Mean feature importance for cluster: {label}')

            for sample in samples[label_samples_idxs]:
                axs[1].plot(sample, "k-", alpha=.2)
            axs[1].set_ylabel('Feature value')
            axs[1].set_title(f'Samples from cluster: {label}')

            if wandb is not None:
                save_dir = os.path.join(wandb.run.dir, 'aggregate_local_explanations')
                os.makedirs(save_dir, exist_ok=True)
                fig.savefig(os.path.join(save_dir,
                         f'cluster_{label}.png'))
            else:
                plt.show()

def normalize_data(data):
    return ((data - np.min(data)) / (np.max(data) - np.min(data)))

def get_top_features(mean_absolute_contributions, n_top, extra_features, extra_feat_start, label, mode="default"):
    def index_to_string(result, extra_feat_start, extra_features, extra_only):
        if extra_only:
            feat_string = extra_features[result]
        else:
            if result>=extra_feat_start:
                feat_index = result - extra_feat_start
                feat_string = extra_features[feat_index]
            else:
                feat_string = f"timestep_{result}"
        return feat_string

    if mode == "extra_only":
        feat_impt = mean_absolute_contributions[extra_feat_start:]
        extra_only = True
    elif mode == "ts_only":
        feat_impt = mean_absolute_contributions[:extra_feat_start]
        extra_only = False
    elif mode=="default":
        feat_impt = mean_absolute_contributions
        extra_only=False

    ind = np.argpartition(feat_impt, -n_top)[-n_top:]
    top_sorted = ind[np.argsort(feat_impt[ind])] #top to bottom

    top_features = {
        "index": top_sorted,
        "value": feat_impt[top_sorted]
    }

    top_features["index_string"] = [index_to_string(feat, extra_feat_start, extra_features, extra_only) for feat in top_features["index"]]

    undefined_counter = 0
    for index, i in enumerate(top_sorted):
        if top_features["value"][index] == 0:
            undefined_counter += 1
            top_features["index_string"][index] = f"zero_{undefined_counter}"

    top_features["label"] = label
    return top_features

def plot_top_features(features_list, class_config, save_dir, model, mode):
    rows = len(features_list)
    fig, axs = plt.subplots(rows, 1, figsize=(15,10))
    for index, top_features in enumerate(features_list):
        axs[index].barh(top_features["index_string"], top_features["value"])
        axs[index].set_ylabel(top_features["label"])

    plt.tight_layout()
    fig.savefig(f"{save_dir}/{class_config['DATASET']}_barplots_{model}_{mode}")

def plot_xgb(train_x, class_config, class_model, save_dir, extra_features, n_top = 10, fontsize = 20, tick_fontsize=18):
    shap_explainer = SHAPExplainer(class_config, class_model)
    shap_explainer.compute_contributions(train_x, method_name='treeshap')
    feat_explainer = FeatImpExplainer(class_config, class_model)
    feat_explainer.compute_contributions()
    feat_imps = feat_explainer.feat_imps
    shap_val, shap_samples = shap_explainer.contributions

    feat_impt_dict = {
        "treeshap": shap_val,
        "feat_imp": feat_imps
    }

    with open(f"{save_dir}/{class_config['DATASET']}_xgb_xai_dict.p", "wb") as f:
        pickle.dump(feat_impt_dict, f)

    history_size = shap_samples.shape[1]
    x_range = list(range(0, history_size))
    x_ticks = list(range(0, history_size))

    global_shap_vals = np.abs(shap_val).sum(axis=0).sum(axis=0)

    fig, axs = plt.subplots(3,1, sharex="row", figsize=(15,15))

    plt.xticks(x_range, x_ticks)
    plt.locator_params(axis='x', nbins=10) # is 10 good for any scenario
    for index, i in enumerate(axs):
        axs[index].tick_params(axis='both', which='major', labelsize=tick_fontsize)

    axs[0].plot(global_shap_vals, "r")
    axs[0].set_ylabel("TreeSHAP", fontsize = fontsize)
    axs[2].plot(feat_imps, "b")
    axs[2].set_ylabel("Tree feature importance",  fontsize = fontsize)
    axs[1].set_ylabel("Feature values",  fontsize = fontsize)
    for sample in shap_samples[:20]:
        axs[1].plot(sample, "k-", alpha=.2)

    plt.tight_layout()
    fig.savefig(f"{save_dir}/{class_config['DATASET']}_xgb_shap_feat.pdf", format="pdf")

    modes = ["default", "extra_only", "ts_only"]
    extra_feat_start = class_config["HISTORY_SIZE"] - extra_features.shape[0]
    for mode in modes:
        shap_features = get_top_features(global_shap_vals, n_top, extra_features, extra_feat_start, label="shap", mode=mode)
        feat_imp_features = get_top_features(feat_imps, n_top, extra_features, extra_feat_start, label="feat_imp", mode=mode)
        features_list = [shap_features, feat_imp_features]
        plot_top_features(features_list, class_config, save_dir, model="xgb", mode=mode)

def plot_fcn(train_x, train_y, class_config, class_model, save_dir, extra_features, n_top = 10, fontsize = 20, tick_fontsize=18):
    pred_y = class_model.predict(train_x).argmax(axis=1)
    y_pred = pred_y
    labels = np.unique(y_pred)

    cam_explainer = CAMExplainer(class_config, class_model)
    cam_explainer.compute_contributions(train_x)
    shap_explainer = SHAPExplainer(class_config, class_model)
    shap_explainer.compute_contributions(train_x, method_name='gradshap')
    shap_val, shap_samples = shap_explainer.contributions
    cam_val, cam_samples = cam_explainer.contributions

    feat_impt_dict = {
        "gradshap": shap_val,
        "gradcam": cam_val
    }

    with open(f"{save_dir}/{class_config['DATASET']}_fcn_xai_dict.p", "wb") as f:
        pickle.dump(feat_impt_dict, f)

    print(f"shap val shape: {shap_val.shape}")
    print(f"cam val shape: {cam_val.shape}")
    global_shap_vals = normalize_data(np.abs(shap_val).sum(axis=0).sum(axis=0))
    global_cam_vals = normalize_data(np.abs(cam_val).sum(axis=0))

    pred_y = class_model.predict(train_x).argmax(axis=1)
    history_size = shap_samples.shape[1]
    x_range = list(range(0, history_size))

    y_true = train_y
    n_samples = class_config["PLOT_MAX_SAMPLES"]

    extra_feat_start = class_config["HISTORY_SIZE"] - extra_features.shape[0]

    indi_list = [None, True]

    modes = ["default", "extra_only", "ts_only"]
    for mode in modes:
        gradshap_top_features = get_top_features(global_shap_vals, n_top, extra_features, extra_feat_start, label="gradshap", mode=mode)
        cam_top_features = get_top_features(global_cam_vals, n_top, extra_features, extra_feat_start, label="cam", mode=mode)
        features_list = [cam_top_features, gradshap_top_features]
        plot_top_features(features_list, class_config, save_dir, model="fcn", mode=mode)

    for label in labels:
        # go by cluster
        # print(np.where(y_pred == label))
        label_samples_idxs = list(np.where(y_pred == label)[0])

        for i in label_samples_idxs:
            if y_true[i] != y_pred[i]:
                label_samples_idxs.remove(i)

        label_samples_idxs = np.array(label_samples_idxs)

        if len(label_samples_idxs) != 0:

            sample_contributions = np.abs(shap_val[label, label_samples_idxs])
            # sample_contributions = np.array([normalize_data(sample_contrib) for sample_contrib in sample_contributions])
            mean_absolute_contributions = np.mean(np.abs(sample_contributions), axis=0)

            cam_sample_contributions = np.abs(cam_val.numpy()[label_samples_idxs])
            # cam_sample_contributions = np.array([normalize_data(sample_contrib) for sample_contrib in cam_sample_contributions])
            cam_mean_absolute_contributions = np.mean((cam_sample_contributions), axis=0)

            for plot_individual_samples in indi_list:
                print(f"plot_individual: {plot_individual_samples}")
                fig, axs = plt.subplots(3,1, sharex=True, figsize=(15,15))
                plt.xticks(x_range, x_range)
                plt.locator_params(axis='x', nbins=10) # is 10 good for any scenario

                for index, i in enumerate(axs):
                    axs[index].tick_params(axis='both', which='major', labelsize=tick_fontsize)

                if plot_individual_samples:
                    for sample_contrib in sample_contributions:
                        axs[0].plot(sample_contrib, "k-", alpha=.2)
                    for cam_sample_contrib in cam_sample_contributions:
                        axs[2].plot(cam_sample_contrib, "k-", alpha=.2)

                axs[0].plot(shap_explainer.aggregate_windows(mean_absolute_contributions), "r-")
                axs[0].set_ylabel('GradientSHAP values', fontsize = fontsize)

                axs[2].plot(cam_explainer.aggregate_windows(cam_mean_absolute_contributions), "r-")
                axs[2].set_ylabel('Grad-CAM values', fontsize = fontsize)

                for sample in shap_samples[label_samples_idxs]:
                    axs[1].plot(sample, "k-", alpha=.2)
                axs[1].set_ylabel('Feature values', fontsize = fontsize)

                plt.tight_layout()

                if plot_individual_samples:
                    fig.savefig(f"{save_dir}/{class_config['DATASET']}_samples_fcn_plot_{label}.pdf", format="pdf")
                else:
                    fig.savefig(f"{save_dir}/{class_config['DATASET']}_fcn_plot_{label}.pdf", format = "pdf")


import discord
from discord.ext import commands
from discord import app_commands
import os
from dotenv import load_dotenv
import asyncio

bot = commands.Bot(command_prefix="/", intents=discord.Intents.all())

print("Lancement du bot...")
bot.remove_command("help")

@bot.event
async def on_ready():
    print("Bot lancé!")
    await bot.change_presence(status=discord.Status.online)
    try:
        synced = await bot.tree.sync()
        print(f"Synced {len(synced)} command(s)")
    except Exception as e:
        print(e)

@bot.tree.command(name="help")
async def help(interaction: discord.Interaction):
    await interaction.response.send_message("Voici la commande help.", ephemeral=True)
    user = interaction.user
    embed = discord.Embed(title = "Help", description = f"{user.mention}, voici la liste des commands slash :\n> hello\n> ping\n> test_button\n> fruts_dropdown", color = 0xffffff)
    embed.set_footer(text = "Template Bot")
    await interaction.channel.send(embed=embed)

@bot.tree.command(name="hello")
async def hello(interaction: discord.Interaction):
    await interaction.response.send_message("Heyyy", ephemeral=True)

@bot.tree.command(name="ping")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message(f"Pong !\n> **{round(bot.latency *1000)}** ms", ephemeral=True)

class TestButton(discord.ui.View):
    def __init__(self):
        super().__init__()

    @discord.ui.button(label="📌 Test", style=discord.ButtonStyle.red)
    async def testBtn(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_message("Vous avez appuyez !", ephemeral=True)

@bot.tree.command(name="test_button")
async def fruts_dropdown(interaction: discord.Interaction):
    await interaction.response.send_message("Boutton créé !", ephemeral=True)
    await interaction.channel.send("Test boutton 📌", view=TestButton())

class frutsDropdown(discord.ui.Select):
    def __init__(self):
        options = [
            discord.SelectOption(label="🍎 Pomme", description='Tu es team pomme ?'),
            discord.SelectOption(label="🍓 Fraise", description='Tu es team fraise ?'),
            discord.SelectOption(label="🍌 Banane", description='Tu es team banane ?'),
        ]

        super().__init__(placeholder="🍍 Choisi ton fruit !", options=options, min_values=1, max_values=1)

    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(f"Vous avez choisi la team {self.values[0]} !", ephemeral=True)

class frutsView(discord.ui.View):
    def __init__(self):
        super().__init__()
        self.add_item(frutsDropdown())

@bot.tree.command(name="fruts_dropdown")
async def fruts_dropdown(interaction: discord.Interaction):
    await interaction.response.send_message("Dropdown créé !", ephemeral=True)
    await interaction.channel.send("Choisi ton fruit 🍍 :", view=frutsView())

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
bot.run(TOKEN)


from PIL import Image
from os import path, listdir

saved_image_name = "! illusion image.png"

def print_size(image):
    return f"(size: {image.size[0]} x {image.size[1]})"

def make_photo(images, final_path):  # Creates the optical illusion image
    # Check the images are all the same size
    for i in range(1, len(images)):
        if images[0].size != images[i].size:
            print("Please make sure the images are same size")
            print(f"Image number {i + 1} {print_size(images[i])} is different size than the first image {print_size(images[0])}")
            return

    pixels = []
    for i in range(len(images)):
        pixels.append(images[i].load())

    # Overwrite the first image, make strips in it. Then save it as a new image
    image_index = 0
    for x in range(int(images[0].size[0])):
        # Fill out the strip
        for y in range(int(images[0].size[1])):
            pixels[0][x, y] = pixels[image_index][x, y]

        # Increment image_index
        image_index += 1
        if image_index >= len(images):
            image_index -= len(images)

    # Save the image
    images[0].save(final_path)
    print("Made an image from " + str(len(images)) + " images")
    print("Image saved at " + str(final_path))

def get_user_data():
    answer = input("Do you want to see the instructions? (yes, no) ")
    if answer.lower() != "no":
        print("\nInstructions:")
        print("Create a folder that holds the images you want to use. Make sure they all are the same size")
        print("Then run this program, say 'no' and then type the location of that folder")
        print("The new image will be created in that same folder.")
        print("The animation order of the images is top down in the folder, so make sure they are in the correct order")
        print("\nNote:")
        print("The amount of images usually depends on the paper with the strips")
        print("Remember to print the image in the correct size. Otherwise it won't work")
        return

    folder_path = input("Please provide the folder path: ")

    # If inputs nonsense
    if not path.exists(folder_path):
        print("Please input a valid path")
        return

    # If it isn't a directory
    if not path.isdir(folder_path):
        print("Please input a directory")
        return

    # Create a tuple with the paths
    paths = listdir(folder_path)

    # Remove the final image from the list if it exists
    for i in range(len(paths)):
        if path.basename(paths[i]) == saved_image_name:
            paths.pop(i)
            break

    try:
        for i in range(len(paths)):
            absolute_path = path.join(folder_path, paths[i])
            if path.isdir(absolute_path):
                print("Please put only images in the folder")
                return
            paths[i] = Image.open(absolute_path)
    except:
        print("Please make sure all the files are images")
        return

    make_photo(paths, path.join(folder_path, saved_image_name))

get_user_data()


import numpy as np
import matplotlib.pyplot as plt

L = 114
Fs = 48
const = 2*np.pi/Fs

F_p1 = 3 + 0.6*(L-107)
F_p2 = 3 + 0.6*(L-109)

delta = 0.15

delF = 0.3

F_s1 = F_p1 + 0.3
F_s2 = F_p2 - 0.3

omega_p1 = const*F_p1
omega_p2 = const*F_p2

omega_c = (omega_p1+omega_p2)/2
omega_l = (omega_p1-omega_p2)/2

omega_s1 = const*F_s1
omega_s2 = const*F_s2
delomega = 2*np.pi*delF/Fs

A = -20*np.log10(delta)
N = np.ceil((A-8)/(4.57*delomega))

N = 100
n = np.arange(-N, N)
print(n[N])
hlp = np.sin(n*omega_l)/(n*np.pi)
hlp[N] = omega_l/np.pi

hbp = 2*hlp*np.cos(n*omega_c)

omega = np.arange(-np.pi/2, np.pi/2, np.pi/200)

Hbp = abs(np.polyval(hbp, np.exp(-1j*omega)))
plt.plot(omega/np.pi, Hbp)
plt.xlabel(r'$\omega / \pi$')
plt.ylabel(r'$|H_{bp}(\omega)|$')
plt.show()

fir_coeff = hbp


import cv2
import imutils

def find_bright_spots(image):
	'''
		retuns bright spots in the center of the frame.
	'''

	# get the image dimensions
	height, width, _ = image.shape
	# value by which the center of the image should be added an subtracted to get a window for detection
	cropping_threshold = 200
	# area above which bright spots should be cansidered
	threshold_area = 5000

	# cropping the image, take the entire height but width using cropping_threshold
	image = image[0:height, width // 2 - cropping_threshold:width // 2 + cropping_threshold]

	# convert to grayscale
	gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
	# blur the image
	blurred = cv2.GaussianBlur(gray, (11, 11), 0)

	# replace all pixels with value more than 170 by 255 and others to 0 to get all the bright ares
	thresh = cv2.threshold(blurred, 170, 255, cv2.THRESH_BINARY)[1]

	# remove noise from the threshold image
	thresh = cv2.erode(thresh, None, iterations=2)
	thresh = cv2.dilate(thresh, None, iterations=4)

	# find contours, areas with same intensity
	cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
			cv2.CHAIN_APPROX_SIMPLE)

	# grab all the contours
	cnts = imutils.grab_contours(cnts)

	# find all the contours with area greater than the threshold_area
	cnts = list(filter(lambda cnt:cv2.contourArea(cnt) > threshold_area, cnts))
	# print("*" * 10)
	# for cnt in cnts:
	# 	print(cv2.contourArea(cnt))

	# print("*" * 10)
	return len(cnts)


# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Extract all sentences belonging to selected languages.

Useful when constructing example text. Only considers the base language, not
sublocale-specifiers.
"""

import argparse
import collections
import json
import tempfile
from pathlib import Path
from typing import Dict, List

parser = argparse.ArgumentParser()
parser.add_argument(
    "languages", nargs="+", help="ISO 639-1 language codes to extract sentences for."
)
parsed_args = parser.parse_args()

CORPUS = Path(__file__).parent.parent / "corpus" / "aosp.json"

with open(CORPUS) as f:
    data = json.load(f)

# NOTE: Android seems to use different local specifiers, so just use the bare language.
desired_languages = set(l.split("-")[0] for l in parsed_args.languages)
findings: Dict[str, List[str]] = collections.defaultdict(list)

for sentence, info in data.items():
    languages = set(l.split("-")[0] for l in info["langs"])
    for l in languages:
        if l in desired_languages:
            findings[l].append(sentence)

TEMP_DIR = Path(tempfile.gettempdir())

for language, sentences in findings.items():
    target_path = TEMP_DIR / f"sentences-{language}.txt"
    with open(target_path, "w+") as f:
        f.write("\n".join(sorted(sentences)))
    print(f"Wrote {str(target_path)}")


import scrapy
from XuetangSpider.items import XuetangspiderItem
from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.webdriver.common.by import By
from scrapy.http import HtmlResponse
import time

class XuetangSpider(scrapy.Spider):
    name = 'xuetang'
    allowed_domains = ['www.xuetangx.com']
    start_urls = ['https://www.xuetangx.com/university/all']

    def parse(self, response):
        item = XuetangspiderItem()
        option = webdriver.ChromeOptions()
        option.add_argument('--headless')
        driver = webdriver.Chrome(chrome_options=option)
        driver.get(response.url)
        driver.implicitly_wait(5)
        for i in range(1, 36):
            response = HtmlResponse(url=driver.current_url, body=driver.page_source, encoding='utf-8')
            for each in response.xpath("/html/body/div[1]/div/div[2]/div[1]/div[2]/*"):
                item['school'] = each.xpath("./div[1]/p[1]/text()").get().strip()
                item['classnum'] = each.xpath("./p/text()").get().strip()
                yield (item)
            but = driver.find_element(By.CLASS_NAME, "btn-next")
            but.click()
            time.sleep(2)


from gi.repository import Gdk, Gio, GObject, Gtk

KEYBINDING_FLAGS = GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION

class MeldNotebook(Gtk.Notebook):
    """Notebook subclass with tab switch and reordering behaviour

    MeldNotebook implements some fairly generic tab switching shortcuts
    and a popup menu for simple tab controls, as well as some
    Meld-specific tab label handling.
    """

    __gtype_name__ = "MeldNotebook"

    __gsignals__ = {
        'tab-switch': (KEYBINDING_FLAGS, None, (int,)),
        'page-label-changed': (0, None, (GObject.TYPE_STRING,)),
    }

    # Python 3.4; no bytes formatting
    css = (
        b"""
        @binding-set TabSwitchBindings {
          bind "<Alt>1" { "tab-switch" (0) };
          bind "<Alt>2" { "tab-switch" (1) };
          bind "<Alt>3" { "tab-switch" (2) };
          bind "<Alt>4" { "tab-switch" (3) };
          bind "<Alt>5" { "tab-switch" (4) };
          bind "<Alt>6" { "tab-switch" (5) };
          bind "<Alt>7" { "tab-switch" (6) };
          bind "<Alt>8" { "tab-switch" (7) };
          bind "<Alt>9" { "tab-switch" (8) };
          bind "<Alt>0" { "tab-switch" (9) };
        }
        notebook.meld-notebook { -gtk-key-bindings: TabSwitchBindings; }
        """
    )

    ui = """
      <?xml version="1.0" encoding="UTF-8"?>
      <interface>
        <menu id="tab-menu">
          <item>
            <attribute name="label" translatable="yes">Move _Left</attribute>
            <attribute name="action">popup.tabmoveleft</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">Move _Right</attribute>
            <attribute name="action">popup.tabmoveright</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Close</attribute>
            <attribute name="action">win.close</attribute>
          </item>
        </menu>
      </interface>
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.action_group = Gio.SimpleActionGroup()

        actions = (
            ("tabmoveleft", self.on_tab_move_left),
            ("tabmoveright", self.on_tab_move_right),
        )
        for (name, callback) in actions:
            action = Gio.SimpleAction.new(name, None)
            action.connect('activate', callback)
            self.action_group.add_action(action)

        self.insert_action_group("popup", self.action_group)

        builder = Gtk.Builder.new_from_string(self.ui, -1)
        self.popup_menu = builder.get_object("tab-menu")

        provider = Gtk.CssProvider()
        provider.load_from_data(self.css)
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(), provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        stylecontext = self.get_style_context()
        stylecontext.add_class('meld-notebook')

        self.connect('button-press-event', self.on_button_press_event)
        self.connect('popup-menu', self.on_popup_menu)
        self.connect('page-added', self.on_page_added)
        self.connect('page-removed', self.on_page_removed)

    def do_tab_switch(self, page_num):
        self.set_current_page(page_num)

    def on_popup_menu(self, widget, event=None):
        self.action_group.lookup_action("tabmoveleft").set_enabled(
            self.get_current_page() > 0)
        self.action_group.lookup_action("tabmoveright").set_enabled(
            self.get_current_page() < self.get_n_pages() - 1)

        popup = Gtk.Menu.new_from_model(self.popup_menu)
        popup.attach_to_widget(widget, None)
        popup.show_all()

        if event:
            popup.popup_at_pointer(event)
        else:
            popup.popup_at_widget(
                widget,
                Gdk.Gravity.NORTH_WEST,
                Gdk.Gravity.NORTH_WEST,
                event,
            )
        return True

    def on_button_press_event(self, widget, event):
        if (event.triggers_context_menu() and
                event.type == Gdk.EventType.BUTTON_PRESS):
            return self.on_popup_menu(widget, event)
        return False

    def on_tab_move_left(self, *args):
        page_num = self.get_current_page()
        child = self.get_nth_page(page_num)
        page_num = page_num - 1 if page_num > 0 else 0
        self.reorder_child(child, page_num)

    def on_tab_move_right(self, *args):
        page_num = self.get_current_page()
        child = self.get_nth_page(page_num)
        self.reorder_child(child, page_num + 1)

    def on_page_added(self, notebook, child, page_num, *args):
        child.connect("label-changed", self.on_label_changed)
        self.props.show_tabs = self.get_n_pages() > 1

    def on_page_removed(self, notebook, child, page_num, *args):
        child.disconnect_by_func(self.on_label_changed)
        self.props.show_tabs = self.get_n_pages() > 1

    def on_label_changed(self, page, text: str, tooltip: str) -> None:
        nbl = self.get_tab_label(page)
        nbl.props.label_text = text
        nbl.set_tooltip_text(tooltip)

        # Only update the window title if the current page is active
        if self.get_current_page() == self.page_num(page):
            self.emit('page-label-changed', text)
        self.child_set_property(page, "menu-label", text)


from panda3d.core import *
from panda3d.physics import *
from direct.interval.IntervalGlobal import *
from direct.particles import ParticleEffect
from direct.particles import Particles
from direct.particles import ForceGroup
import random

class Bubbles(NodePath):

    def __init__(self, parent, renderParent):
        NodePath.__init__(self)
        self.renderParent = renderParent.attachNewNode('bubbleRenderParent')
        self.renderParent.setBin('fixed', 0)
        self.assign(parent.attachNewNode('bubbles'))
        self.effect = ParticleEffect.ParticleEffect()
        p0 = Particles.Particles('particles-1')
        p0.setFactory('PointParticleFactory')
        p0.setRenderer('SpriteParticleRenderer')
        p0.setEmitter('DiscEmitter')
        p0.setPoolSize(8)
        p0.setBirthRate(0.75)
        p0.setLitterSize(2)
        p0.setLitterSpread(1)
        p0.factory.setLifespanBase(2.0)
        p0.factory.setLifespanSpread(0.5)
        p0.factory.setTerminalVelocityBase(400.0)
        p0.factory.setTerminalVelocitySpread(40.0)
        p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
        p0.renderer.setUserAlpha(1.0)
        p0.renderer.setTextureFromNode('phase_4/models/char/bubble', '**/*')
        p0.renderer.setXScaleFlag(1)
        p0.renderer.setYScaleFlag(1)
        p0.renderer.setInitialXScale(0.07)
        p0.renderer.setFinalXScale(0.2)
        p0.renderer.setInitialYScale(0.07)
        p0.renderer.setFinalYScale(0.2)
        p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
        p0.emitter.setAmplitudeSpread(0.025)
        p0.emitter.setAmplitude(0.1)
        p0.emitter.setRadius(0.5)
        gravityForceGroup = ForceGroup.ForceGroup('air')
        force0 = LinearVectorForce(Vec3(0.0, 0.0, 1.0), 1.0, 0)
        force0.setActive(1)
        force1 = LinearJitterForce(2.5, 0)
        force1.setActive(1)
        gravityForceGroup.addForce(force0)
        gravityForceGroup.addForce(force1)
        self.effect.addForceGroup(gravityForceGroup)
        self.effect.addParticles(p0)
        self.effect.setPos(0, 0, 0)

    def start(self):
        self.effect.start(self, self.renderParent)

    def stop(self):
        self.effect.disable()

    def destroy(self):
        self.effect.cleanup()
        self.renderParent.removeNode()
        del self.effect
        del self.renderParent


# we have to import opencv
import cv2 as cv

# in this file we will take an image and show it, as well
# a video too

# 1. Reading Image
# use the imread() to take input and store its array in image
img = cv.imread("Photos/cats.jpg")
# show the image using imshow()
cv.imshow("Cats", img)
# cv.waitKey(0)

# 2. Reading Videos
# 1st we have to include a source in a variable
capture = cv.VideoCapture("Videos/kitten.mp4")  # for camera use 0,1..

# for video we have to make a while loop
while True:

    isTrue, frame = capture.read()  # isTrue will tell us if there is video or not
    cv.imshow("Video", frame)

    if cv.waitKey(20) & 0xff == ord("s"):
        # if i pass 0 it waits for each frame a key to be press
        # if cv.waitKey(0) & off==ord('s'):
        break