# -*- coding: utf-8 -*-
import json
import sys
import textwrap

from .constants import BOIL_EVAPORATION
from .constants import GAL_PER_LITER
from .constants import GRAIN_TYPE_DME
from .constants import GRAIN_TYPE_LME
from .constants import HOP_TYPE_PELLET
from .constants import HOP_UTILIZATION_SCALE_PELLET
from .constants import HOPS_CONSTANT_IMPERIAL
from .constants import HOPS_CONSTANT_SI
from .constants import IMPERIAL_TYPES
from .constants import IMPERIAL_UNITS
from .constants import LITER_PER_GAL
from .constants import PPG_DME
from .constants import PPG_CEREAL
from .constants import SI_TYPES
from .constants import SI_UNITS
from .constants import WATER_WEIGHT_IMPERIAL
from .constants import WATER_WEIGHT_SI
from .exceptions import ColorException
from .exceptions import RecipeException
from .grains import GrainAddition
from .hops import HopAddition
from .utilities.abv import alcohol_by_volume_alternative
from .utilities.abv import alcohol_by_volume_standard
from .utilities.abv import alcohol_by_weight
from .utilities.abv import final_gravity_from_abv_standard
from .utilities.color import calculate_mcu
from .utilities.color import calculate_srm
from .utilities.color import calculate_srm_daniels
from .utilities.color import calculate_srm_morey
from .utilities.color import calculate_srm_mosher
from .utilities.color import srm_to_ebc
from .utilities.efficiency import get_wort_correction
from .utilities.hops import HopsUtilizationGlennTinseth
from .utilities.sugar import gu_to_sg
from .utilities.sugar import sg_to_gu
from .utilities.sugar import sg_to_plato
from .validators import validate_grain_type
from .validators import validate_hop_type
from .validators import validate_optional_fields
from .validators import validate_percentage
from .validators import validate_required_fields
from .validators import validate_units

__all__ = [u"Recipe", u"RecipeBuilder"]

