import requests # Requests library to retrieve HTML
from bs4 import BeautifulSoup # Beautiful Soup library to parse HTML
from Tkinter import *

def window(balance, limit, days_left):
 win = Tk()
 l1 = Label( win, text = 'Airtel Usage Statistics :')
 l1.grid(row = 0, column = 0)
 l2 = Label( win, text = 'Balance Data :')
 v2 = StringVar()
 v2.set(balance)
 e2 = Entry(win , textvariable = v2)
 l2.grid(row = 1, column = 0)
 e2.grid(row = 1, column = 1)
 l3 = Label( win, text = 'Data Limit :')
 v3 = StringVar()
 v3.set(limit)
 e3 = Entry( win , textvariable = v3)
 l3.grid(row = 2, column = 0)
 e3.grid(row = 2, column = 1)
 l4 = Label( win, text = 'Days Left :')
 v4 = StringVar()
 v4.set(days_left)
 e4 = Entry(win , textvariable = v4)
 l4.grid(row = 3, column = 0)
 e4.grid(row = 3, column = 1)
 win.mainloop()

response1 = requests.get('http://www.airtel.in/smartbyte-s/page.html')
page1 = response1.content
soup1 = BeautifulSoup(page1)
sample1 = [x['src'] for x in soup1.find_all('iframe')] #iframe with external ip based link is loaded
sample2 = ''.join(sample1)

# external ip based link is requested
response2 = requests.get(sample2)
page2 = response2.content
soup2 = BeautifulSoup(page2)
sample4 = soup2.find('ul','list1 clearfix')
sample5 = sample4.find_all('span')

#parse and get following values
balance = sample5[1].text
DSLno = sample5[3].text
limit = sample5[0].text
days_left = sample5[2].text

window(balance, limit, days_left)


"""The :mod:`~virtual_rainforest.models.hydrology.hydrology_model` module
creates a
:class:`~virtual_rainforest.models.hydrology.hydrology_model.HydrologyModel`
class as a child of the :class:`~virtual_rainforest.core.base_model.BaseModel` class.
At present a lot of the abstract methods of the parent class (e.g.
:func:`~virtual_rainforest.core.base_model.BaseModel.spinup`) are overwritten using
placeholder functions that don't do anything. This will change as the Virtual Rainforest
model develops. The factory method
:func:`~virtual_rainforest.models.hydrology.hydrology_model.HydrologyModel.from_config`
exists in a more complete state, and unpacks a small number of parameters from our
currently pretty minimal configuration dictionary. These parameters are then used to
generate a class instance. If errors crop here when converting the information from the
config dictionary to the required types they are caught and then logged, and at the end
of the unpacking an error is thrown. This error should be caught and handled by
downstream functions so that all model configuration failures can be reported as one.
"""  # noqa: D205, D415

from __future__ import annotations

from math import sqrt
from typing import Any, Union

import numpy as np
from numpy.typing import NDArray
from pint import Quantity
from xarray import DataArray

from virtual_rainforest.core.base_model import BaseModel
from virtual_rainforest.core.config import Config
from virtual_rainforest.core.constants_loader import load_constants
from virtual_rainforest.core.data import Data
from virtual_rainforest.core.exceptions import InitialisationError
from virtual_rainforest.core.logger import LOGGER
from virtual_rainforest.core.utils import set_layer_roles
from virtual_rainforest.models.hydrology import above_ground, below_ground
from virtual_rainforest.models.hydrology.constants import HydroConsts

