import pickle
from collections import defaultdict
from pathlib import Path
from typing import Any, Optional, Union

import numpy as np
import obspy
import scipy.stats
import torch
from obspy import UTCDateTime
from obspy.clients.fdsn import Client
from obspy.geodetics import locations2degrees
from obspy.taup import TauPyModel
from tqdm import tqdm

import seisbench
import seisbench.util as sbu

from .base import WaveformModel
from .phasenet import PhaseNet
from .team import PhaseTEAM

class DepthPhaseModel:
    """
    Helper class implementing all tools for determining depth from depth phases

    :param time_before: Time included before the P pick in seconds
    :param depth_levels: Array of depth levels to search for
    :param tt_args: Arguments for the :py:class:`TTLookup`
    :param qc_std: Maximum standard deviation to pass quality control.
                   If None, no quality control is applied.
    :param qc_depth: Quality control is only applied to predictions shallower than this depth.
                     If None, quality control is applied to all depth levels.
    """

    def __init__(
        self,
        time_before: float = 12.5,
        depth_levels: Optional[np.ndarray] = None,
        tt_args: Optional[dict[str, Any]] = None,
        qc_std: Optional[float] = None,
        qc_depth: Optional[float] = None,
    ) -> None:
        self.time_before = time_before
        if tt_args is None:
            tt_args = {}
        self._ttlookup = TTLookup(**tt_args)

        self.qc_std = qc_std
        self.qc_depth = qc_depth

        if depth_levels is None:
            self.depth_levels = np.linspace(0, 650, 651)
        else:
            self.depth_levels = depth_levels

    def _prepare_classify_args(
        self,
        p_picks: dict[str, UTCDateTime],
        distances: Optional[dict[str, float]],
        inventory: Optional[obspy.Inventory],
        epicenter: Optional[tuple[float, float]],
    ) -> tuple[dict[str, UTCDateTime], dict[str, float]]:
        distances = self._validate_distances(distances, epicenter, inventory)

        if distances is None:
            distances = self._calculate_distances(inventory, epicenter)

        p_picks = self._validate_p_picks(p_picks, distances)

        return p_picks, distances

    @staticmethod
    def _validate_distances(distances, epicenter, inventory):
        if distances is None and (inventory is None or epicenter is None):
            raise ValueError(
                "Either distances of inventory and epicenter need to be defined."
            )
        elif distances is not None and not (inventory is None or epicenter is None):
            seisbench.logger.warning(
                "Distances and station/event positions are provided. "
                "Will ignore station/event positions."
            )

        if distances is not None:
            distances = {
                DepthPhaseModel._homogenize_station_name(k): v
                for k, v in distances.items()
            }
        return distances

    @staticmethod
    def _validate_p_picks(p_picks, distances):
        p_picks = {
            DepthPhaseModel._homogenize_station_name(k): v for k, v in p_picks.items()
        }
        del_keys = []
        for key in p_picks:
            if key not in distances:
                seisbench.logger.warning(
                    f"No distance for '{key}'. Trace will be ignored"
                )
                del_keys.append(key)
            elif not (15 < distances[key] < 100):
                seisbench.logger.debug(
                    f"Station '{key}' at distance outside 15 to 100 degrees will be ignored."
                )
                del_keys.append(key)
        for key in del_keys:
            del p_picks[key]
        return p_picks

    @staticmethod
    def _calculate_distances(
        inventory: obspy.Inventory, epicenter: tuple[float, float]
    ) -> dict[str, float]:
        distances = {}
        for net in inventory:
            for sta in net:
                if len(sta) == 0:
                    # No channel details given, assume empty location code
                    code = f"{net.code}.{sta.code}."
                    distances[code] = locations2degrees(
                        sta.latitude, sta.longitude, *epicenter
                    )
                    continue

                for channel in sta:
                    code = f"{net.code}.{sta.code}.{channel.location_code}"
                    if code in distances:
                        continue
                    distances[code] = locations2degrees(
                        channel.latitude, channel.longitude, *epicenter
                    )

        return distances

    @staticmethod
    def _homogenize_station_name(trace_id: str) -> str:
        """
        Truncates station name to `NET.STA.LOC`
        """
        if trace_id.count(".") == 3:  # Channel given
            return trace_id[: trace_id.rfind(".")]
        elif trace_id.count(".") == 2:  # Correct format
            return trace_id
        elif trace_id.count(".") < 2:  # Add missing parts
            return trace_id + "." * (2 - trace_id.count("."))
        else:
            raise ValueError(f"Could not parse trace id '{trace_id}'")

    def _line_search_depth(
        self, annotations: obspy.Stream, distances: dict[str, float], caller_name: str
    ) -> sbu.ClassifyOutput:
        annotations = annotations.slice(
            starttime=UTCDateTime(0)
        )  # Make sure sample 0 is at the P arrival

        probabilities = []
        for station, station_annotations in self._group_traces(annotations).items():
            probabilities.append(
                self._backproject_single_station(
                    station_annotations, distances[station]
                )
            )

        if len(probabilities) == 0:
            sbu.ClassifyOutput(
                caller_name,
                depth=np.nana,
                depth_levels=self.depth_levels,
                probabilities=np.nan * self.depth_levels,
                avg_probabilities=np.nan * self.depth_levels,
            )

        probabilities = np.stack(probabilities, axis=0)

        avg_probabilities = scipy.stats.mstats.gmean(
            probabilities, nan_policy="omit", axis=0
        )

        depth = self.depth_levels[np.argmax(avg_probabilities)]

        depth = self._qc_prediction(avg_probabilities, depth)

        return sbu.ClassifyOutput(
            caller_name,
            depth=depth,
            depth_levels=self.depth_levels,
            probabilities=probabilities,
            avg_probabilities=avg_probabilities,
        )

    def _qc_prediction(self, prob: np.ndarray, depth: float) -> float:
        normed_prob = self._norm_label(prob, eps=1e-12)
        mean = np.sum(normed_prob * self.depth_levels)
        var = np.sum(normed_prob * ((self.depth_levels - mean) ** 2))
        std = np.sqrt(var)

        if self.qc_std is not None:
            if std > self.qc_std and (self.qc_depth is None or depth < self.qc_depth):
                seisbench.logger.warning(
                    f"Standard deviation ({std:.1f} km) above quality control "
                    f"limit ({self.qc_std:.1f} km). Returning NaN. You can increase "
                    f"qc_std to get a depth estimate nonetheless, but the result is "
                    f"likely unreliable."
                )
                return np.nan

        return depth

    def _backproject_single_station(
        self,
        station_annotations: obspy.Stream,
        dist: float,
        q_min: float = 0.5,
        truncate: int = 100,
    ):
        """
        Backproject single station

        :param q_min: Quantile to use as lower cutoff for stability
        :param truncate: Number of samples truncated at the end for stability
        """
        prob = np.ones_like(self.depth_levels)
        has_phases = np.zeros(
            len(self.depth_levels), dtype=bool
        )  # Log where at least one phase value was available
        for i, depth in enumerate(self.depth_levels):
            arrivals = self._ttlookup.get_traveltimes(dist, depth)
            for phase in ["pP", "sP"]:
                j = self._ttlookup.phases.index(phase)
                trace = station_annotations.select(channel=f"*_{phase}")[0]
                y_trace = trace.data[:-truncate]

                y_trace = self._smooth_curve(y_trace)
                y_trace = self._norm_label(y_trace)

                if not np.isnan(arrivals[j]):
                    sample = int(arrivals[j] * trace.stats.sampling_rate)
                    if sample < y_trace.shape[0]:
                        prob[i] *= max(y_trace[sample], np.quantile(y_trace, q_min))
                        has_phases[i] = True
                    else:
                        prob[i] *= np.quantile(y_trace, q_min)
                else:
                    prob[i] *= np.quantile(y_trace, q_min)

        prob[~has_phases] = np.nan  # Set all values without any phase to nan

        return prob

    @staticmethod
    def _norm_label(y: np.ndarray, eps: float = 1e-5) -> np.ndarray:
        """
        Norm label trace to sum to 1.
        """
        y = y + eps / y.shape[-1]
        return y / np.sum(y, axis=-1, keepdims=True)

    @staticmethod
    def _smooth_curve(y: np.ndarray, smoothing: float = 10) -> np.ndarray:
        """
        Smooth curve with Gaussian kernel
        """
        if smoothing == 0:
            return y

        kernel = np.arange(-7 * smoothing, 7 * smoothing + 1, 1)
        kernel = np.exp(-0.5 * (kernel / (2 * smoothing)) ** 2)
        kernel /= np.sum(kernel)

        if y.ndim == 1:
            return np.convolve(y, kernel, "same")
        else:
            y_new = np.zeros_like(y)
            for i in range(y.shape[0]):
                y_new[i] = np.convolve(y[i], kernel, "same")
            return y_new

    @staticmethod
    def _group_traces(annotations: obspy.Stream) -> dict[str, obspy.Stream]:
        grouping = defaultdict(obspy.Stream)
        for trace in annotations:
            key = f"{trace.stats.network}.{trace.stats.station}.{trace.stats.location}"
            grouping[key].append(trace)

        return grouping

    def _rebase_streams_for_picks(
        self, stream: obspy.Stream, p_picks: dict[str, UTCDateTime], in_samples: int
    ) -> obspy.Stream:
        """
        Cuts appropriate segments from the stream. Rebases each trace to have the pick at 0.
        """
        selected_stream = obspy.Stream()

        for trace_id, pick in p_picks.items():
            for trace in stream.select(*trace_id.split(".")):
                trace = trace.slice(starttime=pick - self.time_before).copy()
                trace.data = trace.data[:in_samples]

                if (
                    abs(trace.stats.starttime - pick + self.time_before)
                    > trace.stats.delta
                ) or trace.stats.npts != in_samples:
                    # Trace does not start at correct time or is too short
                    continue

                trace.stats.starttime -= pick
                # trace.stats.starttime = UTCDateTime(0) - self.time_before
                selected_stream.append(trace)

        return selected_stream