class Recipe(object):
    """
    A representation of a Recipe that can be brewed to make beer.
    """

    grain_lookup = {}
    hop_lookup = {}

    def __init__(
        self,
        name,
        grain_additions=None,
        hop_additions=None,
        yeast=None,
        brew_house_yield=0.70,
        start_volume=7.0,
        final_volume=5.0,
        units=IMPERIAL_UNITS,
    ):
        """
        :param str name: The name of the recipe
        :param grain_additions: A list of Grain Additions
        :type grain_additions: list of GrainAddition objects
        :param hop_additions: A list of Hop Additions
        :type hop_additions: list of HopAddition objects
        :param float brew_house_yield: The brew house yield
        :param float start_volume: The starting volume of the wort
        :param float final_volume: The final volume of the wort
        :param str units: The units
        :raises RecipeException: If the units of any GrainAddition is not the same as the units of the Recipe
        :raises RecipeException: If the units of any HopAddition is not the same as the units of the Recipe
        """  # noqa
        self.name = name
        if grain_additions is None:
            grain_additions = []
        self.grain_additions = grain_additions
        if hop_additions is None:
            hop_additions = []
        self.hop_additions = hop_additions
        self.yeast = yeast

        self.brew_house_yield = validate_percentage(brew_house_yield)  # noqa
        self.start_volume = start_volume
        self.final_volume = final_volume

        # Manage units
        self.set_units(units)

        # For each grain and hop:
        # 1. Add to lookup
        # 2. Ensure all units are the same
        for grain_add in self.grain_additions:
            self.grain_lookup[grain_add.grain.name] = grain_add
            if grain_add.units != self.units:
                raise RecipeException(
                    u"{}: Grain addition units must be in '{}' not '{}'".format(  # noqa
                        self.name, self.units, grain_add.units
                    )
                )
        for hop_add in self.hop_additions:
            # The same hops may be used several times, so we must distinguish
            hop_key = u"{}_{}".format(hop_add.hop.name, hop_add.boil_time)
            self.hop_lookup[hop_key] = hop_add
            if hop_add.units != self.units:
                raise RecipeException(
                    u"{}: Hop addition units must be in '{}' not '{}'".format(  # noqa
                        self.name, self.units, hop_add.units
                    )
                )

    def __str__(self):
        if sys.version_info[0] >= 3:
            return self.__unicode__()
        else:
            return self.__unicode__().encode(u"utf8")

    def __unicode__(self):
        return self.name

    def __repr__(self):
        out = u"{0}('{1}'".format(type(self).__name__, self.name)
        if self.grain_additions:
            out = u"{0}, grain_additions=[{1}]".format(
                out, u", ".join([repr(h) for h in self.grain_additions])
            )  # noqa
        if self.hop_additions:
            out = u"{0}, hop_additions=[{1}]".format(
                out, u", ".join([repr(h) for h in self.hop_additions])
            )  # noqa
        if self.yeast:
            out = u"{0}, yeast={1}".format(out, repr(self.yeast))
        if self.brew_house_yield:
            out = u"{0}, brew_house_yield={1}".format(
                out, self.brew_house_yield
            )  # noqa
        if self.start_volume:
            out = u"{0}, start_volume={1}".format(out, self.start_volume)
        if self.final_volume:
            out = u"{0}, final_volume={1}".format(out, self.final_volume)
        if self.units:
            out = u"{0}, units={1}".format(out, self.units)
        out = u"{0})".format(out)
        return out

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        if (
            (self.name == other.name)
            and (self.grain_additions == other.grain_additions)
            and (self.hop_additions == other.hop_additions)
            and (self.yeast == other.yeast)
            and (self.brew_house_yield == other.brew_house_yield)
            and (self.start_volume == other.start_volume)
            and (self.final_volume == other.final_volume)
            and (self.units == other.units)
        ):
            return True
        return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def set_units(self, units):
        """
        Set the units and unit types

        :param str units: The units
        """
        self.units = validate_units(units)
        if self.units == IMPERIAL_UNITS:
            self.types = IMPERIAL_TYPES
        elif self.units == SI_UNITS:
            self.types = SI_TYPES

    def change_units(self):
        """
        Change units of the class from one type to the other

        :return: Recipe in new unit type
        :rtype: Recipe
        """
        if self.units == IMPERIAL_UNITS:
            start_volume = self.start_volume * LITER_PER_GAL
            final_volume = self.final_volume * LITER_PER_GAL
            units = SI_UNITS
        elif self.units == SI_UNITS:
            start_volume = self.start_volume * GAL_PER_LITER
            final_volume = self.final_volume * GAL_PER_LITER
            units = IMPERIAL_UNITS
        return Recipe(
            self.name,
            grain_additions=[ga.change_units() for ga in self.grain_additions],
            hop_additions=[ha.change_units() for ha in self.hop_additions],
            yeast=self.yeast,
            brew_house_yield=self.brew_house_yield,
            start_volume=start_volume,
            final_volume=final_volume,
            units=units,
        )

    def get_total_points(self):
        """
        Get the total points of the recipe

        :return: PPG or HWE depending on the units of the Recipe
        :rtype: float
        """
        total_points = 0
        for grain_add in self.grain_additions:
            # DME and LME are 100% efficient in disolving in water
            # Cereal extraction depends on brew house yield
            efficiency = self.brew_house_yield
            if grain_add.grain_type in [GRAIN_TYPE_DME, GRAIN_TYPE_LME]:
                efficiency = 1.0
            total_points += grain_add.gu * efficiency  # noqa
        return total_points

    def get_original_gravity_units(self):
        """
        Get the original gravity units

        :return: The original gravity units
        :rtype: float
        """
        return self.get_total_points() / self.final_volume

    def get_original_gravity(self):
        """
        Get the original specific gravity

        :return: The original specific gravity
        :rtype: float
        """
        return gu_to_sg(self.get_original_gravity_units())

    @property
    def og(self):
        return self.get_original_gravity()

    def get_boil_gravity_units(self, evaporation=BOIL_EVAPORATION):  # noqa
        """
        Get the boil gravity units

        :param float evaporation: Percent water evaporation during boil
        :return: The boil gravity units
        :rtype: float
        """
        return self.get_total_points() / (
            (1.0 - evaporation) * self.start_volume
        )  # noqa

    def get_boil_gravity(self, evaporation=BOIL_EVAPORATION):  # noqa
        """
        Get the boil specific gravity

        :param float evaporation: Percent water evaporation during boil
        :return: The boil specific gravity
        :rtype: float
        """
        return gu_to_sg(self.get_boil_gravity_units(evaporation=evaporation))

    @property
    def bg(self):
        return self.get_boil_gravity()

    def get_final_gravity_units(self):
        """
        Get the final gravity units

        :return: The final gravity units
        :rtype: float
        """
        return self.get_original_gravity_units() * (
            1.0 - self.yeast.percent_attenuation
        )  # noqa

    def get_final_gravity(self):
        """
        Get the final specific gravity

        :return: The final specific gravity
        :rtype: float
        """
        return gu_to_sg(self.get_final_gravity_units())

    @property
    def fg(self):
        return self.get_final_gravity()

    def get_wort_correction(self, current_gu, current_volume, efficiency=PPG_DME):
        """
        Get the amount of sugar to add to correct the wort
        """
        return get_wort_correction(
            current_gu,
            current_volume,
            self.get_original_gravity_units(),
            self.final_volume,
            efficiency=efficiency,
        )

    def get_degrees_plato(self):
        """
        Get the degrees plato

        :return: The degrees plato of the wort
        :rtype: float
        """
        return sg_to_plato(self.get_boil_gravity())

    @property
    def plato(self):
        return self.get_degrees_plato()

    def get_brew_house_yield(self, plato_actual, vol_actual):
        """
        Get the Brew House Yield

        :param float plato_actual: The actual degrees Plato
        :param float vol_actual: The actual volume collected from the kettle
        :return: Brew House Yield
        :rtyle: float
        """
        num = plato_actual * vol_actual * self.brew_house_yield
        den = self.plato * self.final_volume
        return num / den

    def get_extract_weight(self):
        """
        Get the weight of the extract

        :return: The weight of extract
        :rtype: float
        """
        water_density = WATER_WEIGHT_IMPERIAL
        if self.units == SI_UNITS:
            water_density = WATER_WEIGHT_SI
        return (
            water_density
            * self.final_volume
            * self.get_boil_gravity()
            * (self.plato / 100.0)
        )

    def get_percent_malt_bill(self, grain_add):
        """
        Get Percent Malt Bill

        :param GrainAddition grain_add: The Grain Addition
        :return: The percent extract the addition adds to the bill
        :rtype: float

        To ensure different additions are measured equally each is
        converted to dry weight.
        """
        return (
            self.get_grain_add_dry_weight(grain_add) / self.get_total_dry_weight()
        )  # noqa

    def get_grain_add_dry_weight(self, grain_add):
        """
        Get Grain Addition as DME

        :param GrainAddition grain_add: The Grain Addition
        :return: The weight of the grain as DME
        :rtype: float

        When converting Grain to DME its important to remember
        that you can't get 100% efficiency from grains.  Multiplying by
        the brew house yield will decrease the size of the DME
        accordingly.
        """
        return grain_add.get_dme_weight()

    def get_total_dry_weight(self):
        """
        Get total DME weight

        :return: The total weight of the DME
        :rtype: float
        """
        weights = []
        for grain_add in self.grain_additions:
            weights.append(self.get_grain_add_dry_weight(grain_add))
        return sum(weights)

    def get_grain_add_cereal_weight(self, grain_add, ppg=PPG_CEREAL):
        """
        Get Grain Addition as Cereal

        :param GrainAddition grain_add: The Grain Addition
        :return: The weight of the grain as Cereal
        :rtype: float

        When converting DME or LME to grain its important to remember
        that you can't get 100% efficiency from grains.  Dividing by
        the brew house yield will increase the size of the grain
        accordingly.
        """
        return grain_add.get_cereal_weight(ppg=ppg)

    def get_total_grain_weight(self):
        """
        Get total Cereal weight

        :return: The total weight of the Cereal
        :rtype: float
        """
        weights = []
        for grain_add in self.grain_additions:
            weights.append(self.get_grain_add_cereal_weight(grain_add))
        return sum(weights)

    def get_percent_ibus(self, hop_add):
        """
        Get the percentage the hops contributes to total ibus

        :param HopAddition hop_add: The Hop Addition
        :return: The percent the hops contributes to total ibus
        :rtype: float
        """
        bg = self.get_boil_gravity()
        fv = self.final_volume
        return hop_add.get_ibus(bg, fv) / self.get_total_ibu()

    def get_total_ibu(self):
        """
        Convenience method to get total IBU for the recipe

        :return: The total IBU for the Recipe
        :rtype: float
        """
        bg = self.get_boil_gravity()
        fv = self.final_volume
        return sum([hop_add.get_ibus(bg, fv) for hop_add in self.hop_additions])

    @property
    def ibu(self):
        return self.get_total_ibu()

    def get_bu_to_gu(self):
        """
        Get BU to GU Ratio

        :return: Ratio of Bitterness Units to Original Gravity Units
        :rtype: float
        """
        return self.get_total_ibu() / self.get_boil_gravity_units()

    @property
    def abv(self):
        return alcohol_by_volume_standard(self.og, self.fg)

    def get_wort_color_mcu(self, grain_add):
        """
        Get the Wort Color in Malt Color Units

        :param GrainAddition grain_add: The Grain Addition to calculate
        :return: The MCU of the Grain Addition
        :rtype: float
        """  # noqa
        weight = self.get_grain_add_cereal_weight(grain_add)
        return calculate_mcu(
            weight, grain_add.grain.color, self.final_volume, units=self.units
        )

    def get_wort_color(self, grain_add):
        """
        Get the Wort Color in SRM

        :param GrainAddition grain_add: The Grain Addition to calculate
        :return: The SRM of the Grain Addition
        :rtype: float
        """
        mcu = self.get_wort_color_mcu(grain_add)
        return calculate_srm(mcu)

    def get_total_wort_color(self):
        """
        Get the Total Color of the Wort in SRM using Morey Power Equation

        :return: The total color of the wort in SRM
        :rtype: float
        """
        mcu = sum([self.get_wort_color_mcu(ga) for ga in self.grain_additions])
        return calculate_srm(mcu)

    @property
    def color(self):
        return self.get_total_wort_color()

    def get_total_wort_color_map(self):
        """
        Get a map of wort color by method

        :return: A map of wort color in SRM and EBC by method (Morey, Daniels, and Mosher)
        :rtype: dict
        """  # noqa
        mcu = sum([self.get_wort_color_mcu(ga) for ga in self.grain_additions])

        srm_morey = u"N/A"
        srm_daniels = u"N/A"
        srm_mosher = u"N/A"

        ebc_morey = u"N/A"
        ebc_daniels = u"N/A"
        ebc_mosher = u"N/A"

        try:
            srm_morey = calculate_srm_morey(mcu)
            ebc_morey = round(srm_to_ebc(srm_morey), 1)
            srm_morey = round(srm_morey, 1)
        except ColorException:
            pass

        try:
            srm_daniels = calculate_srm_daniels(mcu)
            ebc_daniels = round(srm_to_ebc(srm_daniels), 1)
            srm_daniels = round(srm_daniels, 1)
        except ColorException:
            pass

        try:
            srm_mosher = calculate_srm_mosher(mcu)
            ebc_mosher = round(srm_to_ebc(srm_mosher), 1)
            srm_mosher = round(srm_mosher, 1)
        except ColorException:
            pass

        return {
            u"srm": {
                u"morey": srm_morey,
                u"daniels": srm_daniels,
                u"mosher": srm_mosher,
            },
            u"ebc": {
                u"morey": ebc_morey,
                u"daniels": ebc_daniels,
                u"mosher": ebc_mosher,
            },
        }

    def get_grain_additions_by_type(self, grain_type):
        """
        Return grain additions by given grain_type

        :param str grain_type: The type of grains to return
        :return: list of GrainAddition objects
        """
        validate_grain_type(grain_type)
        return [
            grain_add
            for grain_add in self.grain_additions
            if grain_add.grain_type == grain_type
        ]  # noqa

    def get_hop_additions_by_type(self, hop_type):
        """
        Return hop additions by given hop_type

        :param str hop_type: The type of hops to return
        :return: list of HopAddition objects
        """
        validate_hop_type(hop_type)
        return [
            hop_add for hop_add in self.hop_additions if hop_add.hop_type == hop_type
        ]  # noqa

    def to_dict(self):
        og = self.og
        bg = self.bg
        fg = self.fg
        abv_standard = self.abv
        abv_alternative = alcohol_by_volume_alternative(og, fg)
        recipe_dict = {
            u"name": self.name,
            u"start_volume": round(self.start_volume, 2),
            u"final_volume": round(self.final_volume, 2),
            u"data": {
                u"brew_house_yield": round(self.brew_house_yield, 3),  # noqa
                u"original_gravity": round(og, 3),
                u"boil_gravity": round(bg, 3),
                u"final_gravity": round(fg, 3),
                u"abv_standard": round(abv_standard, 4),
                u"abv_alternative": round(abv_alternative, 4),
                u"abw_standard": round(alcohol_by_weight(abv_standard), 4),
                u"abw_alternative": round(
                    alcohol_by_weight(abv_alternative), 4
                ),  # noqa
                u"total_wort_color_map": self.get_total_wort_color_map(),
                u"total_ibu": round(self.get_total_ibu(), 1),
                u"bu_to_gu": round(self.get_bu_to_gu(), 1),
                u"units": self.units,
            },
            u"grains": [],
            u"hops": [],
            u"yeast": {},
        }

        for grain_add in self.grain_additions:
            grain = grain_add.to_dict()
            wort_color_srm = self.get_wort_color(grain_add)
            wort_color_ebc = srm_to_ebc(wort_color_srm)
            working_yield = round(
                grain_add.grain.get_working_yield(self.brew_house_yield), 3
            )  # noqa
            percent_malt_bill = round(self.get_percent_malt_bill(grain_add), 3)
            grain[u"data"].update(
                {
                    u"working_yield": working_yield,
                    u"percent_malt_bill": percent_malt_bill,
                    u"wort_color_srm": round(wort_color_srm, 1),
                    u"wort_color_ebc": round(wort_color_ebc, 1),
                }
            )
            recipe_dict[u"grains"].append(grain)

        for hop_add in self.hop_additions:
            hop = hop_add.to_dict()

            ibus = hop_add.get_ibus(bg, self.final_volume)

            utilization = hop_add.utilization_cls.get_percent_utilization(
                bg, hop_add.boil_time
            )
            # Utilization is 10% higher for pellet vs whole/plug
            if hop_add.hop_type == HOP_TYPE_PELLET:
                utilization *= HOP_UTILIZATION_SCALE_PELLET

            hop[u"data"].update(
                {u"ibus": round(ibus, 1), u"utilization": round(utilization, 3)}
            )
            recipe_dict[u"hops"].append(hop)

        recipe_dict[u"yeast"] = self.yeast.to_dict()

        return recipe_dict

    def to_json(self):
        return json.dumps(self.to_dict(), sort_keys=True)

    @classmethod
    def validate(cls, recipe):
        required_fields = [
            (u"name", str),
            (u"start_volume", (int, float)),
            (u"final_volume", (int, float)),
            (u"grains", (list, tuple)),
            (u"hops", (list, tuple)),
            (u"yeast", dict),
        ]
        optional_fields = [(u"brew_house_yield", float), (u"units", str)]
        validate_required_fields(recipe, required_fields)
        validate_optional_fields(recipe, optional_fields)

    def format(self, short=False):
        """
        Format the recipe for printing

        :param bool short: Produce short output
        """
        recipe_data = self.to_dict()
        kwargs = {}
        kwargs.update(recipe_data)
        kwargs.update(self.types)
        # Additional format information
        kwargs.update({"evaporation": BOIL_EVAPORATION})

        msg = u""
        msg += textwrap.dedent(
            u"""\
            {name}
            ===================================

            Brew House Yield:   {data[brew_house_yield]:0.1%}
            Start Volume:       {start_volume:0.1f}
            Final Volume:       {final_volume:0.1f}

            Boil Gravity:       {data[boil_gravity]:0.3f} (Evaporation @ {evaporation:0.1%})
            Original Gravity:   {data[original_gravity]:0.3f}
            Final Gravity:      {data[final_gravity]:0.3f}

            ABV / ABW Standard: {data[abv_standard]:0.2%} / {data[abw_standard]:0.2%}
            ABV / ABW Alt:      {data[abv_alternative]:0.2%} / {data[abw_alternative]:0.2%}

            IBU:                {data[total_ibu]:0.1f} ibu
            BU/GU:              {data[bu_to_gu]:0.1f}

            Morey   (SRM/EBC):  {data[total_wort_color_map][srm][morey]} degL / {data[total_wort_color_map][ebc][morey]}
            Daniels (SRM/EBC):  {data[total_wort_color_map][srm][daniels]} degL / {data[total_wort_color_map][ebc][daniels]}
            Mosher  (SRM/EBC):  {data[total_wort_color_map][srm][mosher]} degL / {data[total_wort_color_map][ebc][mosher]}
            """.format(
                **kwargs
            )
        )  # noqa

        # If short output is requested then return it
        if short:
            return msg

        msg += textwrap.dedent(
            u"""\

            Grains
            ===================================

            """
        )

        for grain_data in recipe_data[u"grains"]:
            grain_kwargs = {}
            grain_kwargs.update(grain_data)
            grain_kwargs.update(self.types)

            grain_name = grain_data[u"name"]
            grain_add = self.grain_lookup[grain_name]
            if grain_add.units != self.units:
                grain_add = grain_add.change_units()

            msg += grain_add.format()
            msg += textwrap.dedent(
                u"""\

                    Percent Malt Bill: {data[percent_malt_bill]:0.1%}
                    Working Yield:     {data[working_yield]:0.1%}
                    SRM/EBC:           {data[wort_color_srm]:0.1f} degL / {data[wort_color_ebc]:0.1f}

                    """.format(
                    **grain_kwargs
                )
            )  # noqa

        msg += textwrap.dedent(
            u"""\
            Hops
            ===================================

            """
        )

        for hop_data in recipe_data[u"hops"]:
            hop_kwargs = {}
            hop_kwargs.update(hop_data)
            hop_kwargs.update(self.types)

            hop_key = u"{}_{}".format(hop_data[u"name"], hop_data[u"boil_time"])
            hop = self.hop_lookup[hop_key]
            if hop.units != self.units:
                hop = hop.change_units()

            msg += hop.format()
            msg += textwrap.dedent(
                u"""\

                    IBUs:         {data[ibus]:0.1f}
                    Utilization:  {data[utilization]:0.1%}

                    """.format(
                    **hop_kwargs
                )
            )

        msg += textwrap.dedent(
            u"""\
            Yeast
            ===================================

            """
        )

        msg += self.yeast.format()
        return msg