class HydrologyModel(BaseModel):
    """A class describing the hydrology model.

    Args:
        data: The data object to be used in the model.
        update_interval: Time to wait between updates of the model state.
        soil_layers: A list giving the number and depth of soil layers to be modelled.
        canopy_layers: The initial number of canopy layers to be modelled.
        initial_soil_moisture: The initial volumetric relative water content [unitless]
            for all layers.
        initial_groundwater_saturation: Initial level of groundwater saturation (between
            0 and 1) for all layers and grid cells identical.
        constants: Set of constants for the hydrology model.

    Raises:
        InitialisationError: when initial soil moisture is out of bounds.

    TODOs:

    * find a way to load daily (precipitation) data and loop over daily time_index
    * add time dimension to required_init_vars
    * allow for different time steps (currently only 30 days)
    * potentially move `calculate_drainage_map` to core
    """

    model_name = "hydrology"
    """The model name for use in registering the model and logging."""
    lower_bound_on_time_scale = "1 day"
    """Shortest time scale that hydrology model can sensibly capture."""
    upper_bound_on_time_scale = "1 month"
    """Longest time scale that hydrology model can sensibly capture."""
    required_init_vars = (
        ("precipitation", ("spatial",)),
        ("leaf_area_index", ("spatial",)),
        ("air_temperature_ref", ("spatial",)),
        ("relative_humidity_ref", ("spatial",)),
        ("atmospheric_pressure_ref", ("spatial",)),
        ("evapotranspiration", ("spatial",)),
        ("elevation", ("spatial",)),
    )
    """The required variables and axes for the hydrology model"""

    vars_updated = (
        "precipitation_surface",  # precipitation-interception loss, input to `plants`
        "soil_moisture",
        "surface_runoff",  # equivalent to SPLASH runoff
        "vertical_flow",
        "soil_evaporation",
        "surface_runoff_accumulated",
        "matric_potential",
        "groundwater_storage",
        "river_discharge_rate",
        "total_river_discharge",
        "subsurface_flow",
        "baseflow",
    )
    """Variables updated by the hydrology model."""

    def __init__(
        self,
        data: Data,
        update_interval: Quantity,
        soil_layers: list[float],
        canopy_layers: int,
        initial_soil_moisture: float,
        initial_groundwater_saturation: float,
        constants: HydroConsts,
        **kwargs: Any,
    ):
        # Sanity checks for initial soil moisture
        if type(initial_soil_moisture) is not float:
            to_raise = InitialisationError("The initial soil moisture must be a float!")
            LOGGER.error(to_raise)
            raise to_raise

        if initial_soil_moisture < 0 or initial_soil_moisture > 1:
            to_raise = InitialisationError(
                "The initial soil moisture has to be between 0 and 1!"
            )
            LOGGER.error(to_raise)
            raise to_raise

        if initial_groundwater_saturation < 0 or initial_groundwater_saturation > 1:
            to_raise = InitialisationError(
                "The initial groundwater saturation has to be between 0 and 1!"
            )
            LOGGER.error(to_raise)
            raise to_raise

        super().__init__(data, update_interval, **kwargs)

        # Create a list of layer roles
        layer_roles = set_layer_roles(canopy_layers, soil_layers)

        self.data
        """A Data instance providing access to the shared simulation data."""
        self.layer_roles = layer_roles
        """A list of vertical layer roles."""
        self.soil_layers = soil_layers
        """The number of soil layers."""
        self.update_interval
        """The time interval between model updates."""
        self.initial_soil_moisture = initial_soil_moisture
        """Initial volumetric relative water content [unitless] for all layers and grid
        cells identical."""
        self.initial_groundwater_saturation = initial_groundwater_saturation
        """Initial level of groundwater saturation for all layers identical."""
        self.constants = constants
        """Set of constants for the hydrology model"""
        self.data.grid.set_neighbours(distance=sqrt(self.data.grid.cell_area))
        """Set neighbours."""
        self.drainage_map = above_ground.calculate_drainage_map(
            grid=self.data.grid,
            elevation=np.array(self.data["elevation"]),
        )
        """Upstream neighbours for the calculation of accumulated horizontal flow."""

    @classmethod
    def from_config(
        cls, data: Data, config: Config, update_interval: Quantity
    ) -> HydrologyModel:
        """Factory function to initialise the hydrology model from configuration.

        This function unpacks the relevant information from the configuration file, and
        then uses it to initialise the model. If any information from the config is
        invalid rather than returning an initialised model instance an error is raised.

        Args:
            data: A :class:`~virtual_rainforest.core.data.Data` instance.
            config: A validated Virtual Rainforest model configuration object.
            update_interval: Frequency with which all models are updated.
        """

        # Find number of soil and canopy layers
        soil_layers = config["core"]["layers"]["soil_layers"]
        canopy_layers = config["core"]["layers"]["canopy_layers"]
        initial_soil_moisture = config["hydrology"]["initial_soil_moisture"]
        initial_groundwater_saturation = config["hydrology"][
            "initial_groundwater_saturation"
        ]

        # Load in the relevant constants
        constants = load_constants(config, "hydrology", "HydroConsts")

        LOGGER.info(
            "Information required to initialise the hydrology model successfully "
            "extracted."
        )
        return cls(
            data,
            update_interval,
            soil_layers,
            canopy_layers,
            initial_soil_moisture,
            initial_groundwater_saturation,
            constants,
        )

    def setup(self) -> None:
        """Function to set up the hydrology model.

        At the moment, this function initializes variables that are required to run the
        first update(). Air temperature and relative humidity below the canopy are set
        to the 2 m reference values.

        For the within grid cell hydrology, soil moisture is initialised homogenously
        for all soil layers and groundwater storage is set to the percentage of it's
        capacity that was defined in the model configuration. This design might change
        with the implementation of the SPLASH model :cite:p:`davis_simple_2017` which
        will take care of part of the above-ground hydrology.

        For the hydrology across the grid, this function initialises the accumulated
        surface runoff variable and the subsurface accumulated flow variable. Both
        require a spinup which is currently not implemented.
        """

        # Create 1-dimensional numpy array filled with initial soil moisture values for
        # all soil layers and np.nan for atmosphere layers
        soil_moisture_values = np.repeat(
            a=[np.nan, self.initial_soil_moisture],
            repeats=[
                len(self.layer_roles) - len(self.soil_layers),
                len(self.soil_layers),
            ],
        )
        # Broadcast 1-dimensional array to grid and assign dimensions and coordinates
        self.data["soil_moisture"] = DataArray(
            np.broadcast_to(
                soil_moisture_values,
                (self.data.grid.n_cells, len(self.layer_roles)),
            ).T,
            dims=["layers", "cell_id"],
            coords={
                "layers": np.arange(len(self.layer_roles)),
                "layer_roles": ("layers", self.layer_roles),
                "cell_id": self.data.grid.cell_id,
            },
            name="soil_moisture",
        )

        # Create initial air temperature with reference temperature below the canopy
        # for first soil evaporation update.
        self.data["air_temperature"] = (
            DataArray(self.data["air_temperature_ref"].isel(time_index=0))
            .expand_dims("layers")
            .rename("air_temperature")
            .assign_coords(
                coords={
                    "layers": [self.layer_roles.index("subcanopy")],
                    "layer_roles": ("layers", ["subcanopy"]),
                    "cell_id": self.data.grid.cell_id,
                },
            )
        )

        # Create initial relative humidity with reference humidity below the canopy
        # for first soil evaporation update.
        self.data["relative_humidity"] = (
            DataArray(self.data["relative_humidity_ref"].isel(time_index=0))
            .expand_dims("layers")
            .rename("relative_humidity")
            .assign_coords(
                coords={
                    "layers": [self.layer_roles.index("subcanopy")],
                    "layer_roles": ("layers", ["subcanopy"]),
                    "cell_id": self.data.grid.cell_id,
                },
            )
        )

        # Create initial groundwater storage variable with two layers
        initial_groundwater_storage = (
            self.initial_groundwater_saturation * self.constants.groundwater_capacity
        )
        self.data["groundwater_storage"] = DataArray(
            np.full((2, self.data.grid.n_cells), initial_groundwater_storage),
            dims=("groundwater_layers", "cell_id"),
            name="groundwater_storage",
        )

        # Set initial above-round accumulated runoff to zero
        self.data["surface_runoff_accumulated"] = DataArray(
            np.zeros_like(self.data["elevation"]),
            dims="cell_id",
            name="surface_runoff_accumulated",
            coords={"cell_id": self.data.grid.cell_id},
        )

        # Set initial sub-surface flow (including base flow) to zero
        self.data["subsurface_flow_accumulated"] = DataArray(
            np.zeros_like(self.data["elevation"]),
            dims="cell_id",
            name="subsurface_flow_accumulated",
            coords={"cell_id": self.data.grid.cell_id},
        )

    def spinup(self) -> None:
        """Placeholder function to spin up the hydrology model."""

    def update(self, time_index: int, **kwargs: Any) -> None:
        r"""Function to update the hydrology model.

        This function calculates the main hydrological components of the Virtual
        Rainforest and updates the following variables in the `data` object:

        * precipitation_surface, [mm]
        * soil_moisture, [-]
        * matric_potential, [kPa]
        * surface_runoff, [mm], equivalent to SPLASH runoff
        * surface_runoff_accumulated, [mm]
        * soil_evaporation, [mm]
        * vertical_flow, [mm/timestep]
        * groundwater_storage, [mm]
        * subsurface_flow, [mm]
        * baseflow, [mm]
        * total_river_discharge, [mm]
        * river_discharge_rate, [m3 s-1]

        Many of the underlying processes are problematic at a monthly timestep, which is
        currently the only supported update interval. As a short-term work around, the
        input precipitation is randomly distributed over 30 days and input
        evapotranspiration is divided by 30, and the return variables are monthly means
        or monthly accumulated values.

        Precipitation that reaches the surface is defined as incoming precipitation
        minus canopy interception, which is estimated using a stroage-based approach,
        see
        :func:`~virtual_rainforest.models.hydrology.above_ground.calculate_interception`
        .

        Surface runoff is calculated with a simple bucket model based on
        :cite:t:`davis_simple_2017`, see
        :func:`~virtual_rainforest.models.hydrology.above_ground.calculate_surface_runoff`
        : if precipitation exceeds top soil moisture capacity
        , the excess water is added to runoff and top soil moisture is set to soil
        moisture capacity value; if the top soil is not saturated, precipitation is
        added to the current soil moisture level and runoff is set to zero. Note that
        this function will likely change with the implementation of the SPLASH model
        :cite:p:`davis_simple_2017` in the plant module which will take care of the grid
        cell based above-ground hydrology. The accumulated surface runoff is calculated
        as the sum of current runoff and the runoff from upstream cells at the previous
        time step, see
        :func:`~virtual_rainforest.models.hydrology.above_ground.accumulate_horizontal_flow`
        .

        Potential soil evaporation is calculated with classical bulk aerodynamic
        formulation, following the so-called ':math:`\alpha` method', see
        :func:`~virtual_rainforest.models.hydrology.above_ground.calculate_soil_evaporation`
        , and reduced to actual evaporation as a function of leaf area index.

        Vertical flow between soil layers is calculated using the Richards equation, see
        :func:`~virtual_rainforest.models.hydrology.below_ground.calculate_vertical_flow`
        . That function returns total vertical flow in mm. Note that there are
        severe limitations to this approach on the temporal and spatial scale of this
        model and this can only be treated as a very rough approximation!

        Soil moisture is updated by iteratively updating the soil moisture of individual
        layers under consideration of the vertical flow in and out of each layer, see
        :func:`~virtual_rainforest.models.hydrology.below_ground.update_soil_moisture`
        . The conversion to matric potential is based on :cite:t:`campbell_simple_1974`,
        see
        :func:`~virtual_rainforest.models.hydrology.below_ground.convert_soil_moisture_to_water_potential`
        .

        Groundwater storage and flows are modelled using two parallel linear
        reservoirs, see
        :func:`~virtual_rainforest.models.hydrology.below_ground.update_groundwater_storge`
        . The horizontal flow between grid cells currently uses the same function as the
        above ground runoff.

        Total river discharge is calculated as the sum of above- and below ground
        horizontal flow and converted to river discharge rate in m3/s.

        The function requires the following input variables from the data object:

        * air temperature, [C]
        * relative humidity, []
        * atmospheric pressure, [kPa]
        * precipitation, [mm]
        * wind speed (currently not implemented, default = 0.1 m s-1)
        * leaf area index, [m m-2]
        * layer heights, [m]
        * Volumetric relative water content (previous time step), [unitless]
        * evapotranspiration (current time step), [mm]
        * accumulated surface runoff (previous time step), [mm]
        * accumulated subsurface runoff (previous time step), [mm]

        and a number of parameters that as described in detail in
        :class:`~virtual_rainforest.models.hydrology.constants.HydroConsts`.
        """
        # Determine number of days, currently only 30 days (=1 month)
        if self.update_interval != Quantity("1 month"):
            to_raise = NotImplementedError("This time step is currently not supported.")
            LOGGER.error(to_raise)
            raise to_raise

        days: int = 30

        # Set seed for random rainfall generator
        seed: Union[None, int] = kwargs.pop("seed", None)

        # Select variables at relevant heights for current time step
        hydro_input = setup_hydrology_input_current_timestep(
            data=self.data,
            time_index=time_index,
            days=days,
            seed=seed,
            layer_roles=self.layer_roles,
            soil_moisture_capacity=self.constants.soil_moisture_capacity,
            soil_moisture_residual=self.constants.soil_moisture_residual,
            meters_to_mm=self.constants.meters_to_mm,
        )

        # Create lists for output variables to store daily data
        daily_lists: dict = {name: [] for name in self.vars_updated}

        for day in np.arange(days):
            # Interception of water in canopy, [mm]
            interception = above_ground.calculate_interception(
                leaf_area_index=hydro_input["leaf_area_index_sum"],
                precipitation=hydro_input["current_precipitation"][:, day],
                intercept_param_1=self.constants.intercept_param_1,
                intercept_param_2=self.constants.intercept_param_2,
                intercept_param_3=self.constants.intercept_param_3,
                veg_density_param=self.constants.veg_density_param,
            )

            # Precipitation that reaches the surface per day, [mm]
            precipitation_surface = (
                hydro_input["current_precipitation"][:, day] - interception
            )
            daily_lists["precipitation_surface"].append(precipitation_surface)

            # Calculate daily surface runoff of each grid cell, [mm]; replace by SPLASH
            surface_runoff = above_ground.calculate_surface_runoff(
                precipitation_surface=precipitation_surface,
                top_soil_moisture=hydro_input["soil_moisture_mm"][0],
                top_soil_moisture_capacity=hydro_input["top_soil_moisture_capacity_mm"],
            )

            daily_lists["surface_runoff"].append(surface_runoff)

            # Calculate preferential bypass flow, [mm]
            bypass_flow = above_ground.calculate_bypass_flow(
                top_soil_moisture=hydro_input["soil_moisture_mm"][0],
                sat_top_soil_moisture=hydro_input["top_soil_moisture_capacity_mm"],
                available_water=precipitation_surface - surface_runoff,
                infiltration_shape_parameter=(
                    self.constants.infiltration_shape_parameter
                ),
            )

            # Calculate top soil moisture after infiltration, [mm]
            soil_moisture_infiltrated = np.clip(
                (
                    hydro_input["soil_moisture_mm"][0]
                    + precipitation_surface
                    - surface_runoff
                    - bypass_flow,
                ),
                0,
                hydro_input["top_soil_moisture_capacity_mm"],
            ).squeeze()

            # Calculate daily soil evaporation, [mm]
            top_soil_moisture_vol = (
                soil_moisture_infiltrated / hydro_input["soil_layer_thickness"][0]
            )

            soil_evaporation = above_ground.calculate_soil_evaporation(
                temperature=hydro_input["subcanopy_temperature"],
                relative_humidity=hydro_input["subcanopy_humidity"],
                atmospheric_pressure=hydro_input["subcanopy_pressure"],
                soil_moisture=top_soil_moisture_vol,
                soil_moisture_residual=self.constants.soil_moisture_residual,
                soil_moisture_capacity=self.constants.soil_moisture_capacity,
                leaf_area_index=hydro_input["leaf_area_index_sum"],
                wind_speed=0.1,  # m/s TODO wind_speed in data object
                celsius_to_kelvin=self.constants.celsius_to_kelvin,
                density_air=self.constants.density_air,
                latent_heat_vapourisation=self.constants.latent_heat_vapourisation,
                gas_constant_water_vapour=self.constants.gas_constant_water_vapour,
                heat_transfer_coefficient=self.constants.heat_transfer_coefficient,
                extinction_coefficient_global_radiation=(
                    self.constants.extinction_coefficient_global_radiation
                ),
            )
            daily_lists["soil_evaporation"].append(soil_evaporation)

            # Calculate top soil moisture after evap and combine with lower layers, [mm]
            soil_moisture_evap: NDArray[np.float32] = np.concatenate(
                (
                    np.expand_dims(
                        np.clip(
                            (soil_moisture_infiltrated - soil_evaporation),
                            hydro_input["top_soil_moisture_residual_mm"],
                            hydro_input["top_soil_moisture_capacity_mm"],
                        ),
                        axis=0,
                    ),
                    hydro_input["soil_moisture_mm"][1:],
                )
            )

            # Calculate vertical flow between soil layers in mm per time step
            # Note that there are severe limitations to this approach on the temporal
            # spatial scale of this model and this can only be treated as a very rough
            # approximation to discuss nutrient leaching.
            vertical_flow = below_ground.calculate_vertical_flow(
                soil_moisture=soil_moisture_evap / hydro_input["soil_layer_thickness"],
                soil_layer_thickness=hydro_input["soil_layer_thickness"],  # mm
                soil_moisture_capacity=self.constants.soil_moisture_capacity,  # vol
                soil_moisture_residual=self.constants.soil_moisture_residual,  # vol
                hydraulic_conductivity=self.constants.hydraulic_conductivity,  # m/s
                hydraulic_gradient=self.constants.hydraulic_gradient,  # m/m
                nonlinearily_parameter=self.constants.nonlinearily_parameter,
                groundwater_capacity=self.constants.groundwater_capacity,
                seconds_to_day=self.constants.seconds_to_day,
            )
            daily_lists["vertical_flow"].append(vertical_flow)

            # Update soil moisture by +/- vertical flow to each layer and remove root
            # water uptake by plants (transpiration), [mm]
            soil_moisture_updated = below_ground.update_soil_moisture(
                soil_moisture=soil_moisture_evap,
                vertical_flow=vertical_flow,
                evapotranspiration=hydro_input["current_evapotranspiration"],
                soil_moisture_capacity=(
                    self.constants.soil_moisture_capacity
                    * hydro_input["soil_layer_thickness"]
                ),
                soil_moisture_residual=(
                    self.constants.soil_moisture_residual
                    * hydro_input["soil_layer_thickness"]
                ),
            )

            daily_lists["soil_moisture"].append(
                soil_moisture_updated / hydro_input["soil_layer_thickness"]
            )

            # Convert soil moisture to matric potential
            matric_potential = below_ground.convert_soil_moisture_to_water_potential(
                soil_moisture=(
                    soil_moisture_updated / hydro_input["soil_layer_thickness"]
                ),
                air_entry_water_potential=self.constants.air_entry_water_potential,
                water_retention_curvature=self.constants.water_retention_curvature,
                soil_moisture_capacity=self.constants.soil_moisture_capacity,
            )
            daily_lists["matric_potential"].append(matric_potential)

            # calculate below ground horizontal flow and update ground water
            below_ground_flow = below_ground.update_groundwater_storge(
                groundwater_storage=hydro_input["groundwater_storage"],
                vertical_flow_to_groundwater=vertical_flow[-1],
                bypass_flow=bypass_flow,
                max_percolation_rate_uzlz=self.constants.max_percolation_rate_uzlz,
                groundwater_loss=self.constants.groundwater_loss,
                reservoir_const_upper_groundwater=(
                    self.constants.reservoir_const_upper_groundwater
                ),
                reservoir_const_lower_groundwater=(
                    self.constants.reservoir_const_lower_groundwater
                ),
            )

            for var in ["groundwater_storage", "subsurface_flow", "baseflow"]:
                daily_lists[var].append(below_ground_flow[var])

            # update inputs for next day
            hydro_input["soil_moisture_mm"] = soil_moisture_updated
            hydro_input["groundwater_storage"] = below_ground_flow[
                "groundwater_storage"
            ]

        # create output dict as intermediate step to not overwrite data directly
        soil_hydrology = {}

        # Calculate monthly accumulated values
        for var in [
            "precipitation_surface",
            "surface_runoff",
            "soil_evaporation",
            "subsurface_flow",
            "baseflow",
        ]:
            soil_hydrology[var] = DataArray(
                np.sum(np.stack(daily_lists[var], axis=1), axis=1),
                dims="cell_id",
                coords={"cell_id": self.data.grid.cell_id},
            )

        soil_hydrology["vertical_flow"] = DataArray(
            np.sum(daily_lists["vertical_flow"], axis=(0, 1)),
            dims="cell_id",
            coords={"cell_id": self.data.grid.cell_id},
        )

        # Return mean soil moisture, [-], and matric potential, [kPa], and add
        # atmospheric layers (nan)
        for var in ["soil_moisture", "matric_potential"]:
            soil_hydrology[var] = DataArray(
                np.concatenate(
                    (
                        np.full(
                            (
                                len(self.layer_roles) - self.layer_roles.count("soil"),
                                self.data.grid.n_cells,
                            ),
                            np.nan,
                        ),
                        np.mean(
                            np.stack(daily_lists[var], axis=0),
                            axis=0,
                        ),
                    ),
                ),
                dims=self.data["layer_heights"].dims,
                coords=self.data["layer_heights"].coords,
            )

        # Calculate accumulated runoff for each cell (me + sum of upstream neighbours)
        new_accumulated_runoff = above_ground.accumulate_horizontal_flow(
            drainage_map=self.drainage_map,
            current_flow=np.array(soil_hydrology["surface_runoff"]),
            previous_accumulated_flow=hydro_input["previous_accumulated_runoff"],
        )

        soil_hydrology["surface_runoff_accumulated"] = DataArray(
            new_accumulated_runoff, dims="cell_id"
        )

        # Calculate subsurface accumulated flow, [mm]
        new_subsurface_flow_accumulated = above_ground.accumulate_horizontal_flow(
            drainage_map=self.drainage_map,
            current_flow=np.array(
                soil_hydrology["subsurface_flow"] + soil_hydrology["baseflow"]
            ),
            previous_accumulated_flow=(
                hydro_input["previous_subsurface_flow_accumulated"]
            ),
        )

        # Calculate total river discharge as sum of above- and below-ground flow, [mm]
        total_river_discharge = new_accumulated_runoff + new_subsurface_flow_accumulated
        soil_hydrology["total_river_discharge"] = DataArray(
            total_river_discharge, dims="cell_id"
        )

        # Convert total discharge to river discharge rate, [m3 s-1]
        river_discharge_rate = above_ground.convert_mm_flow_to_m3_per_second(
            river_discharge_mm=total_river_discharge,
            area=self.data.grid.cell_area,
            days=days,
            seconds_to_day=self.constants.seconds_to_day,
            meters_to_millimeters=self.constants.meters_to_mm,
        )
        soil_hydrology["river_discharge_rate"] = DataArray(
            river_discharge_rate, dims="cell_id"
        )

        # Save last state of groundwater stoage, [mm]
        soil_hydrology["groundwater_storage"] = DataArray(
            daily_lists["groundwater_storage"][day],
            dims=self.data["groundwater_storage"].dims,
        )

        # Update data object
        self.data.add_from_dict(output_dict=soil_hydrology)

    def cleanup(self) -> None:
        """Placeholder function for hydrology model cleanup."""