class TTLookup:
    def __init__(
        self,
        dists: np.ndarray = np.linspace(1, 100, 50),  # In degrees
        depths: np.ndarray = np.linspace(5, 660, 50),  # In kilometers
        model: str = "iasp91",
        phases: tuple[str] = ("P", "pP", "sP"),
    ):
        assert self._linspaced(dists), "TTLookup requires linearly spaced distances"
        assert self._linspaced(depths), "TTLookup requires linearly spaced depths"

        self.model = model

        self.dists = dists
        self.depths = depths
        self.phases = phases

        self._grid = None

        cache = seisbench.cache_aux_root / "ttlookup" / f"{model}.pkl"

        write_cache = False
        if cache.is_file():
            with open(cache, "rb") as f:
                dists, depths, phases, grid = pickle.load(f)

            if not (
                dists.shape == self.dists.shape
                and depths.shape == self.depths.shape
                and np.allclose(dists, self.dists)
                and np.allclose(depths, self.depths)
                and phases == self.phases
            ):
                seisbench.logger.warning("Traveltime cache invalid. Recalculating.")
                write_cache = True
            else:
                self._grid = grid
        else:
            write_cache = True

        if write_cache:
            self._precalculate()
            cache.parent.mkdir(parents=True, exist_ok=True)
            with open(cache, "wb") as f:
                pickle.dump((self.dists, self.depths, self.phases, self._grid), f)

    def _precalculate(self):
        seisbench.logger.warning(
            "Precalculating travel times. This will take a moment. "
            "Results will be cached for future applications."
        )
        from obspy.taup import TauPyModel

        model = TauPyModel(model=self.model)

        self._grid = (
            np.zeros((len(self.depths), len(self.dists), len(self.phases))) * np.nan
        )

        for i, depth in enumerate(tqdm(self.depths, desc="Precalculating traveltimes")):
            for j, dist in enumerate(self.dists):
                arrivals = model.get_travel_times(
                    source_depth_in_km=depth,
                    distance_in_degree=dist,
                    phase_list=self.phases + ("Pdiff",),
                )

                if len(arrivals) == 0:
                    continue

                t0 = arrivals[0].time

                if arrivals[0].phase.name not in ["P", "Pdiff"]:
                    t0 = np.nan

                for k, phase in enumerate(self.phases):
                    for arrival in arrivals:
                        if arrival.phase.name == phase:
                            self._grid[i, j, k] = arrival.time - t0
                            break

    def get_traveltimes(self, dist: float, depth: float):
        frac_dist = (dist - self.dists[0]) / (self.dists[1] - self.dists[0])
        frac_depth = (depth - self.depths[0]) / (self.depths[1] - self.depths[0])

        idx_dist = int(frac_dist)
        idx_depth = int(frac_depth)

        alpha_dist = frac_dist - idx_dist
        alpha_depth = frac_depth - idx_depth

        tt = (
            self._grid[idx_depth, idx_dist] * (1 - alpha_depth) * (1 - alpha_dist)
            + self._grid[idx_depth + 1, idx_dist] * alpha_depth * (1 - alpha_dist)
            + self._grid[idx_depth, idx_dist + 1] * (1 - alpha_depth) * alpha_dist
            + self._grid[idx_depth + 1, idx_dist + 1] * alpha_depth * alpha_dist
        )
        return tt

    @staticmethod
    def _linspaced(x: np.ndarray):
        return np.allclose(x[1] - x[0], x[1:] - x[:-1])

class DepthFinder:
    """
    This class is a high-level interface to the depth phase models.
    It determines event depth at teleseismic distances based on a preliminary location.
    In contrast to the depth phase models, it is not provided with waveforms,
    but automatically downloads data through FDSN.
    Furthermore, it automatically determines first P arrivals using predicted travel times
    and a deep learning picker.

    The processing consists of several steps:

    - determine available station at the time of the event
    - predict P arrivals
    - download waveforms through FDSN
    - repick P arrivals with a deep learning model
    - determine depth with deep learning based depth model

    If waveforms and P wave picks are already available, it is highly recommended to directly use
    the underlying depth phase model instead of this helper.

    .. code-block:: python
        :caption: Example application

        networks = {"GFZ": ["GE"], "IRIS": ["II", "IU"]}  # FDSN providers and networks
        depth_model = sbm.DepthPhaseTEAM.from_pretrained("original")  # A depth phase model
        phase_model = sbm.PhaseNet.from_pretrained("geofon")  # A teleseismic picking model
        depth_finder = DepthFinder(networks, depth_model, phase_model)

    :param networks: Dictionary of FDSN providers and seismic network codes to query
    :param depth_model: The depth phase model to use
    :param phase_model: The phase picking model to use for pick refinement
    :param p_window: Seconds around the predicted P arrival to search for actual arrival
    :param p_threshold: Minimum detection confidence for the primary P phase to include a record
    """

    def __init__(
        self,
        networks: dict[str, list[str]],
        depth_model: DepthPhaseModel,
        phase_model: WaveformModel,
        p_window: float = 10,
        p_threshold: float = 0.15,
    ):
        self.networks = networks
        self.depth_model = depth_model
        self.phase_model = phase_model
        self.p_window = p_window
        self.p_threshold = p_threshold
        self.cache: Optional[
            Path
        ] = None  # If set, cache waveforms at this path and try loading them here

        self.tt_model = TauPyModel(model="iasp91")

        self._clients = {provider: Client(provider) for provider in networks.keys()}
        self._network_to_provider = {
            net: provider for provider, nets in networks.items() for net in nets
        }

        self._setup_inventories()

    def _setup_inventories(self):
        self._inventories = {}
        for provider, networks in self.networks.items():
            for network in networks:
                seisbench.logger.debug(
                    f"Querying inventory for {network} from {provider}"
                )
                self._inventories[network] = self._clients[provider].get_stations(
                    network=network, channel="BH?", level="CHANNEL"
                )

    def _get_stations(self, time: UTCDateTime) -> obspy.Inventory:
        stations = obspy.Inventory()
        for network, inv in self._inventories.items():
            stations += inv.select(time=time)

        return stations

    def get_depth(
        self,
        lat: float,
        lon: float,
        depth: float,
        origin_time: UTCDateTime,
    ) -> sbu.ClassifyOutput:
        """
        Get the depth of an event based on its preliminary latitude, longitude, depth and origin time.
        A depth estimate needs to be input, as it is required to predict preliminary P arrivals.
        This is not a circular reasoning, as depth and origin_time trade off against each other.

        :param lat: Latitude of the event
        :param lon: Longitude of the event
        :param depth: Preliminary depth of the event
        :param origin_time: Preliminary origin time of the event
        """
        stations = self._get_stations(origin_time)

        distances = self.depth_model._calculate_distances(stations, (lat, lon))
        distances = {key: val for key, val in distances.items() if 15 < val < 100}

        p_picks_tt = self._get_picks_tt(origin_time, depth, distances)

        stream = self._get_cache(lat, lon, depth, origin_time)
        if stream is None:
            stream = self._get_waveforms(p_picks_tt)
        self._set_cache(stream, lat, lon, depth, origin_time)

        p_picks = self._repick_dl(p_picks_tt, stream)

        seisbench.logger.debug("Calculating depth")
        classify_outputs = self.depth_model.classify(stream, p_picks, distances)

        return sbu.ClassifyOutput(
            self.__class__.__name__,
            depth=classify_outputs.depth,
            depth_levels=classify_outputs.depth_levels,
            probabilities=classify_outputs.probabilities,
            avg_probabilities=classify_outputs.avg_probabilities,
            p_picks=p_picks,
            p_picks_tt=p_picks_tt,
            distances=distances,
            stream=stream,
        )

    def _get_picks_tt(
        self, origin_time: UTCDateTime, depth: float, distances: dict[str, float]
    ):
        seisbench.logger.debug("Calculating traveltimes")
        p_picks = {}
        for station, dist in distances.items():
            # Assume all station are at 0 km elevation. Error is small enough to be fixed by repicker.
            tt = self._get_traveltime(dist, depth)
            if not np.isnan(tt):
                p_picks[station] = origin_time + tt
        return p_picks

    def _get_traveltime(self, dist_deg: float, source_depth_km: float) -> float:
        arrivals = self.tt_model.get_travel_times(
            source_depth_in_km=source_depth_km,
            distance_in_degree=dist_deg,
            phase_list=["p", "P"],
        )

        if len(arrivals) > 0:
            return arrivals[0].time
        else:
            return np.nan

    def _get_cache(
        self, lat: float, lon: float, depth: float, origin_time: UTCDateTime
    ) -> Optional[obspy.Stream]:
        if self.cache is None:
            return

        ev_cache = self.cache / self._get_event_key(lat, lon, depth, origin_time)
        if ev_cache.is_file():
            if ev_cache.stat().st_size == 0:  # Empty token file
                return obspy.Stream()
            else:
                return obspy.read(ev_cache)

    def _set_cache(
        self,
        stream: obspy.Stream,
        lat: float,
        lon: float,
        depth: float,
        origin_time: UTCDateTime,
    ) -> None:
        if self.cache is None:
            return

        ev_cache = self.cache / self._get_event_key(lat, lon, depth, origin_time)
        if not ev_cache.is_file():
            if len(stream) > 0:
                stream.write(ev_cache)
            else:
                with open(ev_cache, "w"):
                    pass  # Create empty token file

    def _get_event_key(
        self, lat: float, lon: float, depth: float, origin_time: UTCDateTime
    ) -> str:
        return f"{origin_time}__{lat:.3f}__{lon:.3f}__{depth:.2f}.mseed"

    def _get_waveforms(
        self,
        p_picks: dict[str, UTCDateTime],
        time_before: float = 100,
        time_after: float = 300,
    ) -> obspy.Stream:

        bulks = {provider: [] for provider in self.networks.keys()}
        for station, pick in p_picks.items():
            net, sta, loc = station.split(".")
            provider = self._network_to_provider[net]
            bulks[provider].append(
                (net, sta, loc, "BH?", pick - time_before, pick + time_after)
            )

        stream = obspy.Stream()
        for provider, bulk in bulks.items():
            seisbench.logger.debug(f"Querying {provider}")
            stream += sbu.fdsn_get_bulk_safe(self._clients[provider], bulk)

        return stream

    def _repick_dl(
        self, p_picks: dict[str, UTCDateTime], stream: obspy.Stream
    ) -> dict[str, UTCDateTime]:
        seisbench.logger.debug("Repicking")
        ann = self.phase_model.annotate(stream).select(channel="*_P")

        refined_p_picks = {}

        for station, pick in p_picks.items():
            net, sta, loc = station.split(".")
            station_ann = ann.select(network=net, station=sta, location=loc)
            station_ann = station_ann.slice(pick - self.p_window, pick + self.p_window)

            if len(station_ann) != 1:
                continue
            station_ann = station_ann[0]

            if np.max(station_ann.data) < self.p_threshold:
                continue

            refined_p_picks[station] = (
                station_ann.stats.starttime
                + station_ann.times()[np.argmax(station_ann.data)]
            )

        return refined_p_picks