class RecipeBuilder(object):
    """
    A class for building recipes
    """

    grain_lookup = {}
    hop_lookup = {}

    def __init__(
        self,
        name,
        grain_list=None,
        hop_list=None,
        target_ibu=33.0,
        target_og=1.050,
        brew_house_yield=0.70,
        start_volume=7.0,
        final_volume=5.0,
        units=IMPERIAL_UNITS,
    ):
        """
        :param str name: The name of the recipe
        :param grain_list: A list of Grains
        :type grain_list: list of Grain objects
        :param hop_list: A list of Hops
        :type hop_list: list of Hop objects
        :param float target_ibu: The IBU Target
        :param float target_og: The Original Gravity Target
        :param float brew_house_yield: The brew house yield
        :param float start_volume: The starting volume of the wort
        :param float final_volume: The final volume of the wort
        :param str units: The units
        """  # noqa
        self.name = name
        if grain_list is None:
            grain_list = []
        self.grain_list = grain_list
        if hop_list is None:
            hop_list = []
        self.hop_list = hop_list

        self.target_ibu = target_ibu
        self.target_og = target_og
        self.brew_house_yield = validate_percentage(brew_house_yield)  # noqa
        self.start_volume = start_volume
        self.final_volume = final_volume

        # Manage units
        self.set_units(units)

        # For each grain and hop:
        # Add to lookup
        for grain in self.grain_list:
            self.grain_lookup[grain.name] = grain
        for hop in self.hop_list:
            self.hop_lookup[hop.name] = hop

    def __str__(self):
        if sys.version_info[0] >= 3:
            return self.__unicode__()
        else:
            return self.__unicode__().encode(u"utf8")

    def __unicode__(self):
        return self.name

    def __repr__(self):
        out = u"{0}('{1}'".format(type(self).__name__, self.name)
        if self.grain_list:
            out = u"{0}, grain_list=[{1}]".format(
                out, u", ".join([repr(h) for h in self.grain_list])
            )  # noqa
        if self.hop_list:
            out = u"{0}, hop_list=[{1}]".format(
                out, u", ".join([repr(h) for h in self.hop_list])
            )  # noqa
        if self.target_og:
            out = u"{0}, target_og={1}".format(out, self.target_og)  # noqa
        if self.brew_house_yield:
            out = u"{0}, brew_house_yield={1}".format(
                out, self.brew_house_yield
            )  # noqa
        if self.start_volume:
            out = u"{0}, start_volume={1}".format(out, self.start_volume)
        if self.final_volume:
            out = u"{0}, final_volume={1}".format(out, self.final_volume)
        if self.units:
            out = u"{0}, units={1}".format(out, self.units)
        out = u"{0})".format(out)
        return out

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        if (
            (self.name == other.name)
            and (self.grain_list == other.grain_list)
            and (self.hop_list == other.hop_list)
            and (self.target_og == other.target_og)
            and (self.brew_house_yield == other.brew_house_yield)
            and (self.start_volume == other.start_volume)
            and (self.final_volume == other.final_volume)
            and (self.units == other.units)
        ):
            return True
        return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def set_units(self, units):
        """
        Set the units and unit types

        :param str units: The units
        """
        self.units = validate_units(units)
        if self.units == IMPERIAL_UNITS:
            self.types = IMPERIAL_TYPES
        elif self.units == SI_UNITS:
            self.types = SI_TYPES

    def change_units(self):
        """
        Change units of the class from one type to the other

        :return: RecipeBuilder in new unit type
        :rtype: RecipeBuilder
        """
        if self.units == IMPERIAL_UNITS:
            start_volume = self.start_volume * LITER_PER_GAL
            final_volume = self.final_volume * LITER_PER_GAL
            units = SI_UNITS
        elif self.units == SI_UNITS:
            start_volume = self.start_volume * GAL_PER_LITER
            final_volume = self.final_volume * GAL_PER_LITER
            units = IMPERIAL_UNITS
        return RecipeBuilder(
            self.name,
            grain_list=self.grain_list,
            hop_list=self.hop_list,
            target_og=self.target_og,
            brew_house_yield=self.brew_house_yield,
            start_volume=start_volume,
            final_volume=final_volume,
            units=units,
        )

    def get_grain_additions(self, percent_list):
        """
        Calculate GrainAdditions from list of percentages

        :param list percent_list: A list of percentages mapped to each Grain
        :return: A list of Grain Additions
        :rtype: list(GrainAddition)
        :raises RecipeException: If sum of percentages does not equal 1.0
        :raises RecipeException: If length of percent_list does not match length of self.grain_list
        """  # noqa
        for percent in percent_list:
            validate_percentage(percent)

        if sum(percent_list) != 1.0:
            raise RecipeException(u"Percentages must sum to 1.0")

        if len(percent_list) != len(self.grain_list):
            raise RecipeException(
                u"The length of percent_list must equal length of self.grain_list"
            )  # noqa

        # Pick the attribute based on units
        if self.units == IMPERIAL_UNITS:
            attr = u"ppg"
        if self.units == SI_UNITS:
            attr = u"hwe"

        gu = sg_to_gu(self.target_og)
        total_points = gu * self.final_volume

        grain_additions = []
        for index, grain in enumerate(self.grain_list):
            efficiency = self.brew_house_yield
            weight = (percent_list[index] * total_points) / (
                getattr(grain, attr) * efficiency
            )  # noqa
            grain_add = GrainAddition(grain, weight=weight, units=self.units)
            grain_additions.append(grain_add)
        return grain_additions

    def get_hop_additions(
        self,
        percent_list,
        boil_time_list,
        hop_type=HOP_TYPE_PELLET,
        utilization_cls=HopsUtilizationGlennTinseth,
    ):
        """
        Calculate HopAdditions from list of boil times

        :param list boil_time_list: A list of boil times mapped to each Hop
        :param HopsUtilization utilization_cls: The utilization class used for calculation
        :return: A list of Hop Additions
        :rtype: list(HopAddition)
        :raises RecipeException: If sum of percentages does not equal 1.0
        :raises RecipeException: If length of percent_list does not match length of self.hop_list
        :raises RecipeException: If length of boil_time_list does not match length of self.hop_list
        """  # noqa
        for percent in percent_list:
            validate_percentage(percent)

        if sum(percent_list) != 1.0:
            raise RecipeException(u"Percentages must sum to 1.0")

        if len(percent_list) != len(self.hop_list):
            raise RecipeException(
                u"The length of percent_list must equal length of self.hop_list"
            )  # noqa

        if len(boil_time_list) != len(self.hop_list):
            raise RecipeException(
                u"The length of boil_time_list must equal length of self.hop_list"  # noqa
            )

        hops_constant = HOPS_CONSTANT_IMPERIAL
        if self.units == SI_UNITS:
            hops_constant = HOPS_CONSTANT_SI

        hop_additions = []
        for index, hop in enumerate(self.hop_list):
            percent = percent_list[index]
            boil_time = boil_time_list[index]

            # Calculate utilization from boil gravity
            bg = gu_to_sg(
                sg_to_gu(self.target_og) * self.final_volume / self.start_volume
            )  # noqa
            utilization = utilization_cls.get_percent_utilization(bg, boil_time)  # noqa
            if hop_type == HOP_TYPE_PELLET:
                utilization *= HOP_UTILIZATION_SCALE_PELLET

            num = self.target_ibu * percent * self.final_volume
            den = utilization * hop.percent_alpha_acids * hops_constant
            weight = num / den
            hop_add = HopAddition(
                hop,
                weight=weight,
                boil_time=boil_time,
                hop_type=hop_type,
                utilization_cls=utilization_cls,
                units=self.units,
            )
            hop_additions.append(hop_add)
        return hop_additions

    def get_yeast_attenuation(self, abv):
        """
        Estimate yeast attenuation given a target abv

        :param float abv: Alcohol by Volume
        :return: Yeast Attenuation Percentage
        :rtype: float

        This uses the ABV Standard Equation
        """
        validate_percentage(abv)
        fg = final_gravity_from_abv_standard(self.target_og, abv)
        attenuation = 1.0 - sg_to_gu(fg) / sg_to_gu(self.target_og)
        return attenuation


import pandas as pd
import seaborn as sns
import plotly.express as px
import matplotlib.pyplot as plt

# # The DataFrames

# +
import os

print(os.listdir("../data"))
PATH = "../data/{}.csv"
# -

games = pd.read_csv(PATH.format("games"))
details = pd.read_csv(PATH.format("games_details"))
players = pd.read_csv(PATH.format("players"))
ranking = pd.read_csv(PATH.format("ranking"))
teams = pd.read_csv(PATH.format("teams"))

dfs = [[games, "games"], [details, "details"], [players, "players"], [ranking, "ranking"], [teams, "teams"]]

def show(df, name):
    print(f"> DataFrame {name} : ")
    display(df.head(5))

[show(df, name) for df, name in dfs];

# ### 1 - Games
#
#
# We start by study the `games` dataset. Each row here corresponds to a game.

# +
## quick look at the different columns
display(games.columns)

## we set them in lower case because caps lock is lame
games.columns = [name.lower() for name in games.columns]
# -

## let's use datetime
games["date"] = pd.to_datetime(games.game_date_est)

f = plt.figure(figsize=(15, 6))
games["SEASON"] = pd.to_datetime(games.season.astype(str))
g = sns.histplot(data=games, x="SEASON", palette="viridis", binwidth=365)
g.set_title("distribution of the number of game per seasons")
g.set_ylabel("number of games")
g.set_xlabel("season");

g = sns.countplot(data=games, x="home_team_wins")
g.set_title("distribution of games won by hosting team")
g.set_xlabel("host team won")
g.set_ylabel("number of games");

## for home team
f, a = plt.subplots(figsize=(15, 6))
sns.histplot(data=games, x="pts_away", color="blue", label="visitor team", kde=True)
sns.histplot(data=games, x="pts_home", color="red", label="home team", kde=True)
plt.legend()
a.set_title("distribution of the number of points scored in a game")
a.set_ylabel("number of game")
a.set_xlabel("number of points");

# We can hypothesize right away that there is a significant difference in the number of points scored when **hosting** or visiting a game. Let's assess this with a **t test**:

# +
from scipy import stats

stats.ttest_ind(games.pts_away.dropna(), games.pts_home.dropna())
# -

# The p-value is $< 0.01$ so we can reject the null-hypothesis saying that these means are the same with a confidence of $99\%$. This statistic can potentially be explained using other features. We might study this later full project.
#
#
# Here we assess the precision in the 3 points shoots between visitors and host teams :

# +
## 3 pts percentage

f = plt.figure(figsize=(15, 6))
sns.histplot(data=games, x="fg3_pct_away", color="blue", label="visitor team", kde=True)
sns.histplot(data=games, x="fg3_pct_home", color="red", label="home team", kde=True)
plt.legend()
# -

# Here, the same for 2 points shots

# +
## 2 pts percentage

f = plt.figure(figsize=(15, 6))
sns.histplot(data=games, x="fg_pct_away", color="blue", label="visitor team", kde=True)
sns.histplot(data=games, x="fg_pct_home", color="red", label="home team", kde=True)
plt.legend()

# -

# The precision is more different for normal shoots than for 3 points shoots.
#
# The dataset's feature `HOME_TEAM_WINS` is not very praticle if we need to compute it multiple times so we precompute the `winner_id`.

# +
def finds_winner(entry):
    if(entry["home_team_wins"]):
        return entry["home_team_id"]
    else :
        return entry["visitor_team_id"]

games["winner_id"] = games.apply(finds_winner, axis=1)
# -

## little check that it worked as intended
games[["home_team_id", "visitor_team_id", "home_team_wins", "winner_id"]].head()

## unecessary features
games = games.drop(["team_id_home", "game_status_text", "game_date_est"], axis=1)

# While we are preprocessing we are going to invistigate the `NaN` entries :

## entries with at least one NaN value
games[games.isna().sum(axis=1) != 0]