def calculate_layer_thickness(
    soil_layer_heights: NDArray[np.float32],
    meters_to_mm: float,
) -> NDArray[np.float32]:
    """Calculate layer thickness from soil layer depth profile.

    Args:
        soil_layer_heights: soil layer heights, [m]
        meters_to_mm: meter to millimeter conversion factor

    Returns:
        soil layer thickness, mm
    """
    return np.array(
        [
            (soil_layer_heights[i] - soil_layer_heights[i - 1]) * (-meters_to_mm)
            if i > 0
            else soil_layer_heights[0] * (-meters_to_mm)
            for i in range(len(soil_layer_heights))
        ],
    )

def setup_hydrology_input_current_timestep(
    data: Data,
    time_index: int,
    days: int,
    seed: Union[None, int],
    layer_roles: list[str],
    soil_moisture_capacity: float,
    soil_moisture_residual: float,
    meters_to_mm: float,
) -> dict[str, NDArray[np.float32]]:
    """Select and pre-process inputs to hydrology.update() for current time step.

    The function resturns a dictionary with the following variables:

    * current_precipitation
    * subcanopy_temperature
    * subcanopy_humidity
    * subcanopy_pressure
    * leaf_area_index_sum
    * current_evapotranspiration
    * soil_layer_heights
    * soil_layer_thickness
    * top_soil_moisture_capacity_mm
    * top_soil_moisture_residual_mm
    * soil_moisture_mm
    * previous_accumulated_runoff
    * previous_subsurface_flow_accumulated
    * groundwater_storage

    Args:
        data: Data object that contains inputs from the microclimate model, the plant
            model, and the hydrology model that are required for current update
        time_index: time index
        days: number of days
        seed: seed for random rainfall generator
        layer_roles: list of layer roles
        soil_moisture_capacity: soil moisture capacity, unitless
        soil_moisture_residual: soil moisture residual, unitless
        meters_to_mm: factor to convert between meters and millimieters

    Returns:
        dictionary with all variables that are required to run one hydrology update()
    """

    output = {}

    # Get atmospheric variables
    output["current_precipitation"] = above_ground.distribute_monthly_rainfall(
        (data["precipitation"].isel(time_index=time_index)).to_numpy(),
        num_days=days,
        seed=seed,
    )
    output["subcanopy_temperature"] = (
        data["air_temperature"].isel(layers=layer_roles.index("subcanopy"))
    ).to_numpy()
    output["subcanopy_humidity"] = (
        data["relative_humidity"].isel(layers=layer_roles.index("subcanopy"))
    ).to_numpy()
    output["subcanopy_pressure"] = (
        data["atmospheric_pressure_ref"].isel(time_index=time_index).to_numpy()
    )

    # Get inputs from plant model
    output["leaf_area_index_sum"] = data["leaf_area_index"].sum(dim="layers").to_numpy()
    output["current_evapotranspiration"] = (
        data["evapotranspiration"].sum(dim="layers") / days
    ).to_numpy()

    # Select soil variables
    output["soil_layer_heights"] = (
        data["layer_heights"]
        .where(data["layer_heights"].layer_roles == "soil")
        .dropna(dim="layers")
    ).to_numpy()
    output["soil_layer_thickness"] = calculate_layer_thickness(  # [mm]
        soil_layer_heights=output["soil_layer_heights"],
        meters_to_mm=meters_to_mm,
    )
    output["top_soil_moisture_capacity_mm"] = (
        soil_moisture_capacity * output["soil_layer_thickness"][0]
    )
    output["top_soil_moisture_residual_mm"] = (
        soil_moisture_residual * output["soil_layer_thickness"][0]
    )

    # Convert soil moisture (volumetric relative water content) to mm as follows:
    # water content in mm = relative water content / 100 * depth in mm
    # Example: for 20% water at 40 cm this would be: 20/100 * 400mm = 80 mm
    output["soil_moisture_mm"] = (
        data["soil_moisture"]
        .where(data["soil_moisture"].layer_roles == "soil")
        .dropna(dim="layers")
        * output["soil_layer_thickness"]
    ).to_numpy()

    # Get accumulated runoff/flow and ground water level from previous time step
    output["previous_accumulated_runoff"] = data[
        "surface_runoff_accumulated"
    ].to_numpy()
    output["previous_subsurface_flow_accumulated"] = data[
        "subsurface_flow_accumulated"
    ].to_numpy()
    output["groundwater_storage"] = data["groundwater_storage"].to_numpy()

    return output