class DepthPhaseNet(PhaseNet, DepthPhaseModel):
    """
    .. document_args:: seisbench.models DepthPhaseNet
    """

    def __init__(
        self,
        phases: str = ("P", "pP", "sP"),
        sampling_rate: float = 20.0,
        depth_phase_args: Optional[dict] = None,
        norm="peak",
        **kwargs,
    ) -> None:
        if depth_phase_args is None:
            depth_phase_args = {}
        PhaseNet.__init__(
            self,
            phases=phases,
            sampling_rate=sampling_rate,
            norm=norm,
            **kwargs,
        )
        DepthPhaseModel.__init__(self, **depth_phase_args)

        self._citation = (
            "Münchmeyer, J., Saul, J. & Tilmann, F. (2023) "
            "Learning the deep and the shallow: Deep learning "
            "based depth phase picking and earthquake depth estimation."
            "Seismological Research Letters (in revision)."
        )

    def forward(self, x: torch.tensor, logits=False) -> torch.tensor:
        y = super().forward(x, logits=True)
        if logits:
            return y
        else:
            return torch.sigmoid(y)

    def annotate(
        self,
        stream: obspy.Stream,
        p_picks: dict[str, UTCDateTime],
        **kwargs,
    ):
        """
        Get depth phase probabilities curves for one event.
        Note that the annotations are aligned to have the P arrival at UTCDateTime(0), i.e.,
        1970-01-01 00:00:00.
        The probability curves are not normalized, there absolute value is therefore meaningless.

        :param stream: Obspy stream to annotate
        :param p_picks: Dictionary of P pick times. Station codes will be truncated to `NET.STA.LOC`.
        :param kwargs: All kwargs are passed to the annotate function of the superclass.
        """
        p_picks = {
            DepthPhaseModel._homogenize_station_name(k): v for k, v in p_picks.items()
        }

        argdict = self.default_args.copy()
        argdict.update(kwargs)

        # Ensure all traces are at the right sampling rate and filtering causes no boundary artifacts
        self.annotate_stream_pre(stream, argdict)
        selected_stream = self._rebase_streams_for_picks(
            stream, p_picks, self.in_samples
        )

        return super().annotate(selected_stream, **kwargs)

    def classify(
        self,
        stream: obspy.Stream,
        p_picks: dict[str, UTCDateTime],
        distances: Optional[dict[str, float]] = None,
        inventory: Optional[obspy.Inventory] = None,
        epicenter: Optional[tuple[float, float]] = None,
        **kwargs,
    ) -> sbu.ClassifyOutput:
        """
        Calculate depth of an event using depth phase picking and a line search over the depth axis.
        Can only handle one event at a time.

        For the line search, the epicentral distances of the stations to the event is required.
        These can either be provided directly or through an inventory and the event epicenter.

        :param stream: Obspy stream to classify
        :param p_picks: Dictionary of P pick times. Station codes will be truncated to `NET.STA.LOC`.
        :param distances: Dictionary of epicentral distances for the stations in degrees
        :param inventory: Inventory for the stations
        :param epicenter: (latitude, longitude) of the event epicenter
        """
        p_picks, distances = self._prepare_classify_args(
            p_picks, distances, inventory, epicenter
        )

        annotations = self.annotate(stream, p_picks, **kwargs)

        return self._line_search_depth(annotations, distances, self.name)

class DepthPhaseTEAM(PhaseTEAM, DepthPhaseModel):
    """
    .. document_args:: seisbench.models DepthPhaseTEAM
    """

    def __init__(
        self,
        phases: str = ("P", "pP", "sP"),
        classes: int = 3,
        sampling_rate: float = 20.0,
        depth_phase_args: Optional[dict] = None,
        norm="peak",
        **kwargs,
    ) -> None:
        if depth_phase_args is None:
            depth_phase_args = {}
        PhaseTEAM.__init__(
            self,
            phases=phases,
            classes=classes,
            sampling_rate=sampling_rate,
            norm=norm,
            **kwargs,
        )
        DepthPhaseModel.__init__(self, **depth_phase_args)

        self._citation = (
            "Münchmeyer, J., Saul, J. & Tilmann, F. (2023) "
            "Learning the deep and the shallow: Deep learning "
            "based depth phase picking and earthquake depth estimation."
            "Seismological Research Letters (in revision)."
        )

    def annotate(
        self,
        stream: obspy.Stream,
        p_picks: dict[str, UTCDateTime],
        **kwargs,
    ):
        """
        Get depth phase probabilities curves for one event.
        Note that the annotations are aligned to have the P arrival at UTCDateTime(0), i.e.,
        1970-01-01 00:00:00.
        The probability curves are not normalized, there absolute value is therefore meaningless.

        :param stream: Obspy stream to annotate
        :param p_picks: Dictionary of P pick times. Station codes will be truncated to `NET.STA.LOC`.
        :param kwargs: All kwargs are passed to the annotate function of the superclass.
        """
        p_picks = {
            DepthPhaseModel._homogenize_station_name(k): v for k, v in p_picks.items()
        }

        argdict = self.default_args.copy()
        argdict.update(kwargs)

        # Ensure all traces are at the right sampling rate and filtering causes no boundary artifacts
        self.annotate_stream_pre(stream, argdict)
        selected_stream = self._rebase_streams_for_picks(
            stream, p_picks, self.in_samples
        )

        return super().annotate(selected_stream, **kwargs)

    def classify(
        self,
        stream: obspy.Stream,
        p_picks: dict[str, UTCDateTime],
        distances: Optional[dict[str, float]] = None,
        inventory: Optional[obspy.Inventory] = None,
        epicenter: Optional[tuple[float, float]] = None,
        **kwargs,
    ) -> sbu.ClassifyOutput:
        """
        Calculate depth of an event using depth phase picking and a line search over the depth axis.
        Can only handle one event at a time.

        For the line search, the epicentral distances of the stations to the event is required.
        These can either be provided directly or through an inventory and the event epicenter.

        :param stream: Obspy stream to classify
        :param p_picks: Dictionary of P pick times. Station codes will be truncated to `NET.STA.LOC`.
        :param distances: Dictionary of epicentral distances for the stations in degrees
        :param inventory: Inventory for the stations
        :param epicenter: (latitude, longitude) of the event epicenter
        """
        p_picks, distances = self._prepare_classify_args(
            p_picks, distances, inventory, epicenter
        )

        annotations = self.annotate(stream, p_picks, **kwargs)

        return self._line_search_depth(annotations, distances, self.name)


import matplotlib.pyplot as plt
import numpy as np
import os

class GameOfLife_GridDraw():
    def __init__(self, state, grid_color='dimgrey'):
        self.current_state = state
        self.grid_color = grid_color
        self.DrawSquareGrid()

    def DrawSquareGrid(self):
        state = self.current_state.copy()
        state.reverse()

        plt.clf()

        plt.xticks(np.arange(0, len(state) + 1, 1))
        plt.yticks(np.arange(0, len(state) + 1, 1))
        plt.xlim(0, len(state))
        plt.ylim(0, len(state))

        blacks = []
        whites = []

        for i in range(len(state)):
            for j in range(len(state[i])):
                x = np.arange(j, j + 1, 0.02)
                y = np.arange(i, i + 1, 0.02)
                for tx in x:
                    for ty in y:
                        if state[i][j] == 1:
                            blacks.append([tx, ty])
                        else:
                            whites.append([tx, ty])
        blacks = np.array(blacks)
        whites = np.array(whites)
        plt.get_current_fig_manager().canvas.set_window_title('Game of Life')
        if len(blacks) > 0:
            plt.scatter(blacks[:, 0], blacks[:, 1], color='black', marker='s', s=12)
        if len(whites) > 0:
            plt.scatter(whites[:, 0], whites[:, 1], color='white', marker='s', s=12)
        plt.tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)
        plt.grid(color=self.grid_color)
        plt.show(block=False)
        plt.pause(1)
        return

def valid_index(i, j, n):
    return bool(i>=0 and i<n and j>=0 and j<n)
def alive_neighbours(state, i, j, n):
    dx = [1, -1, 0, 0, -1, 1, -1, 1]
    dy = [0, 0, 1, -1, -1, -1, 1, 1]
    alive_count = 0
    for it in range(len(dx)):
        alive_count += int(valid_index(i + dx[it], j + dy[it], n) and state[i + dx[it]][j + dy[it]]==1)
    return alive_count

def AliveOrDead(alive,no_of_alive_neighbours):
    if alive:
        if no_of_alive_neighbours in [2,3]:
            return True
        else:
            return False
    else:
        if no_of_alive_neighbours == 3:
            return True
        else:
            return False

def next_generation(state, n):
    next_state = []
    for i in range(n):
        next_state.append([])

    for i in range(len(state)):
        for j in range(len(state[i])):
            next_state[i].append(int(AliveOrDead(bool(state[i][j]),alive_neighbours(state,i,j,n))))
    return next_state