# These are the match that may have been won on "default wins". We decide to get rid of them.

games = games.dropna(how="any")

# We will study the relation between different features :

uninteresting_cols = ["home_team_id", "visitor_team_id", "season", "winner_id", "game_id", "date"]
sns.pairplot(data=games.drop(uninteresting_cols, axis=1), corner=True)

# We store the cleaned dataset in the `data/preprocessed` directory.

games.to_csv("../data/preprocessed/games.csv")

# ### 2 - Details
#
# Now let's study the `details` dataset. This one contains one row per player involved in one game. This gives us information about the actions of each player on the field during a given match.

details.columns = [name.lower() for name in details.columns]
details

details.describe()

# +
## obvious stat : the players have a lower precision on 3 points shoots than in the others

f, a = plt.subplots(figsize=(10, 4))
sns.histplot(details.groupby("player_id").mean(), x="fg3_pct", color="red", label="3 pts shoots", kde=True)
sns.histplot(details.groupby("player_id").mean(), x="fg_pct", label="shoots", kde=True)
a.set_title("precision percentage of shoots per player")
a.set_ylabel("number of players")
a.set_xlabel("precisions ration")
plt.legend();
# -

g = sns.histplot(details.groupby("player_name").pts.mean().reset_index()\
            .sort_values("pts", ascending=False).dropna())
g.set_title("Distribution of the mean number of points per game per player")
g.set_ylabel("number of players")
g.set_xlabel("mean number of points per game");

# +
f = plt.figure(figsize=(15, 7))
team_points = details.groupby('team_id').pts.mean().reset_index()\
            .sort_values("pts").dropna()\
            .merge(teams[["team_id", "name"]], on="team_id")

g = sns.barplot(data=team_points, x='name', y="pts")
g.set_xticklabels(g.get_xticklabels(), rotation=45)
g.set_title("mean number of points for every player in a team")
g.set_xlabel("name of the team")
g.set_ylabel("average number of points for every player in a team");

# +
f = plt.figure(figsize=(15, 5))
shoot = details.groupby("player_name").pts.mean().reset_index()\
            .sort_values("pts", ascending=False).head(20)

g = sns.barplot(data=shoot, x="player_name", y="pts")
g.set_xticklabels(g.get_xticklabels(), rotation=45)
g.set_title("mean points per match for the 20 bests players")
g.set_xlabel("mean number of points per match")
g.set_ylabel("player");
# -

details["min"] = details["min"].astype(str).apply(lambda x : float(x.split(":")[0]))

fig = px.scatter(details.groupby("player_name")[["pts", "min"]].mean().reset_index(),\
                 x="pts", y="min", hover_data=['player_name'],\
                 labels={
                        "pts" : "mean points per match",
                        "min" : "average number of minutes played"
                }, \
                title="Relation between the average of time played and the number of points scored")
fig.update_layout(width=800, height=800)
fig.show()

# +
f = plt.figure(figsize=(15, 5))
shoot = details.groupby("player_name").fg3m.mean().reset_index()\
            .sort_values("fg3m", ascending=False).head(20)

g = sns.barplot(data=shoot, x="player_name", y="fg3m")
g.set_xticklabels(g.get_xticklabels(), rotation=45)
g.set_title("mean points per match for the 20 bests players")
g.set_xlabel("mean number of points 3 points marked per match")
g.set_ylabel("player");
# -

fig = px.scatter(details.groupby("player_name")[["fg3m", "fgm"]].sum().reset_index(),\
                 x="fg3m", y="fgm", hover_data=['player_name'],\
                 labels={
                        "fg3m" : "3 points shoot marked",
                        "fgm" : "2 points shoot marked"
                }, \
                title="Relation between the number of 3 points and 2 points shots marked")
fig.update_layout(width=800, height=800)
fig.show()

# This graph shows us that Lebron James has absolutely 0 ounce of chill.

g = sns.histplot(data=details, x="min", binwidth=3)
g.set_title("distribution of the number of ")
g.set_xlabel("minutes played")
g.set_ylabel("number of players");

# During investigation we discovered that a player played 96 minutes in a game. We did our investigation on the precise game
# and realised this never happened. So we get rid of the row.

details = details.drop(details[details["min"] == 96.0].index[0])

details.to_csv("../data/preprocessed/details.csv")

# ### 3 - Rankings

# +
ranking.columns = [name.lower() for name in ranking.columns]
ranking["standingsdate"] = pd.to_datetime(ranking.standingsdate)

## useless cols
ranking = ranking.drop(["league_id", "returntoplay"], axis=1)
ranking
# -

g = sns.countplot(data=ranking[ranking.standingsdate == "2020-12-21"], x="conference")
g.set_title("number of teams of each conference of the 12/21/2020");

f, a = plt.subplots(figsize=(16, 4))
plt.plot(ranking.groupby("standingsdate").count().team_id)

lebron_at_cleveland_years = list(range(2003, 2011)) + list(range(2014, 2019))
wins_cleveland = ranking[ranking.team == "Cleveland"][["standingsdate", "w"]].drop_duplicates()
wins_cleveland["Lebron_was_here"] = wins_cleveland.standingsdate.apply(lambda date : date.year in lebron_at_cleveland_years)
wins_cleveland = wins_cleveland.drop_duplicates().sort_values("standingsdate", ascending=True)
px.line(wins_cleveland, x="standingsdate", y="w", color="Lebron_was_here", labels={"w" : "wins for Cleveland", "standingsdate" : "date in the season", "Lebron_was_here" : "was Lebron playing at Cleveland ?"}, title="Impact of Lebron on Cleveland wins during a season")

# We have one ranking per day. We need to order the team for each of them to extrac the real ranking for each of them.

# +
def find_ranking(daily_scores):
    sub_sorted = daily_scores.sort_values("w", ascending=False).reset_index()
    sub_sorted["rank"] = sub_sorted.index + 1
    sub_sorted = sub_sorted.set_index("index")
    return sub_sorted

from senpy import notify_me

ranking = ranking.groupby("standingsdate").apply(lambda subdf : find_ranking(subdf))
notify_me("ranking computation done")
# -

ranking.to_csv("../data/preprocessed/rankings.csv")

# ### 4 - Teams
#
#
# This can helps add some data about the team later but isn't so useful by itself.

teams

## do smthg
display(teams[teams.TEAM_ID == 1610612744])
display(teams[teams.TEAM_ID == 1610612757])
teams.columns = [col.lower() for col in teams.columns]
teams["name"] = teams["city"] + " " + teams["nickname"]

teams.name.drop_duplicates()

sns.histplot(data=teams, x="YEARFOUNDED", binwidth=2)

# ### 5 - Players
#
#
# This dataset doesn't contain any new info so we won't use it.

players

# # Lebron Jenkins, out


#std imports
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

#local imports
import currimage
from pbtspot import globalconstants

class Rectangle:
    """
    defines a rectangle in Haar feature
    @param: x_val,y_val,width,height:integer>0
    @param: weight: positive or negative integer
    """
    def __init__(self,x_val,y_val,width,height,weight):
        self.check_values(x_val,y_val,width,height,weight);

        #also check whether all four points within image limits, make a function
        #if you want weights to be +-1, check that too.
        self.x_val=x_val;
        self.y_val=y_val;
        self.width=width;
        self.height=height;
        self.weight=weight;

    def __str__(self):
        return("Rectange.X:%d,Y:%d,W:%d,H:%d"%(self.x_val,self.y_val,self.width,self.height))

    def __repr__(self):
        return("Rectange.X:%d,Y:%d,W:%d,H:%d","Wt:%d"%(self.x_val,self.y_val,self.width,self.height,self.weight))

    def check_values(self,x_val,y_val,width,height,weight):

        #Todo: this class must be able to access image size variables
        if(width<0):
            raise ValueError("Width value must be within limit:%d",width);

        if(height<0):
            raise ValueError("Height value must be within limit:%d",height);

        if(x_val<0):
            raise ValueError("X value must be within limit:%d",x_val);

        if(y_val<0):
            raise ValueError("Y value must be within limit:%d",y_val);
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

class BaseFeature:
    """
    defines a BaseFeature. Different Feature types derive from it.
    defines virtual functions.
    """
    #We define features simply as a list of rectangles
    def __init__(self):
        glob_const=globalconstants.GlobalConstants();
        self.global_window_width=glob_const.global_window_width;
        self.global_window_height=glob_const.global_window_height;

        self.rectlist=None; #defined by subclass

        #Other variables, to be defined during optimization
        #should we initialize them here?
        self.threshold=None;
        self.toggle=None;
        self.error=None
        self.margin=None;
        self.response_vector=None;

    #Abstract classes, to be defined later
    def generate_feature(self,x_val,y_val,width,height):
        raise NotImplementedError('Must implement a "generate_feature" method.')

    def __repr__(self):
        raise NotImplementedError('Subclass must implement a "__repr__" method.')

    #Implemented Classes
    #this class now takes in int_image, not image
    #taking in image was the main cause of slowing down the program
    # for each feature it kept on calculating the same int_image
    def convolve_int_image(self,int_image):

        #int_image=image;
        #currimg=currimage.CurrImage(image);
        #currimg.calc_integral_image();
        #int_image=currimg.int_image;

        if self.rectlist is None:
            raise AttributeError("Rectlist not defined by sublass yet.")

        sum_px=0;
        #can optimize by caching some values
        for rect in self.rectlist:
            sum_px+=rect.weight*self.sum_int_image(int_image,rect);
        return sum_px;

    def sum_int_image(self,int_image,rect):
        top_left=self.int_image_val(int_image,rect.y_val-1,rect.x_val-1);
        bottom_left=self.int_image_val(int_image,rect.y_val+rect.height-1,rect.x_val-1);
        top_right=self.int_image_val(int_image,rect.y_val-1,rect.x_val+rect.width-1);
        bottom_right=self.int_image_val(int_image,rect.y_val+rect.height-1,rect.x_val+rect.width-1);

        #print rect
        #print (bottom_right+top_left-bottom_left-top_right)
        #From Figure 3 of Viola-Jones Paper
        return (bottom_right+top_left-bottom_left-top_right);

    def sum_rotated_int_image(self,rotated_int_image,rect):
        first_term=self.rotated_int_image_val(rotated_int_image,rect.y_val+rect.width,rect.x_val+rect.width);
        second_term=self.rotated_int_image_val(rotated_int_image,rect.x_val-rect.height,rect.y_val+rect.height);
        third_term=self.rotated_int_image_val(rotated_int_image,rect.x_val,rect.y_val);
        fourth_term=self.rotated_int_image_val(rotated_int_image,rect.x_val+rect.width-rect.height,\
                                               rect.y_val+rect.width+rect.height);

    def int_image_val(self,int_image, y_val,x_val):
        """
        this function returns the integral image coordinates
        and handles the special case when values are sought at the
        margin of the matrix
        """
        if(x_val==-1 or y_val==-1):
            return 0;
        else:
            return int_image[y_val,x_val];

    def show_feature(self):
        """
        plots a feature in an image, for testing purposes
        """
        if self.rectlist==None:
            raise ValueError("Rectlist not defined yet");

        img_toshow=50*np.ones((self.global_window_height,self.global_window_width),dtype=int);#Grey
        for rect in self.rectlist:
            if(rect.weight>0):
                img_toshow[rect.y_val:rect.y_val+rect.height,\
                           rect.x_val:rect.x_val+rect.width]=100;#white
            else:
                img_toshow[rect.y_val:rect.y_val+rect.height,\
                           rect.x_val:rect.x_val+rect.width]=0;#black
        fig=plt.figure();
        ax=fig.add_subplot(111);
        ax.imshow(img_toshow,cmap=cm.Greys_r,interpolation='None');
        plt.colorbar
        plt.grid(True);
        plt.show()
        return

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)