import sys, os, platform
os_name=platform.system()
if (os_name=='Darwin'):
    rootpath='/Users/weiwilliam'
    rootarch='/Volumes/WD2TB/ResearchData'
elif (os_name=='Windows'):
    rootpath='F:\GoogleDrive_NCU\Albany'
    rootarch='F:\ResearchData'
    rootgit='F:\GitHub\swei_research'
elif (os_name=='Linux'):
    if (os.path.exists('/scratch1')):
        rootpath='/scratch2/BMC/gsd-fv3-dev/Shih-wei.Wei'
        rootarch='/scratch2/BMC/gsd-fv3-dev/Shih-wei.Wei/ResearchData'
        rootgit='/home/Shih-wei.Wei/research'
    elif (os.path.exists('/glade')):
        rootpath='/glade/work/swei/output/images'
        rootarch='/scratch2/BMC/gsd-fv3-dev/Shih-wei.Wei/ResearchData'
        rootgit='/glade/u/home/swei/research'
        machine='Cheyenne'
    elif (os.path.exists('/cardinal')):
        rootpath='/data/users/swei/Images'
        rootarch='/scratch2/BMC/gsd-fv3-dev/Shih-wei.Wei/ResearchData'
        rootgit='/home/swei/research'
        machine='S4'
sys.path.append(rootgit+'/pyscripts/functions')
from utils import setup_cmap, ndate
from plot_utils import setupax_2dmap,set_size
import setuparea as setarea
import xarray as xa
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.colors as mpcrs
import cartopy.crs as ccrs

# Plotting setup
mpl.rc('axes',titlesize=18,labelsize=16)
mpl.rc('xtick',labelsize=16)
mpl.rc('ytick',labelsize=16)
mpl.rc('legend',fontsize='xx-large')
axe_w=10; axe_h=5
quality=300

if (machine=='Cheyenne'):
   inputpath='/glade/work/dfgrogan/UFS/WM_DTAER/AER'
elif (machine=='S4'):
   inputpath='/data/users/swei/common/MERRA2'

outputpath=rootpath+'/Dataset/MERRA-2/2dmap/CMass_frac'
if ( not os.path.exists(outputpath) ):
    os.makedirs(outputpath)

sdate=2020062212
edate=2020062212
hint=6
area='Glb'
m2tag='inst3_3d_aer_Nv'
tkfreq=2
#species_lst=['dust','seas']
species_lst=['dust','seas','carbon','sulf']

#
clridx=np.array((2,8,9,10,12,14,15,16,18))
cn_cmap=setup_cmap('precip3_16lev',clridx)
cnlvs=np.array((0., 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1.0))
#clridx=[0,11,29,38,56,74,83,92,119,128]
#cn_cmap=setup_cmap('MPL_YlOrBr',clridx)
#cnlvs=[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
norm = mpcrs.BoundaryNorm(cnlvs,len(cnlvs))

# Constant configuration
proj=ccrs.PlateCarree()
grav=9.80665e+0

# Setup plotting area
minlon, maxlon, minlat, maxlat, crosszero, cyclic=setarea.setarea(area)
print(minlat,maxlat,minlon,maxlon,crosszero,cyclic)
if (area=='Glb'):
   minlon=-180. ; maxlon=180.
cornerll=[minlat,maxlat,minlon,maxlon]

dust_lst=['DU001','DU002','DU003','DU004','DU005']
seas_lst=['SS001','SS002','SS003','SS004','SS005']
carbon_lst=['OCPHILIC','OCPHOBIC','BCPHILIC','BCPHOBIC']
sulf_lst=['SO4']

syy=int(str(sdate)[:4]); smm=int(str(sdate)[4:6])
sdd=int(str(sdate)[6:8]); shh=int(str(sdate)[8:10])
eyy=int(str(edate)[:4]); emm=int(str(edate)[4:6])
edd=int(str(edate)[6:8]); ehh=int(str(edate)[8:10])

date1 = datetime(syy,smm,sdd,shh)
date2 = datetime(eyy,emm,edd,ehh)
delta = timedelta(hours=hint)
dates = pd.date_range(start=date1, end=date2, freq=delta)

tnum=0
dlist=[]
cdate=sdate
while (cdate<=edate):
    dlist.append(str(cdate))
    tnum=tnum+1
    cdate=ndate(hint,cdate)

dates_count=0
for date in dlist:
    yy=date[:4] ; mm=date[4:6] ; dd=date[6:8] ; hh=date[8:10]
    pdy=date[:8]
    if (yy=='2020' and mm=='09'):
       m2ind='401'
    else:
       m2ind='400'
# MERRA2_401.inst3_3d_aer_Nv.20200916_12Z.nc4
    if (machine=='Cheyenne'):
       infile=inputpath+'/MERRA2_'+m2ind+'.'+m2tag+'.'+pdy+'_'+hh+'Z.nc4'
       multi_time=0
    if (machine=='S4'):
       infile=inputpath+'/'+yy+'/'+mm+'/MERRA2_'+m2ind+'.'+m2tag+'.'+pdy+'.nc4'
       multi_time=1

    ds=xa.open_dataset(infile)
    if (multi_time):
       ds=ds.sel(time=dates[dates_count])
    else:
       ds=ds.sel(time=ds.time[0])
    if (area!='Glb'):
       ds=ds.sel(lon=slice(minlon,maxlon),lat=slice(minlat,maxlat))

    delp=ds.DELP
    kgkg_kgm2=delp/grav

    spidx=0
    for sp in species_lst:
        spc_conc=0.
        if (sp=='dust'):
           varlst=dust_lst
        if (sp=='seas'):
           varlst=seas_lst
        if (sp=='carbon'):
           varlst=carbon_lst
        if (sp=='sulf'):
           varlst=sulf_lst
        varidx=0
        for var in varlst:
           tmp=ds[var]*kgkg_kgm2
           if (sp=='dust'):
              if (varidx==0):
                 dust_conc=tmp
              else:
                 dust_conc=xa.concat((dust_conc,tmp),dim='bins')
           if (sp=='seas'):
              if (varidx==0):
                 seas_conc=tmp
              else:
                 seas_conc=xa.concat((seas_conc,tmp),dim='bins')
           if (sp=='carbon'):
              if (varidx==0):
                 carbon_conc=tmp
              else:
                 carbon_conc=xa.concat((carbon_conc,tmp),dim='bins')
           if (sp=='sulf'):
              if (varidx==0):
                 sulf_conc=tmp
              else:
                 sulf_conc=xa.concat((sulf_conc,tmp),dim='bins')
           spc_conc=spc_conc+tmp
           varidx+=1

        if (spidx==0):
           conc=spc_conc
        else:
           conc=xa.concat((conc,spc_conc),dim='species')
        spidx+=1