def print_generation(state,generation_count):
    os.system('cls')
    print(f'Generation {generation_count}: ')
    for i in range(len(state)):
        for j in range(len(state[i])):
            print(state[i][j], end=' ')
        print()
    GameOfLife_GridDraw(state)
    return

def Conway_s_Game_of_Life(state, no_of_generation):
    for i in range(no_of_generation):
        print_generation(state,i+1)
        state = next_generation(state,len(state))
    return

state = [
         [0,0,0,0,0],
         [0,0,1,1,1],
         [0,1,1,1,0],
         [0,0,0,0,0],
         [0,0,0,0,0]
]

Conway_s_Game_of_Life(state, no_of_generation=10)


def repetimos(f,v):
  if v == 1:
    print(f)
  else:
    return print(f*v)

if __name__ == '__main__':
  frase = str(input('Dinos algo: '))
  veces = int(input('Cuantas veces lo repetimos: '))
  repetimos(frase,veces)


#영화관에 관객이 120명일 때 티켓 가격은 5달러이다. 영화상영비용은 180달러이며 한 사람당 추가비용 0.04달러가 든다
#티켓이 0.1달러 싸질때마다 관객이 15명 늘어난다. 티켓이 얼마일 때 최대수익을 얻는가

def make_benefit(people, ticket_price):
    income = people * ticket_price
    outcome = 180 + people * 0.04
    result = income - outcome
    return(result)

people = 120
ticket_price = 5
a = list() # 빈 리스트 생성
b = list()

for i in range(0, 50):
    money = make_benefit(people, ticket_price)
    ticket_price = round(ticket_price - 0.1, 2)
    people = people + 15
    a.append(money)
    b.append(ticket_price)

print(b[a.index(max(a))-1]) #a 리스트에서 최댓값 위치 = b 리스트 티켓값 위치
print(max(a))


from django import forms
from django.db import transaction
from lims.settings import BASE_DIR
from lims.importer import *
from lims.models import Accession, Project, Sample, SeedPacket
import os
import datetime
import time
import re

class SampleImportForm(forms.Form):

    csv_file = forms.FileField()

    def import_from_csv(self, raw_input_file):

        empty_rejects_directory()

        input_file = FileWrapper(raw_input_file)
        reader = LimsCSVReader(input_file, delimiter=",", quotechar="\"")
        row_number = 0
        for row in reader:
            try:
                with transaction.atomic():
                    self.handle_row(row)
            except Exception as e:
                with open(get_reject_filepath(str(e)), 'a') as rejects_file:
                    rejects_file.write(input_file.last_line)

        return zip_rejects()

    def clean_row(self, row):

        for key in row:
            row[key] = row[key].strip()

    def get_accession(self, row):
        return Accession.objects.get(accession=row['accession'])

    def get_project(self, row):
        return Project.objects.get(project=row['project'])

    def make_sample(self, row):
        sample = Sample()
        sample.accession = self.get_accession(row)
        sample.sample = row['sample']
        sample.sranumber = row['sranumber']
        sample.strategy = row['strategy']
        sample.sequenceinstrument = row['sequenceinstrument']
        sample.sequencingcompany = row['sequencingcompany']
        sample.tissue_type = row['tissue_type']
        sample.time_point = row['time_point']
        sample.dev_time_point = row['dev_time_point']
        sample.file_names = row['file_names']
        sample.file_location = row['file_location']
        sample.notes = row['notes']
        sample.trackingnumber = row['trackingnumber']
        sample.coverage = row['coverage']
        sample.pcrfree = row['pcrfree'] == "YES"
        sample.category = Sample.ESampleType[row['category']]
        sample.save()
        sample.project.add(self.get_project(row))
        return sample

    def handle_row(self, row):
        self.clean_row(row)
        sample = self.make_sample(row)
        sample.save()

class SeedpacketImportForm(forms.Form):

    csv_file = forms.FileField()

    def import_from_csv(self, raw_input_file):

        empty_rejects_directory()

        input_file = FileWrapper(raw_input_file)
        reader = LimsCSVReader(input_file, delimiter=",", quotechar="\"")
        row_number = 0
        for row in reader:
            try:
                with transaction.atomic():
                    self.handle_row(row)
            except Exception as e:
                with open(get_reject_filepath(str(e)), 'a') as rejects_file:
                    rejects_file.write(input_file.last_line)

        return zip_rejects()

    def clean_row(self, row):
        for key in row:
            row[key] = row[key].strip()

    def get_accession(self, row):
        return Accession.objects.get(accession=row['accession'])

    def make_seed_packet(self, row):
        packet = SeedPacket()
        packet.accession = self.get_accession(row)
        packet.notes = row['notes']
        packet.quantity = int(row['quantity'])
        packet.location = row['location']
        if row['date_collected'] != "" and row['date_collected'] != "NA":
            packet.datecollected = datetime.date(*(time.strptime(row['date_collected'] + " 00:00 -0500", "%m/%d/%Y %H:%M %z")[0:3]))
        if row['parenta'] != "" and row['parenta'] != "NA":
            packet.parenta_id = int(row['parenta'])
        if row['parentb'] != "" and row['parentb'] != "NA":
            packet.parentb_id = int(row['parentb'])
        return packet

    def handle_row(self, row):
        self.clean_row(row)
        packet = self.make_seed_packet(row)
        packet.save()


from flask import Flask,render_template,flash,request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import and_,or_

app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']='mysql://root@localhost/book'
app.config['SECRET_KEY']='asd'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False

db=SQLAlchemy(app)

class Author(db.Model):
    __tablename__='author'
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    name=db.Column(db.String(10))

class Book(db.Model):
    __tablename__='book'
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    name=db.Column(db.String(10))
    author_id=db.Column(db.Integer,db.ForeignKey('author.id'))

def first():
    au1=Author(id=1,name='张三')
    au2=Author(id=2,name='李四')
    au3=Author(id=3,name='王五')
    db.session.add_all([au1,au2,au3])
    db.session.commit()
    bo1=Book(id=1,name='one',author_id=au1.id)
    bo2=Book(id=2,name='two',author_id=au1.id)
    bo3=Book(id=3,name='three',author_id=au2.id)
    bo4=Book(id=4,name='four',author_id=au2.id)
    bo5=Book(id=5,name='five',author_id=au3.id)
    bo6=Book(id=6,name='six',author_id=au3.id)
    db.session.add_all([bo1,bo2,bo3,bo4,bo5,bo6])
    db.session.commit()

@app.route('/')
def a():
    authors=Author.query.all()
    books=Book.query.all()
    return render_template('book.html',a=authors,b=books)

@app.route('/add',methods=['GET','POST'])
def b():
    q='1'
    authors = Author.query.all()
    books = Book.query.all()
    author=request.form.get('author')
    book=request.form.get('book')
    if request.method=='POST':
        au=Author.query.filter(Author.name==author).first()
        if au:
            bo = Book.query.filter(and_(Book.author_id == au.id, Book.name == book)).first()
            if bo:
                flash('这本书已经存在')
            else:
                bo=Book(name=book,author_id=au.id)
                db.session.add(bo)
                db.session.commit()
        else:
            au=Author(name=author)
            db.session.add(au)
            db.session.commit()
            bo=Book(name=book,author_id=au.id)
            db.session.add(bo)
            db.session.commit()
    return render_template('book.html',q=q,a=authors,b=books)

@app.route('/delete_book',methods=['GET','POST'])
def c():
    w='q'
    authors = Author.query.all()
    books = Book.query.all()
    author=request.form.get('author')
    book=request.form.get('book')
    if request.method=='POST':
        au = Author.query.filter(Author.name == author).first()
        if au:
            bo=Book.query.filter(and_(Book.name==book,Book.author_id==au.id)).first()
            if bo:
                db.session.delete(bo)
                db.session.commit()
            else:
                flash('没有该作者写的这本书')
        else:
            flash('没有该书')
    return render_template('book.html',a=authors,b=books,w=w)

@app.route('/delete_author',methods=['POST','GET'])
def d():
    e='1'
    authors = Author.query.all()
    books = Book.query.all()
    if request.method=='POST':
        author = request.form.get('author')
        au=Author.query.filter(Author.name==author).first()
        if au:
            bo=Book.query.filter(Book.author_id==au.id).all()
            for x in bo:
                db.session.delete(x)
                db.session.commit()
            db.session.delete(au)
            db.session.commit()
        else:
            flash('没有该作者')
    return render_template('book.html',a=authors,b=books,e=e)

@app.route('/update_book',methods=['GET','POST'])
def e():
    r='1'
    authors = Author.query.all()
    books = Book.query.all()
    if request.method=='POST':
        author=request.form.get('author')
        book=request.form.get('book')
        book2=request.form.get('book2')
        au=Author.query.filter(Author.name==author).first()
        if au:
            bo=Book.query.filter(and_(Book.name==book,Book.author_id==au.id)).first()
            if bo:
                bo.name=book2
                db.session.commit()
            else:
                flash('没有该书')
        else:
            flash('没有该书')
    return render_template('book.html',a=authors,b=books,r=r)

if __name__ == '__main__':
    db.drop_all()
    db.create_all()
    first()
    app.run(debug=True)


from pymongo import MongoClient
from bson.objectid import ObjectId