from flask import Flask, json, request, make_response, render_template, jsonify
from utopia.models.flights import AIRPORT_SCHEMA_MANY, ROUTE_SCHEMA_MANY
from utopia import app
from utopia.service.airport_service import AirportService
from utopia.models.users import find_user, refresh_token
import logging
from flask_jwt_extended import get_current_user, JWTManager, jwt_required

AIRLINE_SERVICE = AirportService()
jwt = JWTManager(app)
ADMIN = 1

################### GET ###################

@app.route('/')
def index():
    routes = AIRLINE_SERVICE.read_routes().json['routes']
    response = make_response(render_template('airports.html', routes=routes))
    return response

@app.route('/airlines/read/airports', methods=['GET'])
def readAirports():

    return AIRLINE_SERVICE.read_airports()

@app.route('/airlines/read/routes', methods=['GET'])
def readRoutes():

    return AIRLINE_SERVICE.read_routes()

@app.route('/airlines/find/airport/id=<iata_id>', methods=['GET'])
def findAirport(iata_id):

    return AIRLINE_SERVICE.find_airport(iata_id)

@app.route('/airlines/find/route/id=<route_id>', methods=['GET'])
def findRoute(route_id):

    return AIRLINE_SERVICE.find_route(route_id)

@app.route('/airlines/read/routes_by_airport/<direction>/id=<iata_id>', methods=['GET'])
def readRoutesByAirport(direction, iata_id):

    return AIRLINE_SERVICE.read_routes_by_airport(direction, iata_id)

################### POST ###################

@app.route('/airlines/add/airport', methods=['POST'])
@jwt_required()
def addAirport():
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    return AIRLINE_SERVICE.create_airport(request.json)

@app.route('/airlines/add/airport/test', methods=['POST'])
@jwt_required()
def addAirportTest():
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    AIRLINE_SERVICE.add_airports_test(request.json)
    return ''

@app.route('/airlines/add/route', methods=['POST'])
@jwt_required()
def addRoute():
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    return AIRLINE_SERVICE.add_route(request.json)

################### PUT ###################

@app.route('/airlines/update/airport', methods=['PUT'])
@jwt_required()
def updateAirport():
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    return AIRLINE_SERVICE.update_airport(request.json)

@app.route('/airlines/update/route', methods=['PUT'])
@jwt_required()
def updateRoute():
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    return AIRLINE_SERVICE.update_route(request.json)

################### DELETE ###################

@app.route('/airlines/delete/airport/id=<iata_id>', methods=['DELETE'])
@jwt_required()
def deleteAirport(iata_id):
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    AIRLINE_SERVICE.delete_airport(iata_id)
    return ''

@app.route('/airlines/delete/route/id=<id>', methods=['DELETE'])
@jwt_required()
def deleteRoute(id):
    current_user = get_current_user()
    if current_user['role_id'] != ADMIN:
        return make_response('need admin priveleges to access this resource', 403)

    return AIRLINE_SERVICE.delete_route(id)

@jwt.user_lookup_loader
def user_lookup_callback(_jwt_header, jwt_data):
    identity = jwt_data["sub"]
    return find_user(identity)

@app.after_request
def refresh_expiring_jwts(response):
    return refresh_token(response)


'''
Module to communicate with Tektronix TDS3014b oscilloscope using ethernet.

See example_tektronix.py for example usage.

Modified from module developed by Joe Gregorio, (C) 2006 MIT:
ftp://sprite.ssl.berkeley.edu/pub/sharris/MAVEN_LPW_Preamp/109_TDS3014B_control/tds3014b.py

@author: Jami L Johnson
Created May 27, 2014
'''
from __future__ import print_function, absolute_import

from . import httplib2
import urllib
import os
import struct
import datetime
from obspy import read, Trace, UTCDateTime
from obspy.core.trace import Stats

try:
    import numpy as np

except ImportError:
    pass

class Error(Exception): pass
class connection_error(Error): pass

class TDS3014b:
    def __init__(self, ip_addr):
        self.ip_addr = ip_addr
        self.base_url = "http://%s" % (ip_addr)
        self.h = httplib2.Http()

    def gpibCMD(self, cmd):
        """
        Send a command to the scope and gets the response, if any.
        """
        if(cmd.startswith('*')):
            prefix = ''
        else:
            prefix = ':'

        cmd_url = "?COMMAND=%s%s" % (prefix, urllib.quote_plus(cmd))
        url = '/'.join([self.base_url, cmd_url])
        f = urllib.urlopen(url)
        content = f.read()
        f.close()
        return content

    def getScreen(self):
        """
        :returns: a screen shot in PNG format.
        """
        url = '/'.join([self.base_url, 'Image.png'])
        try:
            f = urllib.urlopen(url)
        except IOError as e:
            raise connection_error(e)
        content = f.read()
        f.close()

        return content

    def forceTrigger(self):
        """
        Forces scope to trigger
        """
        self.gpibCMD('FPANEL:PRESS FORCETRIG')

    def parseISF(self, isf_data, header_only=None, convert=None):
        """
        Determine starting point of data.
        Header has something like 'CURVE #520000', where ascii '5' is the
        length of the length field '20000'.

        ::

            'CURVE #520000xxxx...'
              ^      ^^    ^
              |      ||    +---- data_loc:    location of first valid byte of data
              |      |+--------- len_loc:     location of first byte of data length field
              |      +---------- len_len_loc: location of length of length
              +----------------- tag_loc:     location of start tag
        """
        start_tag = 'CURVE #'
        tag_loc = int(isf_data.find(start_tag))
        len_len_loc = tag_loc + len(start_tag)
        len_loc = len_len_loc + 1
        len_len = int(isf_data[len_len_loc]) # e.g. 5
        data_loc = len_loc + len_len
        data_len = int(isf_data[len_loc:len_loc+len_len]) # e.g. 20000

        # Extract and parse header
        header = isf_data[:tag_loc]
        # Reformat the header into a dictionary
        header_dict = {}
        items = header.replace(':WFMPRE:','').replace(':','').split(';') # list of "key value" pairs
        for item in items:
            if(item):
                key, value = item.split(' ', 1) # maxsplit 1 to ignore subsequent spaces in value
                value = value.replace('"', '')
                header_dict[key] = value

        if(header_only):
            return header_dict

        # Extract data and convert from string to integer value
        data = isf_data[data_loc:]

        stats = Stats()
        stats.npts = int(header_dict['NR_PT'])
        stats.calib = float(header_dict['YMULT'])
        byte_order = header_dict['BYT_OR'] # 'MSB' or 'LSB'

        if(byte_order == 'MSB'):
            byte_order = '>'
        else:
            byte_order = '<'
        points = []
        for i in range(0, stats.npts*2, 2):
            value = data[i:i+2] # as string
            converted = struct.unpack('%sh' % byte_order, value)[0]
            points.append(converted)

        # Optionally convert points to engineering units
        if(convert):
            try:
                points = np.array(points) * stats.calib  # requires numpy
            except NameError:
                # If numpy not available, use list instead.
                p = []
                for point in points:
                    p.append(point * stats.calib)
                points = p
        stats.time_offset = float(header_dict['XZERO'])
        stats.calib_unit = header_dict['YUNIT']
        stats.delta = float(header_dict['XINCR'])
        stats.amp_offset = float(header_dict['YOFF'])
        stats.comments = header_dict['WFID']
        stats.channel = header_dict['WFID'][0:3]

        return stats, points

    def getWaveform(self, channel=1, format=None):
        """
        Returns the data points that make up the currently displayed
        waveform. There will be 10,000 points spanning the X-axis time,
        and 7 bits spanning the Y-axis voltage. The time and voltage
        range depend on how the scope is currently configured, and this
        configuration is returned as metadata depending on the value of
        format.

        Arguments:

        :param channel: Integer, 1-4
        :param format: String (see below).
        :returns: Data representing the currently displayed waveform
                  from the oscilloscope in the format described above.

        format argument

        Optional argument specifying what format the returned data
        should be in. Default value is 'eng'. Possible values and
        their effect on the return value are:

        **raw**
            return value will contain only the raw ISF-formatted
            data from the scope. See notes on ISF below.

        **csv**
            return value is a (header, data) tuple, where header is a
            python dict of terms describing the data. Data is a string
            of the 10k data points from the scope in ASCII/CSV format.

            Note that this takes about 20 seconds as opposed to <1
            second for ISF.

        **counts**
            return value is a (header, data) tuple where header is a
            python dictionary of terms describing data, which is a numpy
            array of data points in ADC counts. (If numpy is installed,
            otherwise data is a list.) The header information can be
            used to convert counts to volts.

        **eng**
            return value is a (header, data) tuple as in the 'array'
            case above, only the header will be examined in order to
            return the data points in volts (as floating point numbers).

        Exceptions:

        E.g.:

        getWaveform(channel=1)

        Notes:

        For 'eng' and 'array' formats, the TDS3014B generates 9-bit
        samples, which are packed into a two-byte signed integer. For
        example, 0xF300 = -3328 -3328 * YMULT of 1.5625e-4 = -0.52 volts

        See also:

        http://www.photonics.umd.edu/software/isfread.m
            Example ISF decoder using matlab.

        http://www2.tek.com/cmswpt/swdetails.lotr?ct=SW&cs=sut&ci=5355&lc=EN
            ISF to CSV Conversion utility from Tektronix (DOS)

        http://www.tek.com/forum/viewtopic.php?f=5&t=50
            Notes on ISF header format.
        """

        if(format == None):
            format = 'eng' # Default format

        # get data in ISF format first. If the user requested CSV, return the
        # header from the ISF request along with the CSV data from a second
        # request. This keeps the return value format similar for both cases and
        # ensures that the user doesn't just get CSV data with no metadata.
        content = self.fetchData(channel, format='internal')

        if(format == 'raw'):
            return content
        elif(format == 'csv'):
            header = self.parseISF(content, header_only=True)
            csv_data = self.fetchData(channel, format='spreadsheet')
            return header, csv_data
        elif(format == 'counts'):
            header, points = self.parseISF(content, convert=False)
            return header, points
        elif(format == 'eng'):
            header, points = self.parseISF(content, convert=True)

            return header, points

    def fetchData(self, channel, format):
        """
        Get readout of current data.
        Communicate with the scope and request a readout of the current
        data. Format can be either 'internal' (for ISF data) or
        'spreadsheet' (for CSV).
        """
        data = urllib.urlencode([('command', 'select:ch%d on' % (channel)),
                                 ('command', 'save:waveform:fileformat %s' % (format)),
                                 ('wfmsend', 'Get')])
        url = '/'.join([self.base_url, "getwfm.isf"])
        response, content = self.h.request(url, "POST", data)
        content = content[:-3] # remove '\n\r\n' from content
        return content

    def getIDN(self):
        return self.gpibCMD('*IDN?')

    def getMeasurement(self, meas_num, numeric=False):
        """
        Queries the scope for the measurement number meas_num.  The
        scope can have up to four measurements which are user
        configured.

        e.g.:
        >>> result = t.get_measurement(1) # Get measurement for MEAS1
        >>> print result
        '1.3312E-8'

        The units for the measurement can be determined by using
        get_measurement_params() (below).

        """
        if(1 <= meas_num <= 4):
            retval = self.gpibCMD('measurement:MEAS%d:data?' % meas_num).strip().split(',')[0]
            if(numeric):
                retval = float(retval)
            return retval
        else:
            msg = "ERROR: meas_num must be between 1 and 4 inclusive."
            print(msg)
            raise Error(msg)

    def getMeasurementParams(self, meas_num):
        """
        Queries the scope for the parameters for measurement number
        meas_num.  Returns a string.

        e.g.:
        >>> result = t.getMeasurementParams(3) # get parameters for MEAS3
        >>> print result
        'FREQ;"Hz";CH1;CH2;FORW;RIS;RIS;1'

        """
        if(1 <= meas_num <= 4):
            return self.gpibCMD('measurement:MEAS%d?' % meas_num).strip()
        else:
            msg = "ERROR: meas_num must be between 1 and 4 inclusive."
            print(msg)
            raise Error(msg)

    def recallSavedConfig(self, config_num):
        """
        Recalls scope saved configuration number config_num.
        """
        self.gpibCMD('recall:setup %d' % (config_num))

    def setVscale(self, chan_num, vdiv):
        """
        Sets the vertical scale for the specified channel to the
        specified scale.  Scale is in volts per division.

        e.g.:
        >>> t.setVscale(2, .5) # set ch. 2 to 500 mV/div
        """

        self.gpibCMD('ch%d:scale %s' % (chan_num, vdiv))

    def setHscale(self, secdiv=None, freq=None):
        """
        Sets the scope horizontal timebase.  The scale can either be
        specified by supplying the secdiv parameter, in which case the
        scale is set to the given seconds per division.  Or it can be
        set by supplying a frequency.  The seconds per division value
        will be calculated such that 10 cycles of the given frequency
        will be shown on screen.

        e.g.:
        >>> t.setHscale(secdiv=1) # 1 second per division
        >>> t.setHscale(freq=100) # 10 ms/div
        """
        if(secdiv != None):
            self.gpibCMD('horizontal:main:scale %s' % (str(secdiv)))
        elif(freq != None):
            secdiv = 1.0/freq
            self.gpibCMD('horizontal:main:scale %s' % (str(secdiv)))
        else:
            msg = "ERROR: One of secdiv or freq must be specified."
            print(msg)
            raise Error(msg)