# Get the column integral
    cmass=conc.sum(dim='lev')
    cmass_total=cmass.sum(dim='species')

    spidx=0
    for sp in species_lst:
        cblb='%s Fraction of column mass density' %(sp)
        title='%s ' %(species_lst[spidx])
        outname='%s/%s_%s_cmass_frac.%s.png'  %(outputpath,area,species_lst[spidx],date)

        pltdata=cmass[spidx,:,:]/cmass_total
        fig,ax=setupax_2dmap(cornerll,area,proj,lbsize=16.)
        set_size(axe_w,axe_h,b=0.13,l=0.05,r=0.95,t=0.95)
        cn=ax.contourf(pltdata.lon,pltdata.lat,pltdata,levels=cnlvs,cmap=cn_cmap,norm=norm)
        ax.set_title(title)
        plt.colorbar(cn,ax=ax,orientation='horizontal',ticks=cnlvs[::tkfreq],
                     format='%.2f',fraction=0.045,aspect=40,pad=0.08,label=cblb)
        print(outname)
        fig.savefig(outname,dpi=quality)
        plt.close()

        if (sp=='dust'):
           varlst=dust_lst
           sp_cmass=dust_conc.sum(dim='lev')
        elif (sp=='seas'):
           varlst=seas_lst
           sp_cmass=seas_conc.sum(dim='lev')
        elif (sp=='carbon'):
           varlst=carbon_lst
           sp_cmass=carbon_conc.sum(dim='lev')
        elif (sp=='sulf'):
           varlst=sulf_lst
           sp_cmass=sulf_conc.sum(dim='lev')
        subspidx=0
        for subsp in varlst:
            cblb='%s Fraction of column mass density' %(subsp)
            title='%s ' %(subsp)
            outname='%s/%s_%s_cmass_frac.%s.png'  %(outputpath,area,subsp,date)
            if (sp=='sulf'):
               pltdata=sp_cmass/cmass_total
            else:
               pltdata=sp_cmass[subspidx,:,:]/cmass_total
            fig,ax=setupax_2dmap(cornerll,area,proj,lbsize=16.)
            set_size(axe_w,axe_h,b=0.13,l=0.05,r=0.95,t=0.95)
            cn=ax.contourf(pltdata.lon,pltdata.lat,pltdata,levels=cnlvs,cmap=cn_cmap,norm=norm)
            ax.set_title(title)
            plt.colorbar(cn,ax=ax,orientation='horizontal',ticks=cnlvs[::tkfreq],
                         format='%.2f',fraction=0.045,aspect=40,pad=0.08,label=cblb)
            print(outname)
            fig.savefig(outname,dpi=quality)
            plt.close()
            subspidx+=1

        spidx+=1

    dates_count+=1


# -*- coding: utf-8 -*-
from plone import api
from plone.app.standardtiles import PloneMessageFactory as _
from plone.app.z3cform.widget import RelatedItemsFieldWidget
from plone.autoform import directives
from plone.supermodel import model
from plone.supermodel.model import Schema
from plone.tiles import Tile
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope import schema

import json

class ISliderTile(Schema):
    """
    """
    collection_uid = schema.Choice(
        title=_(u"Select an existing content"),
        required=True,
        vocabulary="plone.app.vocabularies.Catalog",
    )
    directives.widget(
        "collection_uid",
        RelatedItemsFieldWidget,
        pattern_options={"selectableTypes": ["Collection"]},
    )

    infinite = schema.Bool(title=_(u"Infinite"), required=False, default=True)

    dots = schema.Bool(title=_(u"Dots"), required=False, default=True)

    arrows = schema.Bool(title=_(u"Arrows"), required=False, default=True)

    centerMode = schema.Bool(title=_(u"Center"), required=False, default=False)

    fade = schema.Bool(title=_(u"fade"), required=False, default=False)

    vertical = schema.Bool(title=_(u"vertical mode"), required=False, default=False)

    autoplay = schema.Bool(title=_(u"Autoplay mode"), required=False, default=False)

    pauseOnFocus = schema.Bool(title=_(u"pause on focus"), required=False, default=True)

    cssEase = schema.TextLine(
        title=_(u"ccsEase"),
        description=_(u"Use css ease"),
        required=False,
        default=_(u"linear"),
    )
    autoplaySpeed = schema.Int(
        title=_(u"autoplaySpeed"),
        description=_(u"Speed of autoplay"),
        required=False,
        default=2000,
    )

    slidesToShow = schema.Int(
        title=_(u"Show"),
        description=_(u"Slides to show"),
        required=False,
        default=4,
    )

    slidesToScroll = schema.Int(
        title=_(u"Scroll"),
        description=_(u"Slider to scroll"),
        required=False,
        default=4,
    )

    speed = schema.Int(
        title=_(u"Speed"),
        description=_(u"Speed"),
        required=False,
        default=300,
        min=100,
    )

    limit = schema.Int(
        title=_(u"Limit"),
        description=_(u"Limit of slides"),
        required=False,
        default=10,
    )
    size = schema.TextLine(
        title=_(u"Image size"),
        description=_(u"Size of image slider (ex: thumb, large,...)"),
        required=False,
        default=_(u"thumb"),
    )
    model.fieldset(
        'display',
        label=_(u'Display options'),
        fields=[
            'title',
            'description',
            'date',
            'all_button',
        ],
    )

    title = schema.Bool(
        title=_(u"Title"),
        required=False,
        default=True,
    )

    description = schema.Bool(
        title=_(u"Description"),
        required=False,
        default=True,
    )

    date = schema.Bool(
        title=_(u"Date"),
        required=False,
        default=True,
    )

    all_button = schema.Bool(
        title=_(u"All button"),
        required=False,
        default=True,
    )

    breakpoint_full = schema.Int(
        title=_(u"breakpoint_full"),
        description=_(u"What size to display in breakpoint_full"),
        required=False,
        default=1024,
    )
    slidesToShow_full = schema.Int(
        title=_(u"slidesToShow_full"),
        description=_(u"how many element to display in breakpoint_full"),
        required=False,
        default=1,
    )

    slidesToScroll_full = schema.Int(
        title=_(u"slidesToScroll_full"),
        description=_(u"how many element scroll in breakpoint_full"),
        required=False,
        default=1,
    )

    breakpoint_medium = schema.Int(
        title=_(u"breakpoint_medium"),
        description=_(u"What size to display in breakpoint_medium"),
        required=False,
        default=600,
    )
    slidesToShow_medium = schema.Int(
        title=_(u"slidesToShow_medium"),
        description=_(u"how many element to display in breakpoint_medium"),
        required=False,
        default=1,
    )
    slidesToScroll_medium = schema.Int(
        title=_(u"slidesToScroll_medium"),
        description=_(u"how many element scroll in breakpoint_medium"),
        required=False,
        default=1,
    )
    breakpoint_small = schema.Int(
        title=_(u"breakpoint_small"),
        description=_(u"What size to display in breakpoint_small"),
        required=False,
        default=480,
    )
    slidesToShow_small = schema.Int(
        title=_(u"slidesToShow_small"),
        description=_(u"how many element to display in breakpoint_small"),
        required=False,
        default=1,
    )
    slidesToScroll_small = schema.Int(
        title=_(u"slidesToScroll_small"),
        description=_(u"how many element scroll in breakpoint_small"),
        required=False,
        default=1,
    )

class SliderTile(Tile):
    """
    """

    template = ViewPageTemplateFile('templates/slider.pt')

    def __call__(self):
        return self.template()

    def contents(self):
        # import pdb; pdb.set_trace()
        uid = self.data["collection_uid"]
        attrs = json.dumps(
            {
                "infinite": self.data["infinite"],
                "dots": self.data["dots"],
                "slidesToShow": self.data["slidesToShow"],
                "slidesToScroll": self.data["slidesToScroll"],
                "speed": self.data["speed"],
                "arrows": self.data["arrows"],
                "centerMode": self.data["centerMode"],
                "fade": self.data["fade"],
                "vertical": self.data["vertical"],
                "cssEase": self.data["cssEase"],
                "autoplay": self.data["autoplay"],
                "autoplaySpeed": self.data["autoplaySpeed"],
                "pauseOnFocus": self.data["pauseOnFocus"],
                "responsive": [
                    {
                        "breakpoint": self.data["breakpoint_full"],
                        "settings": {
                            "slidesToShow": self.data["slidesToShow_full"],
                            "slidesToScroll":self.data["slidesToScroll_full"]
                        }
                    },
                    {
                        "breakpoint": self.data["breakpoint_medium"],
                        "settings": {
                            "slidesToShow": self.data["slidesToShow_medium"],
                            "slidesToScroll":self.data["slidesToScroll_medium"]
                        }
                    },
                    {
                        "breakpoint": self.data["breakpoint_small"],
                        "settings": {
                            "slidesToShow": self.data["slidesToShow_small"],
                            "slidesToScroll":self.data["slidesToScroll_small"]
                        }
                    }
                ]
            }
        )
        display = {
            "title": self.data["title"],
            "description": self.data["description"],
            "date": self.data["date"],
            "all_button": self.data["all_button"],
        }

        size = {
            "size": self.data["size"],
        }
        data = {
            "url": "",
            "results": [],
            "options": attrs,
            "display": display,
            "size": size,
        }
        limit = self.data["limit"]
        if uid and limit:
            collection = api.content.get(UID=uid)
            if collection:
                data["url"] = collection.absolute_url()
                if collection.portal_type == "Collection":
                    data["results"] = collection.queryCatalog(
                        batch=True, b_size=limit)
        return data


"""
Line Plot
============
Example showing color slicing with cosine, sine, sinc lines.
"""

# test_example = true

import fastplotlib as fpl
import numpy as np

plot = fpl.Plot()
# to force a specific framework such as glfw:
# plot = fpl.Plot(canvas="glfw")

xs = np.linspace(-10, 10, 100)
# sine wave
ys = np.sin(xs)
sine = np.dstack([xs, ys])[0]

# cosine wave
ys = np.cos(xs) + 5
cosine = np.dstack([xs, ys])[0]

# sinc function
a = 0.5
ys = np.sinc(xs) * 3 + 8
sinc = np.dstack([xs, ys])[0]

sine_graphic = plot.add_line(data=sine, thickness=5, colors="magenta")

# you can also use colormaps for lines!
cosine_graphic = plot.add_line(data=cosine, thickness=12, cmap="autumn")

# or a list of colors for each datapoint
colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25
sinc_graphic = plot.add_line(data=sinc, thickness=5, colors=colors)

plot.show()

# indexing of colors
cosine_graphic.colors[:15] = "magenta"
cosine_graphic.colors[90:] = "red"
cosine_graphic.colors[60] = "w"

# indexing to assign colormaps to entire lines or segments
sinc_graphic.cmap[10:50] = "gray"
sine_graphic.cmap = "seismic"

# more complex indexing, set the blue value directly from an array
cosine_graphic.colors[65:90, 0] = np.linspace(0, 1, 90-65)

# additional fancy indexing using numpy
key = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 67, 19])
sinc_graphic.colors[key] = "Red"

key2 = np.array([True, False, True, False, True, True, True, True])
cosine_graphic.colors[key2] = "Green"