class Database:

    def __init__(self, db_url, db_name):
        client = MongoClient(db_url)
        db = client[db_name]
        self.users = db.users
        self.units = db.units
        self.addresses = db.addresses

    def create_user(self, email, first_name, last_name):
        user = {
            'email': email,
            'first_name': first_name,
            'last_name': last_name
        }
        user_id = self.users.insert_one(user).inserted_id
        return str(user_id)

    def update_user(self, uid, input_dict):
        user_id = ObjectId(uid)
        update_dict = {}
        user_keys = ['email', 'first_name', 'last_name']
        for key in input_dict:
            if key in user_keys:
                update_dict[key] = input_dict[key]
        if not update_dict:
            return False
        res = self.users.update_one({'_id': user_id}, {'$set': update_dict}, False)
        if (res.modified_count == 0):
            return False
        else:
            return True

    def delete_user(self, uid):
        user_id = ObjectId(uid)
        res_user = self.users.delete_one({'_id': user_id})
        for unit in self.units.find({'creator_id': user_id}):
            address_id = unit['address_id']
            self.addresses.delete_one({'_id': address_id})
        self.units.delete_many({'creator_id': user_id})
        if (res_user.deleted_count == 0):
            return False
        else:
            return True

    def get_user(self, uid):
        user_id = ObjectId(uid)
        user_doc = self.users.find_one({'_id': user_id})
        if user_doc:
            return {
                'uid': str(user_doc['_id']),
                'email': user_doc['email'],
                'first_name': user_doc['first_name'],
                'last_name': user_doc['last_name']
            }
        else:
            return None

    def get_all_users(self):
        to_return = []
        for user_doc in self.users.find():
            user_dict = {
                'uid': str(user_doc['_id']),
                'email': user_doc['email'],
                'first_name': user_doc['first_name'],
                'last_name': user_doc['last_name']

            }
            to_return.append(user_dict)
        return to_return

    def get_user_by_email(self, email):
        user_doc = self.users.find_one({'email': email})
        if user_doc:
            return {
                'uid': str(user_doc['_id']),
                'email': user_doc['email'],
                'first_name': user_doc['first_name'],
                'last_name': user_doc['last_name']
            }
        else:
            return None

    def create_unit(self, block, street, pin, city, country, lat, lng, num_rooms, num_bathrooms, sqft, creator_id_str):
        address = {
            'block_number': block,
            'street_name': street,
            'postal_code': pin,
            'city': city,
            'country': country,
            'coordinates': [lat, lng]
        }
        address_id = self.addresses.insert_one(address).inserted_id
        creator_id = ObjectId(creator_id_str)
        unit = {
            'address_id': address_id,
            'num_rooms': num_rooms,
            'num_bathrooms': num_bathrooms,
            'sqft': sqft,
            'creator_id': creator_id
        }
        unit_id = self.units.insert_one(unit).inserted_id
        return str(unit_id)

    def update_unit(self, uid, unit_dict):
        address_updates = {}
        unit_updates = {}
        unit_keys = ['num_rooms', 'num_bathrooms', 'sqft']
        address_keys = ['block_number', 'street_name', 'postal_code', 'city', 'country', 'coordinates']
        for key in unit_dict:
            if key in unit_keys:
                unit_updates[key] = unit_dict[key]
        if 'address' in unit_dict:
            for key in unit_dict['address']:
                if key in address_keys:
                    address_updates[key] = unit_dict['address'][key]
        unit_id = ObjectId(uid)
        to_update = self.units.find_one({'_id': unit_id})
        if not to_update:
            return False
        address_id = to_update['address_id']
        creator_id = to_update['creator_id']
        creator = self.users.find_one({'_id': creator_id})
        if not creator:
            return False
        updated_count = 0

        if unit_updates:
            res_unit = self.units.update_one({'_id': unit_id}, {'$set': unit_updates}, False)
            updated_count += res_unit.modified_count
        if address_updates:
            res_addr = self.addresses.update_one({'_id': address_id}, {'$set': address_updates}, False)
            updated_count += res_addr.modified_count
        if updated_count == 0:
            return False
        else:
            return True

    def delete_unit(self, uid):
        unit_id = ObjectId(uid)
        to_delete = self.units.find_one({'_id': unit_id})
        address_id = to_delete['address_id']
        res_unit = self.units.delete_one({'_id': unit_id})
        res_addr = self.addresses.delete_one({'_id': address_id})
        if (res_unit.deleted_count > 0 or res_addr.deleted_count > 0):
            return True
        else:
            return False

    def get_unit(self, uid):
        unit_id = ObjectId(uid)
        unit_doc = self.units.find_one({'_id': unit_id})
        if unit_doc:
            unit = {
                'uid': uid,
                'num_rooms': unit_doc['num_rooms'],
                'num_bathrooms': unit_doc['num_bathrooms'],
                'sqft': unit_doc['sqft'],
                'creator_id': str(unit_doc['creator_id'])
            }
            address_doc = self.addresses.find_one({'_id': unit_doc['address_id']})
            if not address_doc:
                return None
            address = {
                'block_number': address_doc['block_number'],
                'street_name': address_doc['street_name'],
                'postal_code': address_doc['postal_code'],
                'city': address_doc['city'],
                'country': address_doc['country'],
                'coordinates': address_doc['coordinates']
            }
            unit['address'] = address
            return unit
        else:
            return None

    def get_all_units(self, uid):
        user_id = ObjectId(uid)
        to_return = []
        for unit_doc in self.units.find({'creator_id': user_id}):
            unit = {
                'uid': str(unit_doc['_id']),
                'num_rooms': unit_doc['num_rooms'],
                'num_bathrooms': unit_doc['num_bathrooms'],
                'sqft': unit_doc['sqft'],
                'creator_id': str(unit_doc['creator_id'])
            }
            address_doc = self.addresses.find_one({'_id': unit_doc['address_id']})
            if not address_doc:
                continue;
            address = {
                'block_number': address_doc['block_number'],
                'street_name': address_doc['street_name'],
                'postal_code': address_doc['postal_code'],
                'city': address_doc['city'],
                'country': address_doc['country'],
                'coordinates': address_doc['coordinates']
            }
            unit['address'] = address
            to_return.append(unit)
        return to_return


import os

def get_last_model_path(save_dir, modelinit):
    """
    Get the last saved model
    :param save_dir: Directory where models are saved
    :return: the full path of the last model, next epoch (if we will continue training)
    """
    nexte = 2
    model_name = modelinit
    for file in os.listdir(save_dir):
        if file.endswith(".h5") and file.__contains__("_"):
            ce = int(file.split('_')[1][:-3])+1
            if ce>nexte:
                nexte = ce
                model_name = file
    full_path = os.path.join(save_dir, model_name)
    return full_path, nexte

def remove_except_last_model(save_dir, modelinit):
    """
    Remove all model files except the last one
    :param save_dir: Directory where models are saved
    :return:
    """
    fullp, nexte = get_last_model_path(save_dir, modelinit)
    laste = nexte
    for file in os.listdir(save_dir):
        if file.endswith(".h5") and file.__contains__("_"):
            ce = int(file.split('_')[1][:-3])+1
            if ce<laste:
                model_name = file
                full_path = os.path.join(save_dir, model_name)
                os.remove(full_path)
    return


#
# @lc app=leetcode id=203 lang=python3
#
# [203] Remove Linked List Elements
#
# https://leetcode.com/problems/remove-linked-list-elements/description/
#
# algorithms
# Easy (35.31%)
# Total Accepted:    209K
# Total Submissions: 591.7K
# Testcase Example:  '[1,2,6,3,4,5,6]\n6'
#
# Remove all elements from a linked list of integers that have value val.
#
# Example:
#
#
# Input:  1->2->6->3->4->5->6, val = 6
# Output: 1->2->3->4->5
#
#
#
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

## Recursion
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        if not head:
            return head
        if head.val == val:
            return self.removeElements(head.next, val)
        else:
            head.next = self.removeElements(head.next, val)
            return head

## Iteration
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        if not head:
            return head
        dummy = ListNode(0)
        cur = head
        curnew=dummy
        while cur:
            if cur.val !=val:
                curnew.next = cur
                curnew = curnew.next
            cur=cur.next
        curnew.next = None
        return dummy.next


# Created by Alexander W. Zummo and Sophie Meyer
# runs simulations of the package drops described in the project
# the RNG class is used for generating random numbers
# numpy used for means
# scipy used for normal distribution and CDF tools
# matplotlib used to create graphs

import math
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

class RNG:
    def __init__(self, x_0, a, c, k):
        self.seed = x_0
        self.multiplier = a
        self.increment = c
        self.modulus = k
        self.nums = []

    def generate(self, n=1000):
        """
        Generates n random numbers and returns as a list
        :param n: number of random numbers to generate
        :return: a list of random numbers of length n
        """
        ret = []
        x_i = self.seed
        for i in range(n):
            x_i = (x_i * self.multiplier + self.increment) % self.modulus  # x_i = (x_i-1 * a + c) % K
            ret.append(x_i / self.modulus)  # u_i = x_i / K
        self.nums = ret
        return ret

    def show_51(self):
        "returns u_51, u_52, and u_53. Used for the report"
        nums = self.generate(55)
        return nums[50], nums[51], nums[52]

class CDF:
    def __init__(self, observations):
        self.data = observations
        self.data.sort()
        self.size = len(self.data)

    def plt(self, x):
        """returns Probability that an observation is Less Than (PLT) the parameter x"""
        inc_p = 1 / self.size  # incremental probability, I.E. 1 / num observations
        p = 0  # total probability to return
        # i = 0
        for j in range(self.size):
            i = self.data[j]
            if i < x:
                p += inc_p # for each observation less than x, increase P[z < X]
            else:
                break
        # print("value:", x, "was less than", i)
        return p

    def get_graphable(self):
        """returns a tuple: first list is x axis, second list is y axis"""
        x_axis = []
        y_axis = []
        inc_p = 1 / self.size  # incremental probability
        total = 0
        for j in range(self.size):
            total += inc_p
            x_axis.append(total)
            y_axis.append(self.data[j])
        return x_axis, y_axis