def test():
    scope = TDS3014B('localhost:31338')
    return scope


# coding: utf8
"""
Geocoding script that transforms a series of addresses in GPS WSG84

author:bapronx
"""

import requests
import urllib.parse as parse
import json

#load user key
keyfile = "maquest.key"
with open(keyfile, 'r') as f :
    key = f.read().strip()

print("Loaded key : {}".format(key))
url = "http://www.mapquestapi.com/geocoding/v1/batch"
#data = {'location':["France, JASSERON , 01250,  SOUS LE BOIS GIROUD","France, Paris, 75009, 44 rue de dunkerque"], 'maxResults':1, 'key':key}

def submit_data(locations):
    """ Use mapquest api to request GPS coordinates from the address
    """
    dico = {"maxResults":1, "location":locations, "key":key}
    req = requests.get(url, params=dico)
    return json.loads(req.text)['results']

def  line_to_location_string(serie):
  number = serie["No voie"]
  if number.is_integer():
    number = str(int(number))
  else:
    number = ""
  return ", ".join(["France", \
      serie["Commune"], \
      ("%05i" % (serie["Code postal"],)), \
        " ".join([ number, serie["Type de voie"], serie["Voie"]])])

def get_all_location_string(df):
  location = df.apply(line_to_location_string, axis=1)
  return location.unique()

# from exploration import df
# toto = get_all_location_string(df)
# toto


import requests
import time
import logging
import json
import datetime
import config
import csv
import pytz
import os

def getDateTimeNow():
    return datetime.datetime.now(pytz.utc).strftime('%d/%m/%Y, %H:%M:%S')

def getUsers():
    try:
        results = []
        with open(config.csvFilePath["users"], newline='') as inputfile:
            for row in csv.reader(inputfile):
                results.append(row[0])
    except Exception as e:
        logging.warning('{0} : failed to read user list from file. error: {1}'.format(getDateTimeNow(),e))
    logging.info('{0} : Scanned users -{1}'.format(getDateTimeNow(),results))
    return results

def getGist(user):
    url = 'https://api.github.com/users/{0}/gists'.format(user)
    resp = requests.get(url=url)
    data = resp.json()
    gists = []
    currentDateTime = datetime.datetime.now(pytz.utc)
    date = currentDateTime - datetime.timedelta(days=config.period['days'],hours=config.period['hours'], seconds= config.period['seconds'])
    try:
        for i in range(len(data)):
            dateTimeStr = data[i]['updated_at']
            updatedAt = datetime.datetime.strptime(dateTimeStr, '%Y-%m-%dT%H:%M:%SZ')
            updatedAt_local = pytz.utc.localize(updatedAt)
            if updatedAt_local >= date:
                gists.append(data[i])
    except Exception as e:
        logging.warning('{0} : Github-API: Failed to get gist through github-API'.format(getDateTimeNow()))
        if e:
            logging.warning('{0} : Error message: {1}'.format(getDateTimeNow(), e))
        if len(data['message']) != 0:
            logging.warning('{0} : Response message: {1}'.format(getDateTimeNow(),data['message']))

    return gists
def formatGist(gists):
    formatted_gists = []
    for i in range(len(gists)):
        gistDict = {
            "user": gists[i]['owner']['login'],
        "html_url": gists[i]['html_url'],
        "update_date": gists[i]['updated_at']
        }
        formatted_gists.append(gistDict)
    return formatted_gists

def writeGist(formatted_gists):
    try:
        out_file = open("gists.txt", "a")
        out_file.write(json.dumps(formatted_gists, indent=4) + "\n")
        out_file.close()
    except Exception as e:
        logging.warning('{0} : failed to write gist to file. error: {1}'.format(getDateTimeNow(),e))

def createActivity(gists):
    try:
        activityId = []
        for i in range(len(gists)):
            dateTimeStr = gists[i]['updated_at']
            updatedAt = datetime.datetime.strptime(dateTimeStr, '%Y-%m-%dT%H:%M:%SZ')
            tz = pytz.timezone(config.period['timezone'])
            updatedAt_local = tz.localize(updatedAt)
            due_date = datetime.datetime.now(pytz.utc).strftime('%Y-%m-%d')
            due_time = datetime.datetime.now(pytz.utc).strftime('%I:%M %p')
            url = "https://{0}.pipedrive.com/api/v1/activities?api_token={1}".format(config.pipedrive['domain'],config.pipedrive['api-key'])
            data = {
                "subject": "Gist created - User: {0} at {1}".format(gists[i]['owner']['login'],str(updatedAt_local)),
                "type": "Task",
                "note":  "gist url: " + gists[i]['html_url'],
                "due_date": due_date,
                "due_time": due_time
            }
            resp = requests.post(url, json=data)
            response = resp.json()
            if response['data']['id']:
                activityId.append(response['data']['id'])
            else:
                logging.warning('{0} : Pipedrive-API: failed to add activity to pipedrive. Response: {1}'.format(getDateTimeNow(),resp))
    except Exception as e:
        logging.warning('{0} : Pipedrive-API: failed to add activity to pipedrive. Error: {1}'.format(getDateTimeNow(),e))
    logging.info('{0} : Pipedrive-API: Created activities successfully for id : {1}'.format(getDateTimeNow(), str(activityId)))

def scan():
    #logging.basicConfig(filename='system.log', level=logging.DEBUG)
    logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
    users = getUsers()
    open('gists.txt', 'w').close()
    logging.info('{0} : Start Scanning'.format(getDateTimeNow()))
    for i in range(len(users)):
        gists = getGist(users[i])
        formatted_gists = formatGist(gists)
        if len(gists)>0:
            logging.info('{0} : {1} new gists created by user {2} were found.'.format(getDateTimeNow(),str(len(gists)),str(users[i])))
            writeGist(formatted_gists)
            createActivity(gists)
        else:
            logging.info('{0} : NO new gist created by user {1} was found.'.format(getDateTimeNow(),str(users[i])))
    logging.info('{0} : Completed Scanning'.format(getDateTimeNow()))


import json

filename = 'favoriteNumber.json'

try:
    with open(filename) as f_object:
        content = json.load(f_object)
        print("I know your favourite number is "+content)

except FileNotFoundError:
    favoriteNumber = input('what is your favourite number?')
    favoriteNumber = str(favoriteNumber)
    with open(filename, 'w') as f_object:
        json.dump(favoriteNumber, f_object)
        print("Your name has been stored in the database")


import jax
from jax.random import PRNGKey
import jax.numpy as jnp
import jaks
import jaks.modules as nn
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
from tqdm import tqdm
import typer

import numpy as np
from jax.nn.initializers import variance_scaling
import jaks
import jaks.modules as nn

from pathlib import Path

import matplotlib.pyplot as plt
from einops import rearrange, reduce
import optax

from dataclasses import dataclass
from collections import OrderedDict
from functools import partial
from typing import Iterable, Optional, Union, Tuple, Callable

from scipy.spatial.distance import cdist
from sklearn.decomposition import PCA
from matplotlib import font_manager
from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
from imageio import imwrite

from math import sqrt
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Iterable, Tuple, Union
import pickle

from datasets import mnist

def zscore(x, eps=1e-5):
    return (x - x.mean()) / (x.std() + eps)

@dataclass
class LayerNorm(nn.Module):
    dims: int

    def parameters(self, random_key):
        yield "scale", jnp.ones(self.dims)
        yield "translate", jnp.zeros(self.dims)

    def forward(self, params, x):
        return zscore(x) * params["scale"] + params["translate"]

@dataclass
class Linear(nn.Module):
    in_dims: int
    out_dims: int

    def parameters(self, random_key):
        matrix = jax.random.normal(random_key, (self.out_dims, self.in_dims)) * sqrt(2 / self.in_dims)
        yield "weight", matrix
        yield "bias", jnp.zeros((self.out_dims,))

    def forward(self, params, x):
        return jnp.matmul(params["weight"], x) + params["bias"]

@dataclass
class Patches(nn.Module):
    patches: int
    channels: int
    patch_shape: Tuple[int]
    image_shape: Tuple[int]
    position: bool = True

    def parameters(self, key):
        next_key = jaks.utils.PRNGSplitter(key)

        shape = (self.patches, self.channels, *self.patch_shape)
        dims = self.channels
        for dim in self.patch_shape:
            dims *= dim
        yield "filters", jax.random.normal(next_key(), shape) * sqrt(2 / dims)

        if self.position:
            dim = 1
            for pdim, idim in zip(self.patch_shape, self.image_shape):
                dim *= idim // pdim
            shape = (dim, self.patches)
            dims = self.patches
            yield "position", jax.random.normal(next_key(), shape) * sqrt(2 / dims)

    def forward(self, params, x):
        n_dims = len(self.patch_shape)
        dims = ' '.join(['dim' + str(i + 1) for i in range(n_dims)])

        x = jax.lax.conv(x, params["filters"], self.patch_shape, padding='SAME')
        x = rearrange(x, F"batch channel {dims} -> batch ({dims}) channel")
        if self.position:
            x = x + params["position"]
        return x

@dataclass
class SelfAttention(nn.Module):
    dims: int
    heads: int

    def modules(self):
        yield "layernorm", nn.Vmap(LayerNorm(self.dims))
        yield "qkv", nn.Vmap(Linear(self.dims, self.dims * 3))
        yield "out", nn.Vmap(Linear(self.dims, self.dims))

    def forward(self, params, x):
        x = self.layernorm(params, x)
        qkv = self.qkv(params, x)
        qkv = rearrange(qkv, "patch (d2 head d) -> d2 head patch d", d2=3, head=self.heads)
        q, k, v = qkv[0], qkv[1], qkv[2]
        k_t = rearrange(k, "head patch d -> head d patch")
        attn = jax.nn.softmax(jax.vmap(jnp.matmul)(q, k_t) / (k.shape[-1] ** 0.5), axis=-1)
        values = jax.vmap(jnp.matmul)(attn, v)
        values = rearrange(values, "head patch d -> patch (head d)")
        return self.out(params, values)