plot.canvas.set_logical_size(800, 800)

plot.auto_scale()

if __name__ == "__main__":
    print(__doc__)
    fpl.run()


import requests
import bs4
import csv
import os
from urllib.request import urlopen
import re
from time import sleep
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

session = requests.Session()
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)

def crawler(start_page):
    page_list=[]
    page_list.append(start_page)
    for ind in range(len(page_list)+1):
        url=page_list[ind]
        link_list=[]
        link_list=getLinks(url)
        for i in range(len(link_list)):
            page_list.append(link_list[i])
        mail_list=getEmails(url)
        write_to_csv('test.csv', [mail_list])
        #write_to_csv('test_urls.csv', [page_list])

def write_to_csv(fname, to_be_saved):
    filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Desktop", fname)
    myFile = open(filename, 'w', newline='', encoding="utf-8")
    with myFile:
        writer = csv.writer(myFile)
        writer.writerows(to_be_saved)

def getLinks(url):
    link_list=[]
    source_code=session.get(url)
    soup = bs4.BeautifulSoup(source_code.text, "lxml")
    for a in soup.findAll('a', attrs={'href': re.compile("^http://")}):
        link_list.append(a['href'])
    return (link_list)

def getEmails(url):
    mail_list=[]
    source_code=session.get(url)
    soup = bs4.BeautifulSoup(source_code.text, "lxml")
    results = re.findall(r'[\w\.-]+@[\w\.-]+\.\w+', str(soup))
    for et in results:
        mail_list.append(et)
    return (mail_list)

crawler("http://heller.uni-corvinus.hu/kapcsolat/")


#-*- coding: UTF-8 -*-

# http://debugo.com/apscheduler/
# from apscheduler.schedulers.blocking import BlockingScheduler

# def my_job():
#     print 'hello world'

# sched = BlockingScheduler()
# sched.add_job(my_job, 'interval', seconds=1)
# sched.start()

 #coding=utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
import time
import os
import logging
import psutil

#设置一个日志输出文件
log_filename="logging.txt"

#设置日志输出格式
log_format=' [%(asctime)s]   %(message)s'

#将日志文件格式化
logging.basicConfig (format=log_format,datafmt='%Y-%m-%d %H:%M:%S %p',level=logging.DEBUG,filename=log_filename,filemode='a')

def tick():
	print('Tick! The time is: %s' % datetime.now())
	logging.debug('Tick! The time is : %s' % datetime.now())

# 开启定时任务
if __name__ == '__main__':
	scheduler = BlockingScheduler()
	scheduler.add_job(tick,'cron', second='*/1', hour='*')
	print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
	try:
		scheduler.start()
	except (KeyboardInterrupt, SystemExit):
		scheduler.shutdown()


from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^', include('mimi.urls')),
    url(r'^grade/', include('grade.urls')),
    url(r'^abt-grade/$', 'grade.views.about'),
    url(r'^static/(?P<path>.*)$', 'django.views.static.serve',{'document_root': '/static/'}),
)


class Solution:
    def reverseParentheses(self, s: str) -> str:
        stack = []
        for char in s:
            temp = []
            if char != ')':
                stack.append(char)
            else:
                while stack[-1] != '(':
                    temp.append(stack.pop())
                stack.pop()
                for i in temp:
                    stack.append(i)
        return ''.join(stack)


import numpy as np
import ProMP as pmp

def learn_promp(num_gaba, n_demonstrations, folder="Demonstrations"):

    # load demonstrated trajectories
    demonstrations = []  # A list of ndarrays
    for index in range(n_demonstrations):
        string = folder + "/traj" + str(index) + ".out"
        traj = np.genfromtxt(string, delimiter=',')
        demonstrations.append(traj)

    # learn ProMP
    myProMP = pmp.ProMP(demonstrations, num_gaba)  # The trajectories in demonstrations are time-aligned by ProMP.

    return myProMP


import random
from PyFakeDados.utils import remover_acentos

dominios = ['com', "com.br", "xyz", "info",]

def gerar_site(nome):

    nome = remover_acentos(nome)
    nome = nome.lower().replace(" de ", "").replace(" da ", "").replace(
        " do ", "").replace(" das ", "").replace(" dos ", "").replace(" ", "")
    dominio = random.choice(dominios)
    site = f'{nome}.{dominio}'
    return site


import random

name = input('Podaj swoję imię: ')
number = int(input(f'Podaj liczbę od 1 do 25: '))

bla = random.randint(1, 25)

if __name__ == "__main__":
    while number != bla:
        if bla < number:
            print(f'Przykro mi {name} Twoja liczba jest mniejsza od {number}!')
            number = int(input("Podaj liczbę: "))
        else:
            print(f"Przykro mi {name} Twoja liczba jest większa od {number}!")
            number = int(input("Podaj liczbę: "))

print(f'Brawo {name} zgadłeś!')


from poker.validators import RankAndSuitValidator

class StraightValidator(RankAndSuitValidator):
    def __init__(self, cards):
        self.cards = cards
        self.name = "Straight"
        self.list_rank_indexes = [card.rank_index for card in self.cards]

    def is_valid(self):
        if len(self.cards) < 5:
            return False

        return len(self._five_consecutive_cards(self.list_rank_indexes)) > 0

    def valid_cards(self):
        consec_cards = [
            card for card in self.cards
            if card.rank_index in self._five_consecutive_cards(self.list_rank_indexes)
            ]
        valid_cards = [consec_cards[-1]]
        for card in consec_cards:
            if card.rank != valid_cards[-1].rank:
                valid_cards.append(card)
        return valid_cards[-5:]


# 위에서 부터 내려가면 중간에 계산 결과도 저장해줘야 하고, 그 때마다의 값 최신화를 해줘야 한다고 생각해서 밑에서 역으로 올라오면서 리스트내의 원소를 바꾸는 방법을 생각했다.
# 처음에 삼각형을 슬라이싱을 통해 역으로 뒤집어 주었고, 큰 삼각형은 결국 작은 삼각형으로 구성되있다는 생각으로 작은 삼각형으로 하나하나 쪼개어서 밑변의 2개의 수와 윗변의 수를 각각 더한 것의 최대 값으로 윗변의 리스트를 재 구성하는 방식을 재귀 방식으로
# dp를 구현하였다.

def solution(triangle):
    triangle = triangle[::-1]
    # 	[[4, 5, 2, 6, 5], [2, 7, 4, 4], [8, 1, 0], [3, 8], [7]]

    # 밑에서 부터 삼각형 한 줄씩 반복
    for i in range(len(triangle)):
        # 큰 삼각형 안에서 작은 삼각형으로 쪼개어서 하나씩 연산, 윗변의 값과 밑변의 2개의 값을 각각 더해주고 둘 중의 최대값으로 리스트 재구성해서 로직 반복 하여 삼각형 맨 위의 값을 출력
        for j in range(len(triangle[i])-1):
            triangle[i+1][j] = max((triangle[i][j] + triangle[i+1][j]) , (triangle[i][j+1] + triangle[i+1][j]))

    # 삼각형 맨 끝 값 반환
    return triangle[len(triangle)-1][0]

print(solution([[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]]))

프로그래머스/lv3/43105. 정수 삼각형/정수 삼각형.py


import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
import tokens
import discord
import json
import datetime
import pytz
import requests

apiurl="https://developer-lostark.game.onstove.com/markets/items/"
key=tokens.apikey

def market_item(item_name : str):
    with open("./market/itemcode.json","r",encoding="UTF-8") as file:
        itemcode=json.load(file)
    item_id=itemcode[item_name]['id']
    selected_item=itemcode[item_name]['Name']
    url=apiurl+item_id
    headers={'accept': 'application/json', 'authorization': 'bearer '+tokens.apikey,'Content-Type':'application/json'}
    try:
        response=requests.get(url,headers=headers).json()

        for i in response:
            if (i.get("TradeRemainCount")==0) or (i.get("TradeRemainCount")==None):
                embed=discord.Embed(title=f'**:gem: {selected_item}의 시세** ' ,timestamp=datetime.datetime.now(pytz.timezone('UTC')), color=0x00ff00)
                if i.get("Stats")[0].get("TradeCount")==0:
                    embed.add_field(name=":moneybag: 금일 평균가 : :x: 거래량이 없습니다.",value='\n', inline=False)
                else:
                    embed.add_field(name=f':moneybag: 금일 평균가 : {i.get("Stats")[0].get("AvgPrice")}' ,value='\n', inline=False)
                if i.get("Stats")[1].get("TradeCount")==0:
                    embed.add_field(name=":moneybag: 전일 평균가 : :x: 거래량이 없습니다.",value='\n', inline=False)
                else:
                    embed.add_field(name=f':moneybag: 전일 평균가 : {i.get("Stats")[1].get("AvgPrice")}', value='\n',inline=False)
                embed.set_footer(text="Made by.ㅈㅇㅈ#0081")
                return embed
    except Exception as e:
        embed=discord.Embed(title="오류 발생",timestamp=datetime.datetime.now(pytz.timezone('UTC')),color=0x9B0000)
        embed.add_field(name=e,value="\n",inline=False)
        return embed


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import os.path
import signal
from pprint import pprint
import argparse
import subprocess
import time
import cv2
import numpy as np

sys.path.append('scripts')
from utils.pretty_print import print_cyan
from utils.process import (
    create_process,
    get_process_cfg,
)
from img_toolbox.ffmpeg_utils import (
    execute_simple_ffmpeg_command
)

FPS = 25