def map_rand(x):
    """maps the randomly generated number to a value for the radius"""
    t = 57
    a = 1/t
    return ((-2 * math.log(1-x)) // a**2) ** (1/2)  # this is the inverse cdf

def simulate_package_drop(queue, size):
    """runs a simulation of the package dropping process and returns the sample mean"""
    total = 0.0
    # histogram = []  # I used this to collect data that verified that this method would produce the correct pdf
    for i in range(size):
        x = queue.pop(0)
        total += map_rand(x)
        # histogram.append(mapped)
    # print(histogram)
    return total / size

def convert_to_z(x, n, mean, std_dev):
    """return z score given parameter x of normal dist with parameters n, mean, and std_dev"""
    num = (x - mean)
    den = (std_dev / (n ** .5))
    return num / den

rng = RNG(1000, 24693, 3967, 2**17)
rand_queue = rng.generate(229900)  # I'm using a queue so that each random number is used exactly once
sample_sizes = [10, 30, 50, 100, 150, 250, 500, 1000]

means = {}
for size in sample_sizes:
    means[size] = []
    for i in range(110):
        mean = simulate_package_drop(rand_queue, size)
        means[size].append(mean)

mean_x = 57 * ((math.pi / 2) ** .5)  # theoretical overall mean
std_dev_x = ((4 - math.pi) / (2 * ((1 / 57) ** 2))) ** .5  # theoretical overall standard distribution

z_conv = {}  # dict with keys as n values and values as lists of z scores

for size in sample_sizes:
    k_i = means[size]
    z_conv[size] = []  # initialize list
    for m in k_i:
        z_i = convert_to_z(m, size, mean_x, std_dev_x)  # convert distances to z scores
        z_conv[size].append(z_i)

graphable = {}  # keys as n values, values as [[110 cdf probs], [110 z scores], [7 absolute differences]]
diffs = {}  # keys as n values, values as lists of the 7 absolute differences
j_vals = [-1.4, -1.0, -0.5, 0, 0.5, 1.0, 1.4]
for n in z_conv.keys():
    graphable[n] = []
    cdf = CDF(z_conv[n])
    temp = cdf.get_graphable()
    graphable[n] = [temp[0], temp[1]]
    diffs[n] = []
    graphable[n].append([])
    for j in j_vals:
        fn_zj = cdf.plt(j)  # use CDF object to get P[z<Z]
        ncdf = norm.cdf(j)  # use scipy to get phi(z)
        diffs[n].append(abs(fn_zj - ncdf))
        graphable[n][2].append(fn_zj)  # I used graphable[2] to store the 7 CDF values

f = open("clt_results.txt", 'w')
print("Printing results to file...")
for n in diffs.keys():
    print("n =", n, file=f)
    print("MAD:", max(diffs[n]), file=f)
    for i in diffs[n]:
        print(i, file=f)
    print("=" * 40, file=f)
print("Done")

print("Creating graphs...")
for n in sample_sizes:
    print("n =", n)
    mu = 0
    variance = 1
    sigma = math.sqrt(variance)
    x = np.linspace(mu - 2.5*sigma, mu + 2.5*sigma, 100)
    y = norm.cdf(x, mu, sigma)
    plt.plot(x, y)  # plot CDF of normal distribution
    plt.plot(j_vals, graphable[n][2], marker='.', linestyle='None')  # plot 7 CDF values
    max_diff = max(diffs[n])
    j_ind = diffs[n].index(max_diff)
    x = j_vals[j_ind]
    y = graphable[n][2][j_ind]
    plt.axvspan(x-.01, x+.01 , color='red', alpha=0.5)
    plt.plot(x, y, marker='o', color='red')  # Highlight CDF value corresponding to the MAD
    plt.ylabel("cumulative probability")
    plt.xlabel("z value")
    plt.title("n = " + str(n))
    red_patch = mpatches.Patch(color='red', label='MADn')
    blue_patch = mpatches.Patch(color='blue', label='Normal CDF')
    orange_patch = mpatches.Patch(color='orange', label='Empirical CDF')
    plt.legend(handles=[red_patch, blue_patch, orange_patch])
    plt.savefig("n"+str(n)+".png")
    plt.clf()  # clear plot to start over with next
print("Done")


def monitor_process(key_word, cmd):
    p1 = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
    p2 = subprocess.Popen(['grep', key_word], stdin=p1.stdout, stdout=subprocess.PIPE)
    p3 = subprocess.Popen(['grep', '-v', 'grep'], stdin=p2.stdout, stdout=subprocess.PIPE)
    lines = p3.stdout.readlines()
    if len(lines) > 0:
        return
    sys.stderr.write('process[%s] is lost, run [%s]\n' % (key_word, cmd))
    subprocess.call(cmd, shell=True)

def linux_shell(key_word, cmd):
    p1 = subprocess.Popen([key_word, cmd], stdout=subprocess.PIPE)
    return p1


from dcn_v2 import DCNv2, DCN
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import Tensor
from typing import List
from torch.nn.modules.batchnorm import BatchNorm2d

from torch.nn.modules.conv import Conv2d
from mmdet3d.models import NECKS

def offset_activation_function(x):
    return torch.tan(torch.sigmoid(x) * 3.14 - 1.57)

@NECKS.register_module()
class VideoFeatureFusion2(nn.Module):
    def __init__(self, channels_in: int, frames: int, repeat: int, conv_config=(3, 1, 1, 1)) -> None:
        super().__init__()
        self.frames = frames
        self.repeat = repeat
        chi = channels_in * 2
        k, s, p, d = conv_config

        self.conv_in = nn.Sequential(
            nn.Conv2d(channels_in, chi, 4, 2, 1, bias=False),
            nn.BatchNorm2d(chi),
            nn.ReLU(inplace=True),
        )
        # self.conv_in = nn.Sequential(
        #     nn.Conv2d(channels_in, chi, 3, 1, 1, bias=False),
        #     nn.BatchNorm2d(chi),
        #     nn.ReLU(inplace=True),
        # )
        self.conv_out = nn.Sequential(
            nn.ConvTranspose2d(chi, channels_in, 4, 2, 1, bias=False),
            nn.BatchNorm2d(channels_in),
            nn.Conv2d(channels_in, channels_in, 3, 1, 1, bias=False),
            nn.BatchNorm2d(channels_in),
            nn.ReLU(inplace=True),
        )
        # self.conv_out = nn.Sequential(
        #     nn.ConvTranspose2d(chi, channels_in, 3, 1, 1, bias=False),
        #     nn.BatchNorm2d(channels_in),
        #     nn.Conv2d(channels_in, channels_in, 3, 1, 1, bias=False),
        #     nn.BatchNorm2d(channels_in),
        #     nn.ReLU(inplace=True),
        # )

        self.conv_adp = nn.Sequential(
            DCN(chi, chi, 3, 1, 1),
            nn.ReLU(inplace=True),
            DCN(chi, chi, 3, 1, 1),
            nn.ReLU(inplace=True),
        )
        self.conv_alpha = nn.Conv2d(chi, 1, 3, 1, 1)
        self.conv_alpha.weight.data.zero_()
        self.conv_alpha.bias.data.zero_()
        self.conv_offset_mask = nn.Sequential(
            nn.Conv2d(chi, chi, 7, 1, 3),
            nn.ReLU(inplace=True),
            nn.Conv2d(
            chi,
            k * k * 3,
            3, 1, 1
            )
        )
        # self.conv_offset_mask.weight.data.zero_()
        # self.conv_offset_mask.bias.data.zero_()
        self.conv_offset_uncertainty = nn.Conv2d(chi, 1, 3, 1, 1)
        self.dcn = DCNv2(
            chi,
            chi,
            k,
            stride=s,
            padding=p,
            dilation=d,
        )
        self.conv_fusion = FusionModule(2, chi)
        # self.cheat = nn.Parameter(torch.zeros(2, 48, 160) + 0.5)

    def forward(self, features: List[Tensor]):
        assert len(features) == self.frames
        bs, ch, h, w = features[0].shape
        ret_offset_list: List[Tensor] = [None] * (self.frames - 1)
        ret_offset_uncertainty_list: List[Tensor] = [None] * (self.frames - 1)
        feature_ori = features[0]
        features = [self.conv_in(f) for f in features]
        for r in range(self.repeat):
            for t in range(self.frames - 1):
                f_prev = features[-t - 1]
                f_current = features[-t - 2]
                f_add = self.conv_fusion(f_current, f_prev)
                # f_add = f_current + self.conv_adp(f_prev)
                out = self.conv_offset_mask(f_add)
                o1, o2, mask = torch.chunk(out, 3, dim=1)
                offset = torch.cat((o1, o2), dim=1)
                mask = torch.sigmoid(mask)
                f_align = self.dcn(f_prev, offset.detach(), mask)
                alpha = self.conv_alpha(f_add).sigmoid()
                f_out = f_align * alpha + f_current * (1 - alpha)
                features[-t - 2] = f_out
                if r == self.repeat - 1:
                    ret_offset_list[-t - 1] = offset
                    ret_offset_uncertainty_list[-t - 1] = self.conv_offset_uncertainty(f_add)

        ret_offset_list = [offset_activation_function(t.view(bs, 2, -1, h // 2, w // 2).mean(2)) if t is not None else None for t in ret_offset_list]
        # ret_offset_list = [torch.stack([offset_activation_function(self.cheat)] * bs) for t in ret_offset_list]
        return self.conv_out(features[0]) + feature_ori, ret_offset_list, ret_offset_uncertainty_list

class FusionModule(nn.Module):
    def __init__(self, n_level, channels_in) -> None:
        super().__init__()
        self.n_level = n_level
        for i in range(n_level):
            ch1 = channels_in * 2 ** i
            ch2 = channels_in * 2 ** (i + 1)
            conv_down = nn.Sequential(
                DCN(ch1, ch2, 4, 2, 1),
                nn.BatchNorm2d(ch2),
                nn.ReLU(inplace=True),
            )
            # conv_up = nn.Sequential(
            #     nn.ConvTranspose2d(ch2, ch1, 4, 2, 1),
            #     nn.BatchNorm2d(ch1),
            #     nn.ReLU(inplace=True),
            # )
            conv_up = nn.Sequential(
                nn.Upsample(scale_factor=2, mode='bilinear'),
                DCN(ch2, ch1, 3, 1, 1),
                nn.ReLU(inplace=True)
            )
            setattr(self, 'conv_down_{}'.format(i), conv_down)
            setattr(self, 'conv_up_{}'.format(i), conv_up)

    def forward(self, a, b):
        f = a + b
        return self.recur(f, 0)

    def recur(self, f, i):
        if i == self.n_level: return f
        conv_down = getattr(self, 'conv_down_{}'.format(i))
        conv_up = getattr(self, 'conv_up_{}'.format(i))
        f_down = conv_down(f)
        f_down = self.recur(f_down, i + 1)
        f_down_up = conv_up(f_down)
        return F.relu(f + f_down_up, inplace=True)


from sys import stdin

if __name__ == '__main__':
    num_of_digits, goal = list(map(int, stdin.readline().rstrip().split()))
    digits = list(map(int, stdin.readline().rstrip().split()))
    left, right = 0, 0
    count = 0

    total = digits[0]
    while left <= right < len(digits):
        if total <= goal:
            if total == goal:
                count += 1

            if right + 1 < len(digits):
                right += 1
                total += digits[right]
            else:
                break
        else:
            total -= digits[left]
            left += 1
            if right < left < len(digits):
                right = left
                total = digits[left]

    print(count)


from dataclasses import dataclass
import os
from typing import Iterable, Union
from telebot import TeleBot
from telebot.types import BotCommand, CallbackQuery, Message, BotCommandScope
from services.bot.commands.start_conversation import prepare_text_to_output_in_tg
from services.logs import log

bot = TeleBot(token=os.environ['TOKEN'],
              parse_mode='html')

# bot.set_my_commands([BotCommand(command, description) for command, description in CommandsDescription().get_commands()],
#                     # language_code='ru'
#                     )
# bot.set_my_commands([BotCommand(command, description) for command, description in COMMAND_DESCRIPTIONS_ON_UZB.items()],
#                     language_code='uz')

def start_bot():
    log.critical('Bot Started on POOLING')
    bot.remove_webhook()
    bot.infinity_polling()

def send_message(chat_id: int,
                 text: str,
                 reply_markup=None,
                 disable_web_page_preview: bool = False,
                 disable_notification: bool = True):

    try:
        bot.send_message(chat_id=chat_id,
                         text=text,
                         reply_markup=reply_markup,
                         disable_web_page_preview=disable_web_page_preview,
                         disable_notification=disable_notification)

    except Exception as e:
        log.error(f'Error in send_message cause - {e}, chat_id - {chat_id}')

@dataclass(frozen=True)
class MessageInfo:
    """
        Store message data with names
    """
    chat_id: int
    text: str
    message_id: int

def get_message_info(message: Union[Message, CallbackQuery],
                     callback_str: str = '') -> MessageInfo:
    """
        Метод для получения данных с сообщения будь-то колбека или обычного
    :param message: необходимое сообщение
    :param callback_str: коллбек начало которое нужно обрезать
    :return: айди, текст, айди сообщения
    """
    if isinstance(message, Message):
        if message.content_type != 'text':
            return MessageInfo(message.from_user.id,
                               message.caption,
                               message.message_id)
        else:
            return MessageInfo(message.from_user.id,
                               message.text,
                               message.message_id)
    else:
        return MessageInfo(message.from_user.id,
                           message.data[len(callback_str) - 2:],
                           message.message.message_id)

def schedule_message(chat_id: int,
                     text: str,
                     method,
                     reply_markup=None):
    """
        Метод для регистрации следующего шага, чтоб писать меньше строк
    :param reply_markup:
    :param chat_id: кому отправлять
    :param text:    текст сообщения
    :param method:  нужный метод
    :param markup:  нужная клавиатура
    :return:
    """
    try:
        msg = bot.send_message(chat_id=chat_id,
                               text=text,
                               reply_markup=reply_markup)

    except Exception as e:      # если бота заблокировали
        log.error(f'Не удалось отправить сообщение с регистрацией шага из-за ошибки \n {e}')

    else:
        bot.register_next_step_handler(msg, method)
        return msg

def delete_message(chat_id: int,
                   message_id: int):
    """
        Удаление сообщения из канала с отловом эксепшина
    :param chat_id:
    :param message_id:
    :return:
    """
    try:
        bot.delete_message(chat_id=chat_id,
                           message_id=message_id)

    except Exception as e:
        log.error(f'Не удалось удалить сообщение из-за ошибки \n {e}')

def callback_handler(message: CallbackQuery, callback: str) -> bool:
    return message.data.startswith(callback[:-2])

# __USER_ID_TYPE = int
# __NO_IMPACT_MESSAGE_TYPE = str
#
# empty_messages_data: dict[__USER_ID_TYPE, __NO_IMPACT_MESSAGE_TYPE] = {}   # если сообщение отправлено и не обработано, то говорим юзеру что что-то не так и выдвигаем мейн меню
#
#
# @bot.message_handler()
# def check_if_request_has_empty_impact(message: Message):
#     message_info = get_message_info(message)
#
#     if message_info.text == empty_messages_data.get(message_info.chat_id):
#         message.text = '/start'
#         bot.process_new_messages([message])
#
#     else:
#         ...
#


import datetime

from uuid import uuid4

from django.utils.timezone import now

from faker import Faker

from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APIClient

from datahub.company.test.factories import AdviserFactory
from datahub.core.test_utils import (
    APITestMixin,
    format_date_or_datetime,
)

from datahub.task.test.factories import TaskFactory
from datahub.task.test.utils import BaseEditTaskTests, BaseListTaskTests, BaseTaskTests

class TestListTask(BaseListTaskTests):
    """Test the LIST task endpoint"""

    reverse_url = 'api-v4:task:collection'

    def test_get_all_tasks_returns_ordered_results_when_sortby_param_is_used(self):
        earliest_task = TaskFactory(due_date=datetime.date.today() - datetime.timedelta(days=1))
        latest_task = TaskFactory(due_date=datetime.date.today() + datetime.timedelta(days=1))
        middle_task = TaskFactory(due_date=datetime.date.today())

        url = f'{reverse(self.reverse_url)}?sortby=due_date'

        response = self.api_client.get(url).json()

        ordered_ids_response = [result['id'] for result in response['results']]
        assert ordered_ids_response == [
            str(earliest_task.id),
            str(middle_task.id),
            str(latest_task.id),
        ]

class TestGetTask(APITestMixin):
    """Test the GET task endpoint"""

    def test_get_task_return_404_when_task_id_unknown(self):
        TaskFactory()

        url = reverse(
            'api-v4:task:item',
            kwargs={'pk': uuid4()},
        )
        response = self.api_client.get(url)
        assert response.status_code == status.HTTP_404_NOT_FOUND

    def test_get_task_return_task_when_task_id_valid(self):
        task = TaskFactory()

        url = reverse(
            'api-v4:task:item',
            kwargs={'pk': task.id},
        )
        response = self.api_client.get(url).json()
        expected_response = {
            'id': str(task.id),
            'title': task.title,
            'description': task.description,
            'due_date': task.due_date,
            'reminder_days': task.reminder_days,
            'email_reminders_enabled': task.email_reminders_enabled,
            'advisers': [
                {
                    'id': str(adviser.id),
                    'name': adviser.name,
                    'first_name': adviser.first_name,
                    'last_name': adviser.last_name,
                }
                for adviser in task.advisers.all()
            ],
            'archived': task.archived,
            'archived_by': task.archived_by,
            'archived_reason': task.archived_reason,
            'created_by': {
                'name': task.created_by.name,
                'first_name': task.created_by.first_name,
                'last_name': task.created_by.last_name,
                'id': str(task.created_by.id),
            },
            'modified_by': {
                'name': task.modified_by.name,
                'first_name': task.modified_by.first_name,
                'last_name': task.modified_by.last_name,
                'id': str(task.modified_by.id),
            },
            'created_on': format_date_or_datetime(task.created_on),
            'modified_on': format_date_or_datetime(task.modified_on),
            'investment_project_task': None,
        }
        assert response == expected_response

class TestAddTask(APITestMixin):
    """Test the POST task endpoint"""

    def test_create_task_with_missing_mandatory_fields_returns_bad_request(self):
        url = reverse('api-v4:task:collection')

        response = self.api_client.post(
            url,
            data={},
        )
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert list(response.json().keys()) == ['title', 'advisers']

    def test_create_task_with_unknown_advisor_id_returns_bad_request(self):
        faker = Faker()

        url = reverse('api-v4:task:collection')

        response = self.api_client.post(
            url,
            data={'title': faker.word(), 'advisers': [uuid4()]},
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert list(response.json().keys()) == ['advisers']

    def test_create_task_with_empty_advisors_returns_bad_request(self):
        faker = Faker()

        url = reverse('api-v4:task:collection')

        response = self.api_client.post(
            url,
            data={'title': faker.word(), 'advisers': []},
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert list(response.json().keys()) == ['advisers']

    def test_create_task_with_valid_mandatory_fields_returns_success_created(self):
        faker = Faker()

        adviser = AdviserFactory()

        url = reverse('api-v4:task:collection')

        new_task = {
            'title': faker.word(),
            'description': faker.word(),
            'due_date': now().date(),
            'reminder_days': 3,
            'email_reminders_enabled': True,
            'advisers': [adviser.id],
        }

        post_response = self.api_client.post(
            url,
            data=new_task,
        )
        post_response_json = post_response.json()

        assert post_response.status_code == status.HTTP_201_CREATED

        get_url = reverse(
            'api-v4:task:item',
            kwargs={'pk': post_response_json['id']},
        )
        get_response = self.api_client.get(get_url)

        assert get_response.status_code == status.HTTP_200_OK
        expected_response = {
            'id': post_response_json['id'],
            'title': post_response_json['title'],
            'description': post_response_json['description'],
            'due_date': post_response_json['due_date'],
            'reminder_days': post_response_json['reminder_days'],
            'email_reminders_enabled': post_response_json['email_reminders_enabled'],
            'advisers': [
                {
                    'id': str(adviser.id),
                    'name': adviser.name,
                    'first_name': adviser.first_name,
                    'last_name': adviser.last_name,
                },
            ],
            'archived': post_response_json['archived'],
            'archived_by': post_response_json['archived_by'],
            'archived_reason': post_response_json['archived_reason'],
            'created_by': {
                'name': self.user.name,
                'first_name': self.user.first_name,
                'last_name': self.user.last_name,
                'id': str(self.user.id),
            },
            'modified_by': {
                'name': self.user.name,
                'first_name': self.user.first_name,
                'last_name': self.user.last_name,
                'id': str(self.user.id),
            },
            'created_on': post_response_json['created_on'],
            'modified_on': post_response_json['modified_on'],
            'investment_project_task': None,
        }
        assert get_response.json() == expected_response

class TestEditTask(BaseEditTaskTests):
    reverse_url = 'api-v4:task:item'

    """Test the PATCH task endpoint"""

    def test_edit_task_return_404_when_task_id_unknown(self):
        url = reverse('api-v4:task:item', kwargs={'pk': uuid4()})

        response = self.api_client.patch(
            url,
            data={'title': 'abc'},
        )
        assert response.status_code == status.HTTP_404_NOT_FOUND

    def test_edit_task_with_valid_fields_returns_success(self):
        adviser = AdviserFactory()
        task = TaskFactory(created_by=adviser)
        new_adviser = AdviserFactory()

        url = reverse('api-v4:task:item', kwargs={'pk': task.id})

        response = self.adviser_api_client(adviser).patch(
            url,
            data={'advisers': [new_adviser.id]},
        )
        assert response.status_code == status.HTTP_200_OK
        assert response.json()['advisers'][0]['id'] == str(new_adviser.id)

class TestArchiveTask(BaseTaskTests):
    """Test the archive POST endpoint for task"""

    def test_archive_task_without_reason_returns_bad_request(self):
        adviser = AdviserFactory()
        task = TaskFactory(created_by=adviser)

        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})

        response = self.adviser_api_client(adviser).post(url, data={})

        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert response.data == {
            'reason': ['This field is required.'],
        }

    def test_archive_task_returns_unauthorized_when_user_not_authenticated(self):
        adviser = AdviserFactory()
        task = TaskFactory(advisers=[adviser])

        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})

        response = APIClient().post(url, data={'reason': 'completed'})

        assert response.status_code == status.HTTP_401_UNAUTHORIZED

    def test_archive_task_returns_success_when_user_is_creator_but_not_assigned_to_task(self):
        adviser = AdviserFactory()
        task = TaskFactory(created_by=adviser)

        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})

        response = self.adviser_api_client(adviser).post(url, data={'reason': 'completed'})

        assert response.status_code == status.HTTP_200_OK

    def test_archive_task_returns_success_when_user_is_not_creator_but_is_assigned_to_task(
        self,
    ):
        adviser = AdviserFactory()
        task = TaskFactory(advisers=[adviser])

        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})

        response = self.adviser_api_client(adviser).post(url, data={'reason': 'completed'})

        assert response.status_code == status.HTTP_200_OK

    def test_archive_task_returns_success_when_user_is_creator_and_is_assigned_to_task(self):
        adviser = AdviserFactory()
        task = TaskFactory(advisers=[adviser], created_by=adviser)

        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})

        response = self.adviser_api_client(adviser).post(url, data={'reason': 'completed'})

        assert response.status_code == status.HTTP_200_OK

    def test_archive_task_with_valid_reason_returns_success(self):
        adviser = AdviserFactory()
        task = TaskFactory(created_by=adviser)
        url = reverse('api-v4:task:task_archive', kwargs={'pk': task.id})
        response = self.adviser_api_client(adviser).post(url, data={'reason': 'completed'})

        assert response.status_code == status.HTTP_200_OK
        assert response.data['archived']
        assert response.data['archived_by'] == {
            'id': str(adviser.id),
            'first_name': adviser.first_name,
            'last_name': adviser.last_name,
            'name': adviser.name,
        }
        assert response.data['archived_reason'] == 'completed'
        assert response.data['id'] == str(task.id)