@dataclass
class Lambda(nn.Module):
    function: Callable

    def forward(self, params, x):
        return self.function(x)

@dataclass
class MLP2(nn.Module):
    dims: int
    hidden_scale: float = 4

    def modules(self):
        hidden_dims = int(round(self.dims * self.hidden_scale))

        yield "layernorm", LayerNorm(self.dims)
        yield "linear1", Linear(self.dims, hidden_dims)
        yield "activation", Lambda(jax.nn.relu)
        yield "linear2", Linear(hidden_dims, self.dims)

@dataclass
class EncoderBlock(nn.Module):
    dims: int
    heads: int
    hidden_scale: float = 4

    def modules(self):
        yield "attn", SelfAttention(self.dims, self.heads)
        yield "mlp2", nn.Vmap(MLP2(self.dims, self.hidden_scale))

    def forward(self, params, x):
        x = x + self.attn(params, x)
        return x + self.mlp2(params, x)

class ParallelEncoderBlock(EncoderBlock):
    def forward(self, params, x):
        return x + self.attn(params, x) + self.mlp2(params, x)

@dataclass
class PoolPatches(nn.Module):
    method = 'mean'

    def forward(self, params, x):
        return reduce(x, "patch dim -> dim", self.method)

@dataclass
class ViT(nn.Module):
    layers: int
    dims: int
    heads: int
    classes: int
    patch_shape: Tuple[int]
    image_shape: Tuple[int]
    image_channels: int

    def modules(self):
        yield "patches", Patches(self.dims, self.image_channels, self.patch_shape, self.image_shape)
        for i in range(self.layers):
            yield F"encoder{i+1}", nn.Vmap(EncoderBlock(self.dims, self.heads))
        yield "pool_patches", nn.Vmap(PoolPatches())
        yield "cls_head", nn.Vmap(Linear(self.dims, self.classes))

@dataclass
class ViTHidden(nn.Module):
    layers: int
    dims: int
    heads: int
    classes: int
    patch_shape: Tuple[int]
    image_shape: Tuple[int]
    image_channels: int

    def modules(self):
        yield "patches", Patches(self.dims, self.image_channels, self.patch_shape, self.image_shape)
        for i in range(self.layers):
            yield F"encoder{i+1}", nn.Vmap(EncoderBlock(self.dims, self.heads))
        yield "pool_patches", nn.Vmap(PoolPatches())
        yield "cls_head", nn.Vmap(Linear(self.dims, self.classes))

    def forward(self, params, x):
        x = self.patches(params, x)
        hs = jnp.zeros((x.shape[0], self.layers, *x.shape[1:]))
        hs = hs.at[:,0].set(x)
        for i in range(self.layers):
            layer = getattr(self, F"encoder{i+1}")
            x = layer(params, x)
            hs = hs.at[:,i+1].set(x)
        x = self.pool_patches(params, x)
        return self.cls_head(params, x), hs.reshape(x.shape[0], -1)

def load_dataset():
    return mnist(permute_train=False)

def load(path, name="weights"):
    path = Path(path)
    with Path(path, F"{name}.pickle").open('rb') as f:
        params = pickle.load(f)
        return params["params"], params["config"]

app = typer.Typer()

@app.command()
def run(weights: Path = "./"):

    params, cfg = load(weights)
    vit = ViTHidden(cfg["layers"], cfg["dims"], cfg["heads"], 10, (cfg["patch_size"], cfg["patch_size"]), (28, 28), 1)
    forward_fn = vit.compile(batch=False)

    X, Y, X_test, Y_test = load_dataset()

    zero_idxs, one_idxs = np.where(Y==0)[0], np.where(Y==1)[0]
    print(zero_idxs.shape, one_idxs.shape, X.shape)
    dists = cdist(X[zero_idxs], X[one_idxs])
    idxs = np.where(dists==dists.min())
    print(idxs)
    zero_img, one_img = X[zero_idxs[idxs[0][0]]], X[one_idxs[idxs[1][0]]]

    midpoint_img = (zero_img + one_img) / 2
    radius_img = np.minimum(np.abs(midpoint_img - zero_img), np.abs(midpoint_img - one_img))

    batch_n = 1000
    n_batch = 1000
    r = 1

    ball_imgs = midpoint_img + np.random.uniform(-r, r, size=(batch_n * n_batch, 784)) * radius_img
    #only toward zero
    #ball_imgs = midpoint_img + np.random.uniform(0, r, size=(batch_n * n_batch, 784)) * (zero_img - midpoint_img)

    _, hiddens = forward_fn(params, np.stack([zero_img, one_img]).reshape(2, 1, 28, 28))

    cls_idxs = np.zeros(batch_n * n_batch, dtype=int)
    dce_idxs = np.zeros(batch_n * n_batch, dtype=int)
    for batch_idx in tqdm(range(0, ball_imgs.shape[0], batch_n)):
        ball_batch = ball_imgs[batch_idx:batch_idx+batch_n]
        out, hs = forward_fn(params, ball_batch.reshape(-1, 1, 28, 28))
        cls_idxs[batch_idx:batch_idx+batch_n] = out.argmax(axis=-1)
        dce_idxs[batch_idx:batch_idx+batch_n] = cdist(hs, hiddens).argmax(axis=-1)

    ball_zero = cls_idxs == 0
    ball_one = cls_idxs == 1
    ball_other = cls_idxs >= 2

    dce_zero = dce_idxs == 0
    dce_one = dce_idxs == 1

    fig = plt.figure()
    ax = fig.add_subplot(2,2,1)
    ax.imshow(zero_img.reshape(28, 28))

    ax = fig.add_subplot(2,2,2)
    ax.imshow(midpoint_img.reshape(28, 28))

    ax = fig.add_subplot(2,2,3)
    ax.imshow(ball_imgs[5].reshape(28, 28))

    ax = fig.add_subplot(2,2,4)
    ax.imshow(one_img.reshape(28, 28))

    fig.savefig('figure.png')

    #pca = PCA(2).fit(ball_imgs[:batch_n])
    pca = PCA(2).fit(np.stack([zero_img, midpoint_img, one_img]))
    ball_proj = pca.transform(ball_imgs)
    zero_proj = pca.transform(zero_img.reshape(1, -1))
    one_proj = pca.transform(one_img.reshape(1, -1))
    midpoint_proj = pca.transform(midpoint_img.reshape(1, -1))

    #fig = plt.figure()
    #ax = fig.add_subplot(1,1,1)
    #ax.scatter(ball_proj[ball_one,0], ball_proj[ball_one,1], color='red', s=.01, label="one")
    #ax.scatter(ball_proj[ball_zero,0], ball_proj[ball_zero,1], color='blue', s=.1, label="zero")
    #ax.scatter(ball_proj[ball_other,0], ball_proj[ball_other,1], color='green', s=10, label="other")
    #ax.scatter(zero_proj[:,0], zero_proj[:,1], color='blue', s=100, label='Actual Zero')
    #ax.scatter(one_proj[:,0], one_proj[:,1], color='red', s=100, label='Actual One')
    #ax.scatter(midpoint_proj[:,0], midpoint_proj[:,1], color='black', s=100, label="Actual Midpoint")
    #plt.legend()
    #plt.gcf().set_size_inches(20, 20)
    #fig.savefig('scatter.png')

    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)
    ax.scatter(ball_proj[dce_one,0], ball_proj[dce_one,1], color='red', s=.001, label="one")
    ax.scatter(ball_proj[dce_zero,0], ball_proj[dce_zero,1], color='blue', s=.001, label="zero")
    ax.scatter(ball_proj[np.logical_and(ball_other, dce_one),0], ball_proj[np.logical_and(ball_other, dce_one),1], color='green', s=10, label="cls_other one")
    ax.scatter(ball_proj[np.logical_and(ball_other, dce_zero),0], ball_proj[np.logical_and(ball_other, dce_zero),1], color='yellow', s=10, label="cls_other zero")
    ax.scatter(zero_proj[:,0], zero_proj[:,1], color='blue', s=100, label='Actual Zero')
    ax.scatter(one_proj[:,0], one_proj[:,1], color='red', s=100, label='Actual One')
    ax.scatter(midpoint_proj[:,0], midpoint_proj[:,1], color='black', s=100, label="Actual Midpoint")
    plt.legend()
    plt.gcf().set_size_inches(20, 20)
    fig.savefig('scatter.png')

    #todo
    #-each layer
    #gen pts, find new nearest neighbors, regen between those. are there other classes in between?

if __name__ == "__main__":
    app()


import numpy as np
from mdtraj.testing import get_fn, eq, raises
import mdtraj as md
import mixtape.featurizer, mixtape.subset_featurizer
from mixtape.datasets import fetch_alanine_dipeptide

def test_SubsetAtomPairs0():
    dataset = fetch_alanine_dipeptide()
    trajectories = dataset["trajectories"]
    trj0 = trajectories[0][0]
    atom_indices, pair_indices = mixtape.subset_featurizer.get_atompair_indices(trj0)
    featurizer = mixtape.featurizer.AtomPairsFeaturizer(pair_indices)
    X_all0 = featurizer.transform(trajectories)

    featurizer = mixtape.subset_featurizer.SubsetAtomPairs(pair_indices, trj0)
    featurizer.subset = np.arange(len(pair_indices))
    X_all = featurizer.transform(trajectories)

    any([eq(x, x0) for (x, x0) in zip(X_all, X_all0)])

def test_SubsetAtomPairs1():
    dataset = fetch_alanine_dipeptide()
    trajectories = dataset["trajectories"]
    trj0 = trajectories[0][0]
    atom_indices, pair_indices = mixtape.subset_featurizer.get_atompair_indices(trj0)
    featurizer = mixtape.featurizer.AtomPairsFeaturizer(pair_indices)
    X_all0 = featurizer.transform(trajectories)

    featurizer = mixtape.subset_featurizer.SubsetAtomPairs(pair_indices, trj0, subset=np.arange(len(pair_indices)))
    X_all = featurizer.transform(trajectories)

    any([eq(x, x0) for (x, x0) in zip(X_all, X_all0)])

@raises(AssertionError)
def test_SubsetAtomPairs2():
    dataset = fetch_alanine_dipeptide()
    trajectories = dataset["trajectories"]
    trj0 = trajectories[0][0]
    atom_indices, pair_indices = mixtape.subset_featurizer.get_atompair_indices(trj0)
    featurizer = mixtape.featurizer.AtomPairsFeaturizer(pair_indices)
    X_all0 = featurizer.transform(trajectories)

    featurizer = mixtape.subset_featurizer.SubsetAtomPairs(pair_indices, trj0, subset=np.array([0, 1]))
    X_all = featurizer.transform(trajectories)

    any([eq(x, x0) for (x, x0) in zip(X_all, X_all0)])