def concatenate_images_to_video(image_list, output_path, suffix='', fps=FPS, interlace=False):
    start_time = time.time()

    suffix = f"output_{suffix}" if suffix != '' else "output"

    # Create output folder
    if not os.path.exists(output_path):
        os.makedirs(output_path)

    # Create the concatenation file
    duration_str = "duration %.02f\n" % (1/float(FPS))
    input_path = os.path.dirname(image_list[0])
    concatenation_filepath = os.path.join(input_path, f"concatenation_{suffix}.txt")
    concatenation_file = open(concatenation_filepath, "w")
    for f in image_list:
        concatenation_file.write(f"file \'{os.path.abspath(f)}\'\n")
        concatenation_file.write(duration_str)
    concatenation_file.close()

    # Create command
    ffmpeg_verbose="-hide_banner -loglevel warning"
    ffmpeg_verbose = ''
    if sys.platform == 'win32':
        ffmpeg_command = [os.path.abspath("../mco_3rd_party/ffmpeg-5.1/bin/ffmpeg.exe")]
    else:
        ffmpeg_command = [os.path.abspath("../mco_3rd_party/ffmpeg-5.1/ffmpeg")]
    # ffmpeg_command.extend(ffmpeg_verbose.split(' '))
    # suffix_2 = '_%sfps' % (fps) if fps != FPS else ''
    # suffix_2 = '_%sfps' % (fps)
    # interlace_str = "-vf tinterlace=interleave_top,fieldorder=tff"
    # ffmpeg_command.extend([
    #     "-r", f"{fps}",
    #     "-f", "concat",
    #     "-safe", "0",
    #     "-i", concatenation_filepath,

    #     "-vcodec", "ffv1",
    #     "-level", "1",
    #     "-coder", "1 ",
    #     "-context", "1",
    #     "-g", "1",
    #     "-y", os.path.join(output_path, f"{suffix}.mkv")
    # ])

    ffmpeg_command.extend([
        "-r", f"{fps}",
        "-f", "concat",
        "-safe", "0",
        "-i", concatenation_filepath,

        # "-filter_complex", f"[0:v]scale=iw*0.5:-1:sws_flags=bicubic[outv]",
        # "-map", "[outv]"
    ])

    if sys.platform == 'win32':
        ffmpeg_command.extend([
            "-vcodec", "ffv1",
        ])
    else:
        ffmpeg_command.extend([
            "-tune", "animation",
            "-pix_fmt", "yuv420p",
            "-preset", "medium",
            "-crf",  "15",
        ])

    ffmpeg_command.extend(["-y", os.path.join(output_path, f"{suffix}.mkv")])

    print(' '.join(ffmpeg_command))
    # process_cfg = get_process_cfg()
    # execute_simple_ffmpeg_command(ffmpeg_command)

    result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout = result.stdout.decode('utf-8')
    stderr = result.stderr.decode('utf-8')
    if len(stdout) > 0:
        print(stdout)
    if len(stderr) > 0:
        print(stderr)

    print_cyan("generated in %.02f" % (time.time() - start_time))

def concatenate_images_to_video_cv2(image_list, output_path, suffix='', fps=FPS, interlace=False):
    process_cfg = get_process_cfg()

    start_time = time.time()

    #  TODO: rework output filename
    if suffix != '':
        suffix = "_%s" % (suffix)

    # Create output folder
    if not os.path.exists(output_path):
        os.makedirs(output_path)

    # Open images
    images = [cv2.imread(f_input, cv2.IMREAD_COLOR) for f_input in image_list]

    start_time2 = time.time()

    height, width, c = images[0].shape
    frame_count = len(images)

    # Create command
    if sys.platform == 'win32':
        ffmpeg_command = [os.path.abspath("../3rd_party/ffmpeg-5.1/bin/ffmpeg.exe")]
    else:
        ffmpeg_command = [os.path.abspath("../3rd_party/ffmpeg-5.1/ffmpeg")]
    ffmpeg_command.extend([
        "-hide_banner",
        "-loglevel", "error",

        '-f', 'rawvideo',
        '-pixel_format', 'bgr24',
        '-video_size', "%dx%d" % (width, height),
        "-r", str(FPS),
        '-i', 'pipe:0',

        "-tune", "animation",
        "-c:v", "libx264",
        "-preset", "medium"
    ])
    ffmpeg_command.extend([
        "-crf",  "17",
        "-y", os.path.join(output_path, "f_ep01_%s_cv2.mkv" % (suffix))
    ])
    img_size = width * height * 3
    bufsize = int(10 * img_size * 1.10)

    if False:
        bytes_arr = bytearray()
        for img in images:
            bytes_arr.extend(img.tobytes())
        process = create_process(ffmpeg_command, process_cfg, bufsize=bufsize)
        stdout, stderr = process.communicate(input=bytes(bytes_arr))
    elif False:
        bytes_arr = bytearray()
        for img in images:
            bytes_arr += img.tobytes()
        process = create_process(ffmpeg_command, process_cfg, bufsize=bufsize)
        stdout, stderr = process.communicate(input=bytes(bytes_arr))
    elif True:
        process = create_process(ffmpeg_command, process_cfg, bufsize=bufsize)
        for img in images:
            process.stdin.write(img.tobytes())
        stdout, stderr = process.communicate()

    print("spent_time= %.02f" % (time.time() - start_time2))
    print_cyan("spent_time= %.02f" % (time.time() - start_time))

def main():
    parser = argparse.ArgumentParser(description="concat to mkv file")
    parser.add_argument("--input", "-i", type=str, required=True)
    parser.add_argument("--suffix", "-s", type=str, required=True)
    parser.add_argument("--output", "-o", type=str, default='outputs', required=False)
    parser.add_argument("--fps", type=int, choices=[FPS, 2*FPS], default=FPS, required=False)
    arguments = parser.parse_args()

    input_path = os.path.normpath(arguments.input)
    output_path = os.path.basename(arguments.output)
    suffix = arguments.suffix

    # List images
    image_list = list()
    for f in os.listdir(input_path):
        if f.endswith(".png") and suffix in f:
            image_list.append(os.path.join(input_path, f))
    image_list = sorted(image_list)

    if len(image_list) == 0:
        sys.exit("No images found")

    # Concatenate images into a video (ffv1)
    if True:
        concatenate_images_to_video(image_list=image_list,
            output_path=output_path,
            suffix=suffix,
            fps=arguments.fps)
    else:
        concatenate_images_to_video_cv2(image_list=image_list,
            output_path=output_path,
            suffix=suffix)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    main()


# -*- coding: utf-8 -*-
"""
Created on Fri Jan  5 21:38:47 2018

@author: Nitin
"""

"""
Given two strings s and t, write a function to determine if t is an anagram of s.

For example,
s = "anagram", t = "nagaram", return true.
s = "rat", t = "car", return false.

Note:
You may assume the string contains only lowercase alphabets.

Follow up:
What if the inputs contain unicode characters? How would you adapt your solution to such case?
"""

def validAnagram_nlogn(s,t):
    if len(s) == len(t):
        return True
    if sorted(s) == sorted(t):
        return True
    return False

def validAnagram_n(s,t):
    vec = [0]*256
    for ch in s:
        vec[ord(ch)] += 1
    for ch in t:
        vec[ord(ch)] -= 1
    if vec == [0]*256: return True
    return False

s = "anagram"
t = "nagarams"

print(validAnagram_nlogn(s,t))
print(validAnagram_n(s,t))


'''
JOIN homework
1.
  a. Add another table to your cars database called orders
     should have the follwing fields
       make        TEXT
       model       TEXT
       order_date  DATE (Sqlite3 does not have datatypes for dates, we can use SQLite functions for comparing date values
  b. add 15 records (3 for each car), each with separate order dates

2. Output the car's make and model on one line, the quantity on another line, and then the
   order_dates on the subsequent lines.
   ex:
       Car: Ford Focus ST
       Quantity: 7
       Orderdates:
           2020-01-23
           2020-02-23
'''

import sqlite3

with sqlite3.connect("cars.db") as connection:
    c = connection.cursor()

    c.execute("""CREATE TABLE orders (make TEXT, model TEXT, order_date  TEXT)""")

    orders = [
        ('Ford', 'Focus ST', '2020-01-23'),
        ('Ford', 'Focus ST', '2020-01-07'),
        ('Ford', 'Focus ST', '2020-02-29'),
        ('Ford', 'Edge ST', '2020-02-27'),
        ('Ford', 'Edge ST', '2020-01-01'),
        ('Ford', 'Edge ST', '2020-02-04'),
        ('Ford', 'F-150', '2020-02-05'),
        ('Ford', 'F-150', '2020-01-26'),
        ('Ford', 'F-150', '2020-02-23'),
        ('Honda', 'Civic Type-R', '2020-02-14'),
        ('Honda', 'Civic Type-R', '2020-01-21'),
        ('Honda', 'Civic Type-R', '2020-02-21'),
        ('Honda', 'Accord', '2020-02-09'),
        ('Honda', 'Accord', '2020-01-22'),
        ('Honda', 'Accord', '2020-02-01')
    ]

    c.executemany("INSERT INTO orders (make, model, order_date) VALUES(?, ?, ?)", orders)

    order_text = '''Car: {} {}
Quantity: {}
Order Dates:
        {}
___
     '''

    c.execute("""SELECT i.make, i.model, i.quantity, GROUP_CONCAT(o.order_date, '\n\t') order_dates
              FROM orders as o INNER JOIN inventory as i on i.make = o.make AND i.model = o.model
              GROUP BY i.make, i.model""")

    rows = c.fetchall()

    for r in rows:
        print(order_text.format(*r))


import random

# val = random.random()
# print(val)

# uval = random.uniform(1, 10)

# print(uval)

# rval = random.randint(0, 1)

# print(rval)

# greetings = ['hello', 'hej', 'halloj', 'hejhejhej', 'hejhejhejhej']

# val = random.choice(greetings)
# print(f'{val}, mannen')

# roulette_colors = ['green', 'black', 'red']

# roulette_results = random.choices(roulette_colors, weights=[2, 18, 18], k=10)
# print(roulette_results)

deck = list(range(1, 53))

random.shuffle(deck)  #Choice kan ta samma kort flera ganger. Shuffle battre.

hand = random.sample(deck, k=5)

print(hand)


import logging

from odoo import _, api, models
from odoo.exceptions import ValidationError