class Solution:
    def minTimeToType(self, word: str) -> int:
        time = 0
        prev = 0
        for letter in word:
            curr = ord(letter)-ord('a')
            time += min(abs(curr-prev), (26-abs(curr-prev)))
            prev = curr
        time += len(word)
        return time


import logging

import streamlit as st

from src.dashboard.menus.side_components import (
    visualize_dataset,
    search_room,
    download_data,
)

def side_menu(dataframe):
    """
    Streamlit side config menu
    """
    logging.debug("Launching side menù function")

    st.sidebar.markdown("**Configuration Panel**")

    #### FILTER DATA ####
    visualize_dataset(dataframe)
    search_room(dataframe)

    #### DOWNLOAD DATA ####
    download_data(dataframe)

def main_menu():

    logging.debug("Launching main menu")
    st.markdown(
        f"""
    <style>
        .reportview-container .main .block-container{{
            max-width: 1400px;
        }}
    </style>
    """,
        unsafe_allow_html=True,
    )
    # create the dashboard heading
    st.title("Python Biella Group")
    st.markdown("## Streamlit project example")
    st.markdown("How to use streamlit for your Data Project")
    st.markdown("<-- Open side menu to filter the data")

    return True


from setuptools import setup, find_packages

import wfc

def requirements(filename):
    with open(filename) as file:
        return [req for req in map(
            lambda line: line.strip(),
            filter(
                lambda line: not line.startswith('#'),
                file.readlines()
            )
        )]