def test_that_all_featurizers_run():
    dataset = fetch_alanine_dipeptide()
    trajectories = dataset["trajectories"]
    trj0 = trajectories[0][0]
    atom_indices, pair_indices = mixtape.subset_featurizer.get_atompair_indices(trj0)

    featurizer = mixtape.featurizer.AtomPairsFeaturizer(pair_indices)
    X_all = featurizer.transform(trajectories)

    featurizer = mixtape.featurizer.SuperposeFeaturizer(np.arange(15), trj0)
    X_all = featurizer.transform(trajectories)

    featurizer = mixtape.featurizer.DihedralFeaturizer(["phi" ,"psi"])
    X_all = featurizer.transform(trajectories)

    #featurizer = mixtape.featurizer.ContactFeaturizer()  # Doesn't work on ALA dipeptide
    #X_all = featurizer.transform(trajectories)

    featurizer = mixtape.featurizer.RMSDFeaturizer(trj0)
    X_all = featurizer.transform(trajectories)

    atom_featurizer0 = mixtape.subset_featurizer.SubsetAtomPairs(pair_indices, trj0, exponent=-1.0)
    cosphi = mixtape.subset_featurizer.SubsetCosPhiFeaturizer(trj0)
    sinphi = mixtape.subset_featurizer.SubsetSinPhiFeaturizer(trj0)
    cospsi = mixtape.subset_featurizer.SubsetCosPsiFeaturizer(trj0)
    sinpsi = mixtape.subset_featurizer.SubsetSinPsiFeaturizer(trj0)

    featurizer = mixtape.subset_featurizer.SubsetFeatureUnion([("pairs", atom_featurizer0), ("cosphi", cosphi), ("sinphi", sinphi), ("cospsi", cospsi), ("sinpsi", sinpsi)])
    featurizer.subsets = [np.arange(1) for i in range(featurizer.n_featurizers)]

    X_all = featurizer.transform(trajectories)
    eq(X_all[0].shape[1], 1 * featurizer.n_featurizers)


import uuid
from datetime import date

def extract_standard_variant_tournaments(lichess_tournaments):
    standard = []

    for tournament in lichess_tournaments:
        if tournament["variant"]["key"] == "standard":
            standard.append(tournament)

    return standard

def lichess_to_tournaments(lichess_tournaments):
    tournaments = []

    for tournament in lichess_tournaments:
        tournament_date = str(date.fromtimestamp(tournament["startsAt"]/1000))
        tournament_date = tournament_date.replace('-', '.')
        tournaments.append({
            "id": str(uuid.uuid4()),
            "name": tournament["fullName"],
            "location": "lichess.org INT",
            "date": tournament_date,
            "origin": "Lichess",
            "lichess_id": tournament["id"]
        })

    return tournaments

def transform_result(result):
    result = result.strip()
    if result == "1-0":
        return "White"
    if result == "0-1":
        return "Black"
    if result == "1/2-1/2":
        return "Draw"
    else:
        return "Unfinished/Unknown"


from __future__ import print_function, division
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data
import torch

class conv_block(nn.Module):
    """
    Convolution Block
    """
    def __init__(self, in_ch, out_ch):
        super(conv_block, self).__init__()

        self.conv = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True))

    def forward(self, x):

        x = self.conv(x)
        return x

class up_conv(nn.Module):
    """
    Up Convolution Block
    """
    def __init__(self, in_ch, out_ch):
        super(up_conv, self).__init__()
        self.up = nn.Sequential(
            nn.Upsample(scale_factor=2),
            nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        x = self.up(x)
        return x

class Recurrent_block(nn.Module):
    """
    Recurrent Block for R2Unet_CNN
    """
    def __init__(self, out_ch, t=2):
        super(Recurrent_block, self).__init__()

        self.t = t
        self.out_ch = out_ch
        self.conv = nn.Sequential(
            nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        for i in range(self.t):
            if i == 0:
                x = self.conv(x)
            out = self.conv(x + x)
        return out

class RRCNN_block(nn.Module):
    """
    Recurrent Residual Convolutional Neural Network Block
    """
    def __init__(self, in_ch, out_ch, t=2):
        super(RRCNN_block, self).__init__()

        self.RCNN = nn.Sequential(
            Recurrent_block(out_ch, t=t),
            Recurrent_block(out_ch, t=t)
        )
        self.Conv = nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        x1 = self.Conv(x)
        x2 = self.RCNN(x1)
        out = x1 + x2
        return out

class R2U_Net(nn.Module):
    """
    R2U-Unet implementation
    Paper: https://arxiv.org/abs/1802.06955
    """
    def __init__(self, img_ch=3, output_ch=1, t=2):
        super(R2U_Net, self).__init__()

        n1 = 64
        filters = [n1, n1 * 2, n1 * 4, n1 * 8, n1 * 16]

        self.Maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool3 = nn.MaxPool2d(kernel_size=2, stride=2)

        self.Upsample = nn.Upsample(scale_factor=2)

        self.RRCNN1 = RRCNN_block(img_ch, filters[0], t=t)

        self.RRCNN2 = RRCNN_block(filters[0], filters[1], t=t)

        self.RRCNN3 = RRCNN_block(filters[1], filters[2], t=t)

        self.RRCNN4 = RRCNN_block(filters[2], filters[3], t=t)

        self.RRCNN5 = RRCNN_block(filters[3], filters[4], t=t)

        self.Up5 = up_conv(filters[4], filters[3])
        self.Up_RRCNN5 = RRCNN_block(filters[4], filters[3], t=t)

        self.Up4 = up_conv(filters[3], filters[2])
        self.Up_RRCNN4 = RRCNN_block(filters[3], filters[2], t=t)

        self.Up3 = up_conv(filters[2], filters[1])
        self.Up_RRCNN3 = RRCNN_block(filters[2], filters[1], t=t)

        self.Up2 = up_conv(filters[1], filters[0])
        self.Up_RRCNN2 = RRCNN_block(filters[1], filters[0], t=t)

        self.Conv = nn.Conv2d(filters[0], output_ch, kernel_size=1, stride=1, padding=0)

       # self.active = torch.nn.Sigmoid()

    def forward(self, x):

        e1 = self.RRCNN1(x)

        e2 = self.Maxpool(e1)
        e2 = self.RRCNN2(e2)

        e3 = self.Maxpool1(e2)
        e3 = self.RRCNN3(e3)

        e4 = self.Maxpool2(e3)
        e4 = self.RRCNN4(e4)

        e5 = self.Maxpool3(e4)
        e5 = self.RRCNN5(e5)

        d5 = self.Up5(e5)
        d5 = torch.cat((e4, d5), dim=1)
        d5 = self.Up_RRCNN5(d5)

        d4 = self.Up4(d5)
        d4 = torch.cat((e3, d4), dim=1)
        d4 = self.Up_RRCNN4(d4)

        d3 = self.Up3(d4)
        d3 = torch.cat((e2, d3), dim=1)
        d3 = self.Up_RRCNN3(d3)

        d2 = self.Up2(d3)
        d2 = torch.cat((e1, d2), dim=1)
        d2 = self.Up_RRCNN2(d2)

        out = self.Conv(d2)

      # out = self.active(out)

        return out


import os
import json
import boto3
from boto3.dynamodb.conditions import Key

# Retrieve the secret from AWS Parameter Store

def get_secret(ssm_param_name):
    session = boto3.session.Session()
    ssm = session.client('ssm')

    # Retrieve the secret
    response = ssm.get_parameter(Name=ssm_param_name, WithDecryption=True)
    secret_value = response['Parameter']['Value']
    return secret_value

# AWS Lambda handler function

def get_stories():
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('stories')
    response = table.query(
        # replace with the name of your index on the timestamp attribute
        IndexName='id-index',
        KeyConditionExpression=Key('timestamp').gt(0),
        ScanIndexForward=False,  # sort in descending order
        Limit=25,
    )

def lambda_handler(event, context):

    list = get_stories()
    return {
        'statusCode': 200,
        "headers": {
            "Content-Type": "application/json"
        },
        'body': json.dumps(list)
    }


import torch.utils.data as data
import os
from PIL import Image
import torch
import cv2
import torchvision.transforms as transforms
import random

class Kinetics(data.Dataset):
    def __init__(self, video_path, img_size=368, space=5, split_num=8, lenn=10, num_class=400):
        self.samples = []
        self.split_num = split_num
        self.len = lenn

        actions = sorted(os.listdir(video_path))
        self.action_emb = {action: actions.index(action) for action in actions}
        for action in actions:
            test_videos = os.listdir(video_path + '/' + action)
            test_videos.sort()

            for video in test_videos:
                self.samples.append(action + '/' + video)
        self.video_path = video_path
        self.img_size = img_size
        self.transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])])

        self.to_pil_image = transforms.ToPILImage()
        self.space = space
        random.shuffle(self.samples)
        random.shuffle(self.samples)
        random.shuffle(self.samples)

        print("Samples: " + str(len(self.samples)))

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

    def __getitem__(self, idx):
        video_name = self.samples[idx]

        frames_ = os.listdir(self.video_path + '/' + video_name)
        frames = []
        for frame in frames_:
            if '.jpg' in frame:
                frames.append(frame)
        frames.sort()

        imgs = []
        v_length = len(frames)
        need_frame = self.len
        self.space = max(1, round(float(v_length) / float(need_frame)))
        for i in range(v_length):
            if i % self.space == self.space - 1:
                try:
                    img = Image.open(self.video_path + '/' + video_name + '/' + frames[i])
                    imgs.append(img)
                except:
                    pass

        length = len(imgs)
        input = torch.zeros(length, 3, self.img_size, self.img_size)
        for i in range(length):
            img = imgs[i].resize((self.img_size, self.img_size), Image.ANTIALIAS)
            transformed_img = self.transform(img)
            img.close()
            input[i, :, :, :] = transformed_img
        action = torch.FloatTensor(length)
        for fn in range(0, length):
            action[fn] = self.action_emb[video_name.split('/')[0]]

        L = self.len
        if len(input) >= L:
            return input[0: L], 0, 0, action[0: L]
        if len(input) < L:
            input_ = torch.zeros(L, 3, self.img_size, self.img_size)
            action_ = torch.FloatTensor(L)
            for i in range(len(input)):
                input_[i] = input[i]
                action_[i] = action[i]
            for i in range(L - len(input)):
                input_[len(input) + i] = input[-1]
                action_[len(input) + i] = action[-1]
            return input_, 0, 0, action_

Action Recognition & Anticipation/Standalone model/Kinetics_test_dataset.py


__author__ = 'Brutus'
import random
import numpy
import perceptron

# Opis parametrów roweru
# nazwa
# wynik
# 1. liczba kol
# 2. opona slick
# 3. opona semi-slick
# 4. opona terenowa
# 5. rozmiar kola
# 6. skok widelca
# 7. amortyzator
# 8. pozycja sportowa
# 9. pozycja rekreacyjna
# 10. hamulce

activationTreshold = 0.9
errors = []
eta = 0.04
n = 5000

f = open("input.txt", "r")
training_data = []
expected = []

for line in f.readlines():
    line_split = line.split()
    training_data.append(line_split[1:])
per = perceptron.Perceptron([random.random() for _ in range(0, 10)],activationTreshold)

for i in range(n):
    row_of_data = random.choice(training_data)
    expected = int(row_of_data[0])
    properties = numpy.array(row_of_data[1:],dtype=float)
    result = numpy.dot(per.weights, properties)
    error = expected - per.sigmoidelfun(result)
    errors.append(error)
    per.weights += eta * error * properties

for x in training_data:
    properties = numpy.array(x[1:],dtype=float)
    result = per.sigmoidelfun(numpy.dot(properties, per.weights))
    print("{}: {} -> {}".format(x, result, per.unit_step(result)))
    #print(errors)

#from pylab import plot, ylim
import matplotlib.pylab
matplotlib.pylab
matplotlib.pylab.ylim([-1,1])
matplotlib.pylab.plot(errors)
matplotlib.pylab.show()


from channels.routing import ProtocolTypeRouter, URLRouter
from chat.auth_middleware import TokenAuthMiddleware
import chat.routing

application = ProtocolTypeRouter({
    'websocket': TokenAuthMiddleware(  # populates conn scope with auth'd user
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})