from odoo.addons.account.models.sequence_mixin import SequenceMixin

_logger = logging.getLogger(__name__)

class AccountMove(models.Model):
    _inherit = "account.move"

    @property
    def _sequence_monthly_regex(self):
        return (
            self.env.context.get("regex_fallback")
            and SequenceMixin._sequence_monthly_regex
            or super()._sequence_monthly_regex
        )

    @property
    def _sequence_yearly_regex(self):
        return (
            self.env.context.get("regex_fallback")
            and SequenceMixin._sequence_yearly_regex
            or super()._sequence_yearly_regex
        )

    @property
    def _sequence_fixed_regex(self):
        """
        We keep the standard _sequence_fixed_regex since this one is used
        in the _compute_split_sequence to compute the record.sequence_prefix
        and record.sequence_number.
        When an old invoice has been created with a numbering scheme
        that is not compatible with the latest sequence_override_regex
        the _compute_split_sequence may fail for this sequence.
        Falling back to the standard sequence bypasses this issue.
        Remark:
        A more complete fix would be support a sequence_override_regex
        per date range. We may extend this module to do so once we
        encounter a concrete customer case requiring this.
        """
        return SequenceMixin._sequence_fixed_regex

    @api.depends(lambda self: [self._sequence_field])
    def _compute_split_sequence(self):
        for rec in self:
            if not rec.name or rec.name == "/":
                rec.sequence_prefix = None
                rec.sequence_number = None
            else:
                super(AccountMove, rec)._compute_split_sequence()
        return

    def _constrains_date_sequence(self):
        """
        Bypass constraint for account.move,name = "/".
        Not doing so makes the regex syntax too complex for the
        account.journal,sequence_override_regex field that becomes available
        on the User Interface (Journal Settings) via this module.
        """
        return super(
            AccountMove, self.filtered(lambda r: r.name != "/")
        )._constrains_date_sequence()

    def _get_starting_sequence(self):
        starting_sequence = super()._get_starting_sequence()
        journal = self.journal_id
        if journal.refund_sequence and self.move_type in ("out_refund", "in_refund"):
            starting_sequence = journal.refund_starting_sequence or starting_sequence
        else:
            starting_sequence = journal.starting_sequence or starting_sequence
        return starting_sequence

    @api.model
    def _deduce_sequence_number_reset(self, name):
        ret_val = "never"
        if not name:
            name = self._get_starting_sequence()
        try:
            ret_val = super()._deduce_sequence_number_reset(name)
        except ValidationError:
            if self._sequence_monthly_regex != SequenceMixin._sequence_monthly_regex:
                warn_msg = _(
                    "Error detected while processing sequence regex: %(smr)s, "
                    "fallback to standard regex: %(msmr)s"
                ) % {
                    "smr": self._sequence_monthly_regex,
                    "msmr": SequenceMixin._sequence_monthly_regex,
                }
                self = self.with_context(regex_fallback=True)
                _logger.warning(warn_msg)
                ret_val = super()._deduce_sequence_number_reset(name)
        return ret_val

    def _get_sequence_format_param(self, previous):

        if previous == "/":
            return "never"
        return super()._get_sequence_format_param(previous)


#!/usr/bin/env python3

import argparse
import sys

sys.path.insert(0, '../../')

from survy.core.intercom import Reply
from survy.core.signal import SignalManager
from survy.core.client import Client

parser = argparse.ArgumentParser()
parser.add_argument("device", help="Task to run")
parser.add_argument("sub", help="Json Payload")

args = parser.parse_args()

client = Client()
res = client.send(
    message_type=SignalManager.INTERCOM_MESSAGE_DO_FIRE,
    message_payload={
        'device': args.device,
        'sub': args.sub
    }
)

if (res is None) or (res['status'] == Reply.INTERCOM_STATUS_FAILURE):
    print("Unknown signal")
    sys.exit(-1)

print("Signal sent")
sys.exit(0)


from __future__ import print_function
import isce
from iscesys.Component.Component import Component,Port
from iscesys.Compatibility import Compatibility
from stdproc.stdproc.crossmul import crossmul
import numpy as np

class Crossmul(Component):

    def crossmul(self, image1=None, image2=None, imageInt=None, imageAmp=None):

#        for port in self._inputPorts:
#            port()

        if image1 is not None:
            self.image1 = image1
        if self.image1 is None:
            raise Exception

        if image2 is not None:
            self.image2 = image2
        if self.image2 is None:
            raise Exception

        if imageInt is not None:
            self.imageInt= imageInt
        if self.imageInt is None:
            raise Exception

        if imageAmp is not None:
            self.imageAmp= imageAmp
        if self.imageAmp is None:
            raise Exception

        image1Accessor = self.image1.getImagePointer()
        image2Accessor = self.image2.getImagePointer()
        #create the int and amp file to allow random access
#        lengthIntAmp = np.ceil(self.width / (self.LooksDown*1.0))
#        self.imageInt.createFile(lengthIntAmp)
#        self.imageAmp.createFile(lengthIntAmp)
        imageIntAccessor = self.imageInt.getImagePointer()
        imageAmpAccessor = self.imageAmp.getImagePointer()

        #remember we put the offset for the images in one array
        # so twice the length
        self.setState()
        crossmul.crossmul_Py(self._ptr, image1Accessor,
                         image2Accessor,
                         imageIntAccessor,
                         imageAmpAccessor)
        self.imageAmp.bandDescription = ['amplitude slc1','amplitude slc2']
        self.imageInt.finalizeImage()
        self.imageAmp.finalizeImage()
        self.imageInt.renderHdr()
        self.imageAmp.renderHdr()

        #since the across and down offsets are returned in one array,
        # just split it for each location  #should be an even number
        return

    def setState(self):
        crossmul.setWidth_Py(self._ptr, self.width)
        crossmul.setLength_Py(self._ptr, self.length)
        crossmul.setLooksAcross_Py(self._ptr, self.LooksAcross)
        crossmul.setLooksDown_Py(self._ptr, self.LooksDown)
        crossmul.setBlocksize_Py(self._ptr, self.blocksize)
        crossmul.setScale_Py(self._ptr, self.scale)
        print('Completed set State')
        return

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

        self.width = None
        self.length = None
        self.LooksAcross  = None
        self.LooksDown = None
        self.scale = 1.0
        self.blocksize = 1024
        self._ptr = crossmul.createCrossMul_Py()

        self.image1 = None
        self.image2 = None
        self.imageInt = None
        self.imageAmp = None

        self.dictionaryOfVariables = {}
        self.dictionaryOfOutputVariables = {}
        self.descriptionOfVariables = {}
        self.mandatoryVariables = []
        self.optionalVariables = []
        typePos = 2
        for key , val in self.dictionaryOfVariables.items():
            if val[typePos] == 'mandatory':
                self.mandatoryVariables.append(key)
            elif val[typePos] == 'optional':
                self.optionalVariables.append(key)
            else:
                print('Error. Variable can only be optional or mandatory')
                raise Exception
            pass
        return

    #Temporary fix for segfault - ML 2014-03-08
    #def __del__(self):
    #    crossmul.destroyCrossMul_Py(self._ptr)

    def createPorts(self):
        return None

    pass

if __name__ == '__main__':

    import isceobj
    from iscesys.ImageUtil.ImageUtil import ImageUtil as IU
    import numpy as np

    def load_pickle(step='correct'):
        import cPickle

        iObj = cPickle.load(open('PICKLE/{0}'.format(step),'rb'))
        return iObj

    rlooks =1
    alooks = 1

    iObj = load_pickle()

    objSlc1 = iObj.topoIntImage
    objSlc1.setAccessMode('read')
    objSlc1.createImage()

    wid = objSlc1.getWidth()
    lgth = objSlc1.getLength()

    objSlc2 = isceobj.createSlcImage()
    objSlc2.initImage(iObj.topophaseMphFilename, 'read', wid)
    objSlc2.createImage()

    objInt = isceobj.createIntImage()
    objInt.setFilename('test.int')
    objInt.setWidth(wid/rlooks)
    objInt.setAccessMode('write')
    objInt.createImage()

    objAmp = isceobj.createAmpImage()
    objAmp.setFilename('test.amp')
    objAmp.setWidth(wid/rlooks)
    objAmp.setAccessMode('write')
    objAmp.createImage()

    mul = Crossmul()
    mul.width = wid
    mul.length = lgth
    mul.LooksAcross = rlooks
    mul.LooksDown = alooks
    mul.scale = 1.0
    mul.blocksize = 100

    mul.crossmul(objSlc1, objSlc2, objInt, objAmp)

    objSlc1.finalizeImage()
    objSlc2.finalizeImage()
    objInt.finalizeImage()
    objAmp.finalizeImage()


# python3
# -*- coding: utf-8 -*-
"""
Created on Fri Sep  2 11:44:54 2016

@author: Nikolay_Semyachkin
"""

m = 10
inp = [int(i) for i in input().split()]
n1 = inp[0] - 1
n2 = inp[1]

def F():
    yield 0
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

def pisano(m):
    period = []#[0, 1]
    fib = F()
    while True:
        period.append(next(fib) % m)
        if (period[-1] == 1) and (period[-2] == 0) and len(period) > 2:
            break
    return period[:-2]

def find_sum(n, pis):
    ind = (n+2) % len(pis)
    ans = pis[ind]
    if ans == 0:
        return 9
    else:
        return ans - 1

pis = pisano(m)
ans1 = find_sum(n1, pis)
ans2 = find_sum(n2, pis)
#print(find_sum(n, pis))
nums = [i for i in range(10)]
print(nums[ans2-ans1])
#ind = (n+2) % len(pis)
#ans = pis[ind]
#if ans == 0:
#    print(9)
#else:
#    print(ans - 1)


class Solution:
    def reorderedPowerOf2(self, N: int) -> bool:
        l = [list(str(2**i)) for i in range(32)]
        for i in l:
            i.sort()
        s = list(str(N))
        s.sort()
        for i in l:
            if i == s:
                return True
        return False