if __name__ == '__main__':
    setup(
        name=wfc.get_name(),
        version=wfc.get_version(),
        url='https://github.com/wizeline/wfc',
        author=wfc.get_author(),
        author_email='diego.guzman@wizeline.com',
        description='Wizeline Flow Compiler',
        packages=find_packages(exclude=['tests']),
        include_package_data=True,
        zip_safe=False,
        classifiers=[
            'Development Status :: Development',
            'Environment :: Other Environment',
            'Intended Audience :: Developers',
            'Natural Language :: English',
            'Operating System :: POSIX :: Linux',
            'Operating System :: MacOS :: MacOS X',
            'Programming Language :: Python 3.6.8',
            'Topic :: Utilities'
        ],
        test_require=requirements('requirements.txt'),
        install_requires=requirements('requirements.txt'),
        entry_points={"console_scripts": ["wfc = wfc.cli:main"]}
    )


import os
import json
import logging

import pika

from database.dependencies import get_db
from database.crud import get_metadata
from database.schemas import ClientRequest

USER = os.environ.get('RABBIT_USER')
PASS = os.environ.get('RABBIT_PASSWORD')
HOST = os.environ.get('RABBIT_HOST')
PORT = os.environ.get('RABBIT_PORT')
QUEUE = os.environ.get('RABBIT_QUEUE')

## Configure logger
FORMAT = "%(levelname)s:\t [%(asctime)s] [%(message)s]"
logging.basicConfig(level=logging.ERROR, format=FORMAT)

credentials = pika.PlainCredentials(USER, PASS)

class RabbitMQWorker(object):

    '''
    This defines a worker that pulls messages from the queue and
    executes the task.

    This is the message consumer.
    '''

    def __init__(self):

        '''
        Initialise a new connection to the queue.
        '''

        # Set up RabbitMQ connection
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters(
                host = HOST,
                port = PORT,
                credentials = credentials
            )
        )

        # Create the channel
        self.channel = self.connection.channel()

        # Declare the queue
        self.channel.queue_declare(queue='rpc_queue')

        logging.info(f'WORKER: new connection created {HOST}:{PORT}')

    def on_request(self, ch, method, props, body):
        '''
        This parses the body of messages from the queue. It then
        calls the function which runs the values from the message
        as a query against the DB. Finally it inserts the results
        into a queue.
        '''

        logging.info(f'WORKER: request recieved {props.correlation_id}')

        # Parse message body into dicts
        request = json.loads(body)

        # Open a connection to the database
        db = get_db()

        # Query the database
        response = get_metadata(

            next(db), # Must call next() as db is a generator
            request.get('name'),
            request.get('db_type'),
            request.get('release'),
            request.get('offset'),
            request.get('limit'),

        )

        # Publish the results in the return queue
        ch.basic_publish(

            exchange = '',
            routing_key = props.reply_to,
            properties = pika.BasicProperties(
                correlation_id = props.correlation_id),
            body=json.dumps(response)

        )

        ch.basic_ack(delivery_tag=method.delivery_tag)

        logging.info(f'WORKER: response sent {props.correlation_id}')

    def consume(self):
        '''
        This starts the worker consuming messages.
        '''

        # Configure quality of service
        self.channel.basic_qos(
            prefetch_size = 0,
            prefetch_count = 1
        )

        # Configure the queue
        self.channel.basic_consume(
            queue = 'rpc_queue',
            on_message_callback = self.on_request
        )

        logging.info(f'WORKER: consuming...')

        # Start listening
        self.channel.start_consuming()

if __name__ == '__main__':

    worker = RabbitMQWorker()
    worker.consume()


# import package
from turtle import *

# method to draw ellipse
def drawEclipse(pen, rad, ratio=2, forward=True):
  # rad --> radius of arc
  pen.pendown()
  pen.tilt(45)

  if forward:
    rad = -rad
  for i in range(2):
    # two arcs
    pen.circle(rad,90)
    pen.circle(rad//ratio,90)
  pen.penup()

if __name__ == '__main__':

  # Main section
  # tilt the shape to negative 45
  # turtle.seth(90)
  pen = Turtle()
  pen.hideturtle()
  # calling draw method
  # drawEclipse(pen, 50, ratio=4)
  # pen.fd(-100)
  drawEclipse(pen, 50, forward=True)
  # drawEclipse(pen, 50, forward=False)

  # radius = 50
  # extent = 90
  # ratio = 2
  # pen.circle(radius,extent)
  # pen.circle(radius//ratio,extent)

  exitonclick()