from FHanalyze.error_handling import handle_exception
from pymongo import MongoClient
from pymongo.errors import ConfigurationError
from bson.objectid import ObjectId
from datetime import datetime, timedelta, date
import pandas as pd
import time
import logging
logger = logging.getLogger(__name__)

ALL_READINGS = '*'

class Analyze:
    """The Analyze class connects to the mongodb collection that
    contains the power readings saved by the
    `FHmonitor <https://fhmonitor.readthedocs.io/en/latest/>`_.

    To get an instance of the Analyze class, pass into __init__:

    :param mongodb_path: The mongodb connection string.  See the
        `connection string
        <https://docs.mongodb.com/manual/reference/connection-string/>`_
        documentation.  Defaults to "mongodb://127.0.0.1:27017".

    :param db_str: The database within mongodb that holds the readings.
        Defaults to "FitHome".

    :param collection_name: The collection within the database where the
        readings are stored. Defaults to "aggregate".
    """

    def __init__(self, mongodb_path="mongodb://127.0.0.1:27017",
                 db_str="FitHome", collection_name="aggregate"):
        self.mongodb_path = mongodb_path
        self.db_str = db_str
        self.collection_name = collection_name
        self.collection = None

    def get_isodate_list(self):
        """Get the list of dates in isodate format that contain active and
        reactive power readings.

        :return: list of isodates that contain power readings.
        """
        self._connect_to_collection()  # Will error out if can't connect.
        # Will error out is can't get first, last date from mongodb.
        isodate_first, isodate_last = self._get_first_and_last_isodate()

        iso_days_list = self._filter_out_dates_with_no_readings(isodate_first,
                                                                isodate_last)
        logger.debug(f'first date: {isodate_first} Last date: {isodate_last}')
        return iso_days_list

    def get_DataFrame_for_date(self, date_str=ALL_READINGS):
        """Return the active Power readings for a
        specific date or for all dates.

        :param date: isodate formatted date or "*".
            Defaults to "*"
        :return: A pandas DataFrame with a dateindex and a
            column named 'pA' of active power readings.
        """

        df = self._get_df_no_datetimeindex(date_str)
        if df.empty:
            handle_exception(
                f'There are no readings in the database for {date_str}')
        return self._set_datetimeindex(df)

    def get_always_on_watts(self, date_str=ALL_READINGS, start_time='*',
                            end_time='*', quantile=.3):
        """Return the amount of power (in watts) that are wasted by appliances that
        are always plugged in and use electricity, even when the appliance is
        not being used.

        :param date_str: Either a date in isodate format to use readings in the
            calculation for that date or '*' to use all available dates for
            the calculation.  Defaults to '*'
        :param start_time: Use readings that are taken at this time.  The
            start_time is the beginning of an amount of time to filter
            results.  Defaults to '*'
        :param end_time: Use readings that are taken before this time.
            end_time is used with start_time to filter readings to be
            within the time between start_time and end_time.  Defaults to '*'
        :param quantile: A floating point number between 0 and 1 that cuts
            the distribution of readings.  For example, a value of .3 means
            the always_on_watts value is taken at the place where 30% of the
            readings are found.  Defaults to .3.
        """
        if date_str not in ALL_READINGS:
            self._check_good_isodate(date_str)  # If not errors out.
        always_on_watts = self._calc_always_on(
            date_str, start_time, end_time, quantile)
        return always_on_watts
    #############################################################
    # Internal methods
    #############################################################

    def _connect_to_collection(self):
        """Internal method that connects to the mongo database using The path,
        database name, and collection name the instance of Analyze was
        initialized with.

        This method doesn't do anything if there is already a connection
        to the database.
        """
        if self.collection is None:
            # Create a connection and attempt to access mongod
            client = MongoClient(self.mongodb_path)
            try:
                client.server_info()  # Exception thrown if can't connect.
            except ConfigurationError as e:
                handle_exception(e)
            db = client[self.db_str]
            self.collection = db[self.collection_name]

    def _get_first_and_last_isodate(self):
        """Internal method that finds the first date and last date in which
        there are readings within the mongo db.

        Dates are in isodate format.
        """
        try:
            first_record = self.collection.find_one()
            last_record = list(self.collection.find().sort(
                [('_id', -1)]).limit(1))[0]
            isodate_first = self._id_to_isodate(first_record['_id'])
            isodate_last = self._id_to_isodate(last_record['_id'])
        except Exception as e:
            handle_exception(e)
        return isodate_first, isodate_last

    def _id_to_isodate(self, id):
        """The first 8 bits of the object id in each record is the timestamp
        when the reading was put into the database.

        :param id: object id of the mongodb record.
        """
        id_str = str(id)
        hex_str = id_str[0:8]
        ts = int(hex_str, 16)
        return datetime.fromtimestamp(ts).isoformat()

    def _filter_out_dates_with_no_readings(self, first_isodate, last_isodate):
        """This internal method checks the mongo db to see if the dates in the
        first_isodate and last_isodates range have power readings.

        :param first_isodate: first date in isodate format that should
        be checked until the last_isodate.
        :param last_isodate: the last date between the first date in
        isodate format.
        :return: lists of dates in isodate format that have power readings.
        """
        isodates_list = []
        try:
            # First get the string isodates into date types.
            start_date = datetime.fromisoformat(first_isodate).date()
            end_date = datetime.fromisoformat(last_isodate).date()
            # Create a general expresiion to enumerate through all the
            # possible dates between the start and end dates.
            gen_expr = (start_date + timedelta(n)
                        for n in range(int((end_date-start_date).days)+1))
            # Go through each date and see if there are readings available.
            # If readings are available for the date, append the date to
            # the isodate list in isodate format.

            for dt in gen_expr:
                if self._there_is_a_reading(dt):
                    # Is there at least one reading for this date?
                    isodates_list.append(dt.isoformat())
        except Exception as e:
            handle_exception(e)
        return isodates_list

    def _there_is_a_reading(self, dt):
        """An internal method that determines if there are power
        readings for the date being passed in.
        :param dt: A date in (python) date format
        :return: True if there is a reading for the date.
        """

        # Make an object ID using the date.
        day_id = self._make_objectid(dt)
        dt_next = dt + timedelta(days=1)
        next_day_id = self._make_objectid(dt_next)
        # Get a connection to mongodb if it doesn't exist.
        self._connect_to_collection()
        # Find out if there are any readings.
        # It seems more intuitive to me for the query to use equal
        # instead of lt and gt, but I couldn't find an eq?
        count = self.collection.count(
            {"_id": {'$gt': day_id, '$lt': next_day_id}})
        if count > 0:
            return True
        return False

    def _make_objectid(self, d):
        """An internal method that takes in an isodate and returns an object id
        that can be used to query readings of that date. The object id in the
        mongodb is a string of 12 bytes.  The first 4 bytes is the unix
        timestamp when the entry  was made.  We use these bytes to get readings
        of a specific date.  Once we have the 4 bytes, we put the string '00'in
        the remaining 8 characters.

        :param isodate_string: The isodate that will be turned into an object
        id.date.
        """
        # Get the timestamp as a 4 byte hex string
        ts_string = '{:x}'.format(int(time.mktime(d.timetuple())))
        # Create an object id starting with the time stamp string then padded
        # with 00's to have 12 hex bytes represented within the object id
        # string.
        object_id = ObjectId(ts_string + "0000000000000000")
        return object_id

    def _get_df_no_datetimeindex(self, date_str):
        """An internal method that returns a DataFrame that includes mongo db's
        object id.  The object id has not been coverted to a datetimeindex.

        :param date_str: Either a date in isodate format to use readings in the
            calculation for that date or '*' to use all available dates for
            the calculation.  Defaults to '*'
        :raises a: Exception if the records can't be retrieved or the date_str
            isn't of isodate format (or '*').
        :return: [description]
        :rtype: [type]
        """
        df = pd.DataFrame()
        self._connect_to_collection()
        if date_str not in ALL_READINGS:  # Get eadings for a specific isodate.
            # Check to make sure the date_str is a valid isodate.
            try:
                # Will raise a ValueError if cannot
                # convert from date_str to date datetype.
                dt = self._check_good_isodate(date_str)
                day_id = self._make_objectid(dt)
                dt_next = dt + timedelta(days=1)
                next_day_id = self._make_objectid(dt_next)
                try:
                    df = pd.DataFrame.from_records(self.collection.find(
                        {"_id": {'$gt': day_id, '$lt': next_day_id}},
                        {'_id': 1, 'Pa': 1}))
                except Exception as e:
                    handle_exception(e)
            except ValueError as e:
                handle_exception(e)
        else:
            try:
                df = pd.DataFrame.from_records(
                    self.collection.find(projection={'_id': 1, 'Pa': 1}))
            except Exception as e:
                handle_exception(e)
        return df

    def _set_datetimeindex(self, df):
        """internal method to convert the object id column (_id)
        into a datetimeindex for the power readings.
        :param df: DataFrame created by calling the
        _get_df_no_datetimeindex() method.
        :type df: A pandas DataFrame where one of the columns is
        the mongodb object id labeled as _id.
        :return: A DataFrame with the _id column making up the
        datetimeindex and then removed from the DataFrame.
        """
        df['timestamp'] = df['_id'].astype('|S').str.slice(
            start=0, stop=8).apply(int, base=16)
        df.index = pd.to_datetime(df['timestamp'], unit='s')
        df.index = df.index.tz_localize('UTC').tz_convert('US/Pacific')
        df.index = df.index.round('s').tz_localize(None)
        df.drop(['timestamp', '_id'], axis=1, inplace=True)
        return df

    def _check_good_isodate(self, date_str):
        """internal method that returns a Date datatype
        by converting the isodate date_str into a Date.
        An exception is raised if the date_str is not
        isodate formatted.

        :param date_str: isodate formatted date string.

        """
        dt = None
        try:
            dt = date.fromisoformat(date_str)
        except ValueError as e:
            handle_exception(e)
        return dt

    def _calc_always_on(self,
                        date_str, start_time, end_time, quantile):
        """ Internal method to determine how much of the electricity used
        during the date_str dates and start_time end_time time period
        is wasted.
        :param date_str: Either a date in isodate format to use readings in the
            calculation for that date or '*' to use all available dates for
            the calculation.
        """
        df = self.get_DataFrame_for_date(date_str)
        # If filtering on a time range, both start and end time need
        # to be defined.
        if start_time not in '*' and end_time not in '*':
            df = df.between_time(start_time, end_time)
        watt_leakage = float(df.quantile(quantile))
        return watt_leakage


from django.shortcuts import render,redirect
from .models import *
from django.contrib import messages
from django.contrib.auth.decorators import login_required

def index(request):
    courses = Course.objects.all().order_by('-id')[:3]
    blogs = Blog.objects.all().order_by('-date_created')[:3]
    context = {'courses': courses, 'blogs': blogs}
    return render(request, 'mainsite/index.html', context)

def about(request):
    context = {}
    return render(request, 'mainsite/about.html', context)

def contact(request):
    if request.method == 'POST':
        message = request.POST['message']
        name = request.POST['name']
        email = request.POST['email']
        subject = request.POST['subject']
        messages.info(request, 'Thank you for Contacting us ' +
                      name + ", We shall get back to you very soon")

        print(message)
        print(name)
        print(email)
        print(subject)

    context = {}
    return render(request, 'mainsite/contact.html', context)

@login_required(login_url='/login/')
def blogsingle(request, blog_id):
    blogs = Blog.objects.all().order_by('-date_created')[:7]
    blog = Blog.objects.get(pk=blog_id)
    comments = BlogComments.objects.filter(post=blog).order_by('-id')
    if request.method == 'POST':
        name = request.user.username
        email = request.user.email
        comment = request.POST['comment']
        BlogComments.objects.create(
            post=blog, name=name, email=email, comment=comment)

    context = {'blog': blog, 'comments': comments, 'blogs': blogs}
    return render(request, 'mainsite/blogpost.html', context)

@login_required(login_url='/login/')
def blog(request):
    courses = Course.objects.all().order_by('-id')[:3]
    blogs = Blog.objects.all().order_by('-id')[:10]
    context = {'blogs': blogs, 'courses': courses}
    return render(request, 'mainsite/blog.html', context)

@login_required(login_url='/login/')
def courses(request):
    courses = Course.objects.all().order_by('tag')
    context = {'courses': courses}
    return render(request, 'mainsite/courses.html', context)

@login_required(login_url='/login/')
def courseDetails(request, course_id):
    course = Course.objects.get(pk=course_id)
    lesson = lessons.objects.filter(course=course).order_by('postion')
    reviews = coursefeedback.objects.filter(course=course).order_by('-id')
    if request.method == 'POST':
        name = request.user.username
        comment = request.POST['feedback']
        coursefeedback.objects.create(
            course=course, name=name, comment=comment)
    context = {'course': course, 'lesson': lesson, 'reviews': reviews}
    return render(request, 'mainsite/courseDetails.html', context)

@login_required(login_url='/login/')
def courseLesson(request, lesson_id):
    lesson = lessons.objects.get(pk=lesson_id)
    reviews = lessonfeedback.objects.filter(lesson=lesson).order_by('-id')
    if request.method == 'POST':
        name = request.user.username
        comment = request.POST['feedback']
        lessonfeedback.objects.create(
            lesson=lesson, name=name, comment=comment)

    context = {'lesson': lesson,'reviews':reviews}
    return render(request, 'mainsite/lesson.html', context)


from typing import Dict, List, Any, Union, Tuple

import requests
from bs4 import BeautifulSoup
import unidecode
import datetime
from dateutil.rrule import rrule, WEEKLY
import logging

BASE_URL_DATA = "https://www.stw.berlin/xhr/speiseplan-und-standortdaten.html"
BASE_URL_MENU = "https://www.stw.berlin/xhr/speiseplan-wochentag.html"

mensa2id = {
    **dict.fromkeys(["hardenberg", "haupt", "hauptmensa", "hardenbergmensa", "321"], "321"),
    **dict.fromkeys(["skyline cafe", "skyline", "cafe skyline" "tel", "657"], "657"),
    **dict.fromkeys(["march", "mar", "marmensa", "marchgebäude", "mar cafe", "march cafeteria", "538"], "538"),
    **dict.fromkeys(["wetterleuchte", "wetterleuchten", "hauptgebaudecafe", "cafe wetterleuchte",
                    "wetterleuchten cafe", "541"], "541"),
    # TODO: add more cafeterias
}

id2mensa = {
    "321": "Hardenbergmensa",
    "657": "Skyline Café",
    "538": "March Cafeteria",
    "541": "Wetterleuchten Café"
}

def get_mensa_id(mensa):
    # get's a raw mensa-name transforms this to lowercase and replaces unicode characters with
    # normal ascii characters (ä -> a) and if that name exist the corresponding id is returned
    return mensa2id.get(unidecode.unidecode(mensa.lower()))

def get_mensa_name(mensa_id):
    # defines the name for mensas
    return id2mensa.get(mensa_id)

class MensaRequest:

    weekday_translation = {
        "Mo.": 0,
        "Di.": 1,
        "Mi.": 2,
        "Do.": 3,
        "Fr.": 4,
        "Sa.": 5,
        "So.": 6
    }

    def __init__(self, mensa, date=None):
        if not isinstance(mensa, str):
            raise ValueError("mensa  must be from type list and only contain strings")
        if date and not (isinstance(date, datetime.datetime)):
            raise ValueError("date must be of type datetime.datetime or None")
        if not date:
            date = datetime.datetime.now()

        self.mensa = mensa
        self.mensa_id = get_mensa_id(mensa)
        self.date = date

        logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
        logging.getLogger("requests").setLevel(logging.DEBUG)
        logging.getLogger("urllib3").setLevel(logging.DEBUG)
        logging.getLogger("chardet.charsetprober").setLevel(logging.DEBUG)

    def _get_page_soup(self, url, date=None):
        # expects a mensa which is contained in the mensa_dict and can handle a date in the format YYYY-MM-DD
        # returns the page content as BeautifulSoup object

        # all the payload for the POST request
        if not self.mensa_id:
            logging.debug("Mensa: " + self.mensa + " not found")
            return None
        data = {"resources_id": self.mensa_id, "date": date}
        headers = {"User-Agent": "Mozilla/5.0"}

        # HTTP request
        page = requests.post(url, data, headers=headers)
        if page.status_code != 200:
            raise ConnectionRefusedError("Couldn't connect to the stw-website ERROR: {}".format(page.status_code))

        # logging.debug(page.content)
        return BeautifulSoup(page.content, "html.parser")

    def get_mensa_menu(self):
        # cf. https://gitlab.tubit.tu-berlin.de/thilo.michael/alex-studiprojekt-ss17/blob/mensa/mensa/mensaCrawler
        # /crawler.py this function requests all dishes available in a specific day for a specific mensa return a
        # list of dictionaries
        # TODO: describe datatype somewhere
        # logging.debug("\nMensa: " + self.mensa + '\n' + "Date: " + str(self.date))
        url = BASE_URL_MENU
        date_for_request = self.date.strftime("%Y-%m-%d")
        page_soup = self._get_page_soup(url, date_for_request)
        if not page_soup:
            return None
        food_offer = unidecode.unidecode(page_soup.find("div", class_="container-fluid splGroupWrapper")
                                         .get_text().strip().lower())
        if food_offer == "kein speisenangebot":
            logging.debug("No food food_offer on: "
                          + self.date.strftime("%d-%m-%Y") + " for Mensa: " + self.mensa_id)
            return self.mensa_id, self.date, None

        # list to be filled in
        food_list = []
        icon_translations = {
            "ampel_gruen_70x65.png": "green",
            "ampel_gelb_70x65.png": "yellow",
            "ampel_rot_70x65.png": "red",
            "1.png": "vegetarian",
            "15.png": "vegan",
            "18.png": "bio",
            "38.png": "marine",
            "43.png": "climate"
        }

        for course in page_soup.findAll("div", class_="container-fluid splGroupWrapper"):
            curr_course = course.find_next("div", class_="col-md-12 splGroup").get_text()
            for dish in course.findAll("div", class_="row splMeal"):
                food_item = _MensaFood()
                # extract course information
                food_item.course = curr_course

                # extract name information
                food_item.name = dish.find_next("span").get_text()

                # extract price information
                # logging.info("Price: " + dish.find_next("div", class_="col-xs-6 col-md-3 text-right")
                # .get_text().strip())
                prices = dish.find_next("div", class_="col-xs-6 col-md-3 text-right").get_text().strip()
                if prices != "":
                    prices = list(map(float, dish.find_next("div", class_="col-xs-6 col-md-3 text-right").get_text()
                                      .split()[1].replace(',', '.').split('/')))
                    if len(prices) == 3:
                        food_item.price_student = prices[0]
                        food_item.price_employee = prices[1]
                        food_item.price_extern = prices[2]
                    else:
                        food_item.price_student = food_item.price_employee = food_item.price_extern = prices[0]

                # extract tag information
                # HINT: this has to be a String because there are tags like "21a"
                food_item.tags = dish.find_next("div", class_="kennz ptr toolt").contents[0] \
                    .strip("\n\r ()").split(", ")

                # extract information based on icons
                for icon in dish.find_next("div", class_="col-xs-12 col-md-3").findAll("img"):
                    icon_src = icon.get("src").split('/')[-1:][0]
                    if "ampel" in icon_src:
                        food_item.traffic_light = icon_translations[icon_src]
                    else:
                        setattr(food_item, icon_translations[icon_src], True)
                food_list.append(food_item.get_mensa_food_dict())

        return self.mensa_id, self.date, food_list

    def get_mensa_data(self) -> (str, dict):
        # {
        # "name": ..,
        # "address": ..,
        # "website": ..,
        # "email": ..,
        # "phone": ..,
        # "opening_hours": ..,
        # "opening_hours_additional": ..,
        # "transfer": ..,
        # }
        url = BASE_URL_DATA
        page_soup = self._get_page_soup(url)
        if not page_soup:
            return None
        # dict to be filled in
        mensa_data: Dict[str, Union[Union[None, str, List[Tuple[List[Any], List[Any]]], List[str]], Any]] = {
            "name": None,
            "address": None,
            "website": None,
            "email": None,
            "phone": None,
            "opening_hours": None,
            "opening_hours_additional": None,
            "transfer": None
        }
        # extracting information
        # HINT: this uses an icon as orientation, which isn't that nice
        # extract name information
        name_information = page_soup.find(selected="selected").get_text()
        mensa_data["name"] = name_information
        # extract address information
        address_elements = page_soup.find(class_="glyphicon glyphicon-map-marker").parent.find_next("div")
        street = address_elements.contents[0].strip()
        postcode = address_elements.contents[4].strip()
        district = address_elements.find_all_next("small")[1].contents[0].strip("()")
        mensa_data["address"] = '\n'.join([street, postcode, district])

        # extract website information
        mensa_data["website"] = page_soup.find(id="directlink").get_text()
        # extract email information
        mensa_data["email"] = page_soup.find(class_="glyphicon glyphicon-envelope").parent.find_next("a").contents[0]

        # extract phone information
        mensa_data["phone"] = page_soup.find(class_="glyphicon glyphicon-earphone") \
            .parent.find_next("div").contents[0].strip()

        # extract opening hours
        def _create_opening_hours_list(hours):
            # function to create a data format, which makes it possible to check
            # if a mensa is open on a specific day/time
            # data format: opening_hours_list = [([opening-datetime-of-weekday-for-next-four-weeks],
            #                                           [closing-datetime-of-weekday-for-next-four-weeks]), ...]
            # TODO: explain/code check_if_open(when<datetime>)
            opening_hours_list = []
            if unidecode.unidecode(hours[1]) == '-':  # handling day interval
                for day in range(self.weekday_translation[hours[0]], self.weekday_translation[hours[2]] + 1):
                    opening_unformatted = hours[3].split(':')
                    opening = list(rrule(dtstart=datetime.datetime.now()
                                         .replace(hour=int(opening_unformatted[0]), minute=int(opening_unformatted[1]),
                                                  second=0), freq=WEEKLY, count=4, byweekday=day))
                    closing_unformatted = hours[5].split(':')
                    closing = list(rrule(dtstart=datetime.datetime.now().
                                         replace(hour=int(closing_unformatted[0]), minute=int(closing_unformatted[1]),
                                                 second=0), freq=WEEKLY, count=4, byweekday=day))
                    opening_hours_list.append((opening, closing))
            else:  # handling a single day
                day = self.weekday_translation[hours[0]]
                opening_unformatted = hours[1].split(':')
                opening = list(rrule(dtstart=datetime.datetime.now()
                                     .replace(hour=int(opening_unformatted[0]), minute=int(opening_unformatted[1]),
                                              second=0), freq=WEEKLY, count=4, byweekday=day))
                closing_unformatted = hours[3].split(':')
                closing = list(rrule(dtstart=datetime.datetime.now().
                                     replace(hour=int(closing_unformatted[0]), minute=int(closing_unformatted[1]),
                                             second=0), freq=WEEKLY, count=4, byweekday=day))
                opening_hours_list.append((opening, closing))
            return opening_hours_list

        opening_hours_information_list = []
        opening_hours_additional_information = []
        curr_div = page_soup.find(class_="glyphicon glyphicon-time").parent.find_next("div")
        while len(curr_div.contents) < 2 or "transfer" not in str(curr_div.contents[1]):
            if len(curr_div.contents) > 4:
                opening_hours_information = curr_div.contents[3]
                if opening_hours_information and not isinstance(opening_hours_information, str):
                    opening_hours_information_formatted = opening_hours_information.get_text().split()
                    if opening_hours_information_formatted[0] in self.weekday_translation.keys():
                        opening_hours_information_list \
                            .extend(_create_opening_hours_list(opening_hours_information_formatted))
                    else:
                        opening_hours_additional_information.append(' '.join(opening_hours_information_formatted))

            curr_div = curr_div.find_next("div")
        mensa_data["opening_hours"] = opening_hours_information_list
        mensa_data["opening_hours_additional"] = opening_hours_additional_information
        # extract transfer information
        transfer_information_list = []
        transfer_elements = page_soup.find(class_="glyphicon glyphicon-transfer").parent.find_next("div").contents
        for element in transfer_elements:
            if isinstance(element, str):
                if element.strip():
                    transfer_information_list.append(element.replace('\xa0', ' '))
            else:
                transfer_information_list.append(element.get_text().strip())
        mensa_data["transfer"] = ''.join(transfer_information_list)
        return self.mensa_id, mensa_data

class _MensaFood:
    name = None
    course = None
    price_student = None
    price_employee = None
    price_extern = None
    tags = None
    traffic_light = None
    vegetarian = None
    vegan = None
    climate = None
    bio = None
    marine = None

    def __init__(self):
        self.vegetarian = False
        self.vegan = False
        self.climate = False
        self.bio = False
        self.marine = False
        return

    def get_mensa_food_dict(self):
        mensa_food_dict = {
            "name": self.name,
            "course": self.course,
            "price_student": self.price_student,
            "price_employee": self.price_employee,
            "price_extern": self.price_extern,
            "tags": self.tags,
            "traffic_light": self.traffic_light,
            "vegetarian": self.vegetarian,
            "vegan": self.vegan,
            "climate": self.climate,
            "bio": self.bio,
            "marine": self.marine
        }
        return mensa_food_dict


"""
@author: Brett Settle
@Department: UCI Neurobiology and Behavioral Science
@Lab: Parker Lab
@Date: August 6, 2015
"""
import tifffile
from .bin2mat import *
#from PyQt4.QtCore import *
#from PyQt4.QtGui import *
from qtpy.QtCore import *
from qtpy.QtGui import *
from qtpy import QtWidgets
import xlrd, re, os
import numpy as np
import flika
from flika import global_vars as g
from flika.window import Window

try:
	from scipy.ndimage import imread
except:
	print("Imread could not be loaded. Some image files will not be openable")
import sys
#from PyQt4.QtCore import pyqtSignal as Signal
from qtpy.QtCore import Signal
last_dir = ''

def getHeaders(fname, delimiter='\t'):
	return [s.strip() for s in open(fname, 'r').readline().split(delimiter)]

def export3DArray(fname, arr, header = '', fmt='%-7.2f'):
	with open(fname, 'w') as outfile:
		outfile.write('%s\n' % header)
		for sub in arr:
			for row in sub:
				outfile.write(['\t'.join([fmt % i for i in row])])

def export3DArray_2(fname, arr, header = '', fmt='%-7.2f'):
	with open(fname, 'w') as outfile:
		outfile.write('%s\n' % header)
		for sub in arr:
			sub = np.array2string(sub, separator='\t',formatter={'float_kind':lambda x: fmt % x}).replace("[", "").replace("]","")
			#sub = np.array2string(sub, precision=4, separator='\t',suppress_small=True).replace("[", "").replace("]","")
			outfile.write('%s\n' % sub)

def export_arr(arr, header = '', fmt='%-7.2f'):
	savename = getSaveFilename()
	export3DArray_2(savename, arr, header = header, fmt=fmt)
	g.m.statusBar().showMessage('coordiates exported') #send completion message to flika
	return

def exportDictionaryList(filename, dict_list, delimiter="\t", order=None):
	'''export a list of dictionaries with similar keys '''
	if order == None:
		order = set(dict_list[0].keys())
		for d in dict_list:
			order &= set(d.keys())
		order = list(order)
	with open(filename, 'w') as outf:
		outf.write("%s\n" % delimiter.join(order))
		for d in dict_list:
			outf.write("%s\n" % delimiter.join([str(d[name]) for name in order]))

def getFilename(title='Select a file to open', **args):
	global last_dir
	to_ret = str(QtWidgets.QFileDialog.getOpenFileName(caption=title, directory=last_dir, **args)[0])
	last_dir = os.path.dirname(to_ret)
	return to_ret

def getFilenames(title='Select a file to open', **args):
	global last_dir
	to_ret = [str(i) for i in QtWidgets.QFileDialog.getOpenFileNames(caption=title, directory=last_dir, **args)]
	if len(to_ret) == 0:
		return
	last_dir = os.path.dirname(to_ret[0])
	return to_ret

def getSaveFilename(title='Save to file...', initial='', **args):
	global last_dir
	if initial != '':
		last_dir = os.path.join(last_dir, initial) if "\\" not in initial else initial
	savename = str(QtWidgets.QFileDialog.getSaveFileName(caption=title, directory = last_dir, **args)[0])
	last_dir = os.path.dirname(savename)
	return savename

def getDirectory(caption, initial=''):
	return QtWidgets.QFileDialog.getExistingDirectory(None, caption, directory=initial)

def readTif(fname=''):
	if fname == '':
		fname = getFilename(filter = "All files (*.*);;TIF Files (*.tif)")
	if not os.path.isfile(fname):
		raise Exception("No File Selected")
	tif = tifffile.TIFFfile(fname).asarray().astype(np.float32)
	img = np.squeeze(tif)
	return img

def fileToArray(f=''):
	arr = []
	def fix_str_type(s):
		if re.match(r'^[\d\.]+$', s):
			return float(s)
		else:
			return str(s)
	if f == '':
		f = getFilename(filter = "All files (*.*);;Image Files (*.jpg, *.png);;TIF Files (*.tif);;Other Files (*.stk, *.lsm, *.nih)")
	if f.endswith(('.tif', '.nd2', '.nih', '.stk')):
		return read_tif(f)
	elif f.endswith(('.jpg', '.png')):
		arr = imread(f)
	elif f.endswith('.txt'):
		try:
			arr = np.loadtxt(f)
			breaks = [i for i, line in enumerate(open(f, 'r')) if line.strip() == '']
			breaks = np.subtract(breaks, range(len(breaks)))
			if len(breaks) > 0:
				arr = [a for a in np.split(arr, breaks) if a.size > 0]
		except Exception as e:
			l = 0
			for i, line in enumerate(open(f)):
				arr.append([fix_str_type(l.strip()) for l in line.split('\t')])
	elif f.endswith(('.xls', 'xlsx')):
		workbook = xlrd.open_workbook(f)
		arr = []
		for sheet in workbook.sheets():
			num_rows = sheet.nrows - 1
			curr_row = -1
			while curr_row < num_rows:
				curr_row += 1
				row = sheet.row(curr_row)
				arr.append([float(fix_str_type(str(s.value))) for s in row])
	elif f.endswith(('.npy', '.npz')):
		arr = np.load(f)
		if arr.ndim == 2 and arr.shape[0] > arr.shape[1]:
			arr = arr.transpose()
	else:
		print('Failed to open')
		return

	if type(arr) == list and any([type(j) == str for j in arr[0]]):
		names = arr[0]
		arr_dict = {n : [[]] for n in arr[0]}
		count = 0
		for row in arr[1:]:
			if all([i == '' for i in row]):
				count += 1
				for n in names:
					arr_dict[n].append([])
			else:
				for i, j in enumerate(row):
					arr_dict[names[i]][count].append(j)

		return {k: np.squeeze(v) for k,v in arr_dict.items()}
	else:
		return np.array(arr)

class ImageImporter(QThread):
	done = Signal(object)
	def __init__(self, filename=''):
		super(ImageImporter, self).__init__()
		if filename == '':
			filename = getFilename(filter = "All files (*.*);;Image Files (*.jpg, *.png);;TIF Files (*.tif);;Other Files (*.stk, *.lsm, *.nih)")
		self.filename = filename
		if self.filename == '':
			raise Exception('No file selected')

	def run(self):
		if self.filename.endswith(('.tif', '.nd2', '.nih', '.stk')):
			image = tifffile.TIFFfile(self.filename).asarray().astype(np.float32)
			image = np.squeeze(np.swapaxes(image, 2, 1))
		elif self.filename.endswith(('.jpg', '.png')):
			image = imread(self.filename).swapaxes(0, 1)
		elif self.filename.endswith(('.npy', '.npz')):
			image = np.load(self.filename)
			if simage.ndim == 2 and image.shape[0] > image.shape[1]:
				image = self.image.transpose()
		self.done.emit(image)
		del image
		self.terminate()

class TiffExporter(QThread):
	done = Signal()
	def __init__(self, fname, tiff):
		super(TiffExporterd, self).__init__()
		self.filename = fname
		self.tiff = tiff

	def run(self):
		tifffile.imsave(self.filename, self.tiff)
		self.done.emit()
		self.terminate()

def importDictionary(filename='', mode='columns'):
	if filename == '':
		filename = getFilename(filter = "Text files (*.txt)")
	keys = []
	data = []
	for i, line in enumerate(open(filename, 'r')):
		if i == 0:
			keys = line.split()
			if k[0].startswith('# '):
				k[0] = k[0][2:]
		else:
			data.append([float(j) for j in line.split()])
	data = np.array(data)
	if mode == 'columns':
		data = data.transpose()
		return OrderedDict([(keys[i], data[i]) for i in range(len(keys))])
	elif mode == 'rows':
		return [dict(zip(keys, i)) for i in data]

def importFile(filename, delimiter="\t", columns=[], evaluateLines=True, skipFirstRow=False, encoding=False):
	'''read info from a file, into a list of columns (specified by args) or dictionaries (specified by kargs)'''
	data = []

	if evaluateLines:
		if encoding==False:
			lines = [[evaluate(i) for i in line.split(delimiter)] for line in open(filename, 'r')]
		else:
			lines = [[evaluate(i) for i in line.split(delimiter)] for line in open(filename, 'r', encoding=encoding)]
	else:
		if encoding==False:
			lines = [[i for i in line.split(delimiter)] for line in open(filename, 'r')]
		else:
			lines = [[i for i in line.split(delimiter)] for line in open(filename, 'r', encoding=encoding)]

	if skipFirstRow:
		lines.pop(0)

	if len(columns) == 0: # no columns given, return data as it is read from file
		return lines
	else:
		names = lines[0]    # read data to dictionary given columns
		lines = lines[1:]
		if all([type(i) == str for i in columns]):
			data = {}
			for n in columns:
				data[n] = [lines[i][names.index(n)] for i in range(len(lines))]
		elif all([type(i) == int for i in columns]):
			data = {}
			for i in columns:
				n = names[i]
				data[n] = [lines[j][i] for j in range(len(lines))]
		return data

def evaluate(i):
	try:
		return eval(i)
	except:
		return i


#$env:FLASK_APP = "server2.py"
#flask run
from flask import Flask, request,render_template
import sqlite3

app = Flask(__name__)

#lägger till i min databas
@app.route('/postjson', methods=['POST'])
def add_weather():
    conn = sqlite3.connect('databas.db')
    c = conn.cursor()
    p = (request.json['sensor'], request.json['temperature'], request.json['humidity'], request.json['pressure'], request.json['wind'])
    c.execute('INSERT INTO SensorData (sensor, temperature, humidity, pressure, wind) VALUES(?,?,?,?,?)',p)
    conn.commit()
    return f"Successful"

#läser in ifrån databas till html tablen
@app.route('/')
def w_data():
    conn = sqlite3.connect('databas.db')
    c = conn.cursor()
    c.execute('''SELECT id, date, sensor, temperature, humidity, pressure, wind FROM SensorData''')
    w_data = c.fetchall()
    return render_template ('weather.html', w_data=w_data)
#tar bort min info

@app.route('/delete')
def delete():
    conn = sqlite3.connect('databas.db')
    c = conn.cursor()
    c.execute('''DELETE FROM SensorData''')
    conn.commit()
    return render_template('deleted.html')

app.run(debug=True)


import collections

# Complete the sherlockAndAnagrams function below.
def sherlockAndAnagrams(s):
    counter = collections.Counter()
    for size in range(1, len(s)):
        for i in range(len(s) - size + 1):
            key = ''.join(sorted(s[i:i+size]))
            counter[key] += 1
    return sum((s * (s - 1) // 2) for s in counter.values())

def main():
    cases = [
        'abba',
        'ifailuhkqq',
        'kkkk',
        'cdcd',
    ]
    for case in cases:
        print(sherlockAndAnagrams(case))

if __name__ == '__main__':
    main()


import flwr as fl
import utils
import tensorflow as tf

def fit_round(rnd):
  """Send round number to client."""
  return {"rnd": rnd}

def get_eval_fn(model):
  """Return an evaluation function for server-side evaluation."""

  # Load test data here to avoid the overhead of doing it in `evaluate` itself
  x_test, y_test = utils.load_data('test.csv', 15)

  # The `evaluate` function will be called after every round
  def evaluate(parameters):
    # Update model with the latest parameters
    model.set_weights(parameters)
    loss, accuracy = model.evaluate(x_test, y_test)
    return loss, {"accuracy": accuracy}

  return evaluate

# Start Flower server for three rounds of federated learning
if __name__ == "__main__":

    #set random seed, so all radom numbers genation process can be reproceduced between executions
    tf.random.set_seed(42)

    #create the model
    model=tf.keras.Sequential([
      tf.keras.Input(shape=(24,)),
      tf.keras.layers.Dense(48,activation="relu"),
      tf.keras.layers.Dense(4,activation="softmax")
    ])

    #compile the model
    model.compile(
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      optimizer=tf.keras.optimizers.Adam(),
      metrics="accuracy"
    )

    strategy = fl.server.strategy.FedAvg(
        min_available_clients=2,
        eval_fn=get_eval_fn(model),
        on_fit_config_fn=fit_round,
    )

    fl.server.start_server("0.0.0.0:8080", strategy=strategy, config={"num_rounds": 2})


import discord
from discord.ext import commands
#By Tomás Ryan
#tomasryanmann@gmail.com
##########
#tammi bot code
#last edited 5/1/2018
##########
#basically a warm up to get back into coding as im rusty as fuck
##########
bot = commands.Bot(command_prefix='yotam ', description='sure no bother')

@bot.event
async def on_ready():
    print('this is')
    print(bot.user.name)
#Bot command list
@bot.command()
async def hello():
    await bot.say('h0i! im TamMiB0t! c:')
@bot.command()
async def joke():
    await bot.say('make me')
@bot.command()
async def endWorld():
    await bot.say("couldn't be arsed tbh")
@bot.command()
async def say(*, something):
    await bot.say(something)

bot.run('MzA1MTc3MzExMzY2MzQ4ODEx.DTEsCg.XQL8D5MESGion9OtogvuW8dsyT8')

#https://discordapp.com/api/oauth2/authorize?client_id=305177311366348811&scope=bot&permissions=0


# -*- coding: utf-8 -*-
"""
Created on Mon Nov 23 00:46:52 2020

@author: bbari
"""
import pandas as pd
import wikipedia
import io, sys, os, hashlib
from selenium import webdriver
from image_scrapper import search_and_download
import wikipedia
column_names = ["personName", "birthDate", "imageUrl", "info"]

df = pd.DataFrame(columns = column_names)

dataset = pd.read_csv('moovies.csv' )

dataset = dataset.iloc[:, -3:-1].values

pure_names = []

## awwww shit what the heckkkk!!! written when high otherwise, wouldn't bother to mess this shit
for item in dataset:
    for name in item:
        if("," in name):
            names = name.split(",")
            for string in names :
                if("(" in string):
                    if("|" in string.split("(")[0]):
                        pure_names.append( string.split("(")[0].split("|")[0])
                    else:
                        pure_names.append( string.split("(")[0])
                else:
                    if("|" in string):
                        pure_names.append( string.split("|")[0])
                    else:
                        pure_names.append( string)
        else:
            if("(" in name):
                pure_names.append(name.split("(")[0])
            else:
                if("|" in name):
                    pure_names.append(name.split("|")[0])
                else:
                    pure_names.append(name)

pure_names.remove("actors") ##// some fucked headers
pure_names.remove("writers") ##// dont mind these lines

chrome_options = webdriver.chrome.options.Options()
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--headless")

i = 0
from webdriver_manager.chrome import ChromeDriverManager
wd = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=chrome_options)
for name in pure_names:
    try:
        information = wikipedia.summary(name, auto_suggest= False)
    except:
        try:
            information = wikipedia.summary(name+ " (actor)", auto_suggest= True)
        except:
            continue
    try:
        birthDate = information.split("born")[1].split(")")[0]
    except:
        birthDate = "NaN"
    df2 = pd.DataFrame([[name, birthDate,  search_and_download(wd, name, 'chromedriver.exe', number_images=1), information]], columns = column_names)
    df = pd.concat([df2, df])

    i = i+1
    print("Progress: %" + "{:06.2f}".format((i/len(pure_names)*100)))

df.to_csv('person_row.csv', mode='a', header=False, index=False)


import torch
import torch.nn as nn
import torch.nn.functional as F
from network.classification import Classification, Classification2
import torchvision.models as models

class ToySynNet_Pretrained_ResNet(nn.Module):
    def __init__(self, clip_length):
        super().__init__()
        self.clip_length = clip_length

        self.conv1 = nn.Conv2d(3, 6, 3, 1)
        self.conv2 = nn.Conv2d(6, 16, 3, 1)
        # self.fc1 = nn.Linear(54*54*16, 120)
        self.fc1 = nn.Linear(198 * 198 * 16, 120)
        self.fc2 = nn.Linear(120, 84)

        # self.classification1 = Classification(self.clip_length)
        self.classification2 = Classification2(self.clip_length)
        # self.conv_encoder = ConvEncoder()

    def forward(self, clip1, clip2):
        # clip1 = self.conv_encoder(clip1)
        # clip2 = self.conv_encoder(clip2)
        clip1 = F.relu(self.conv1(clip1)) # [10, 3, 800, 800]
        clip2 = F.relu(self.conv1(clip2))

        clip1 = F.max_pool2d(clip1, 2, 2)
        clip2 = F.max_pool2d(clip2, 2, 2)

        clip1 = F.relu(self.conv2(clip1))
        clip2 = F.relu(self.conv2(clip2))

        clip1 = F.max_pool2d(clip1, 2, 2)
        clip2 = F.max_pool2d(clip2, 2, 2)

        # clip1 = clip1.view(-1, 54*54*16)
        # clip2 = clip2.view(-1, 54*54*16)
        clip1 = clip1.view(-1, 198 * 198 * 16)
        clip2 = clip2.view(-1, 198 * 198 * 16)

        clip1 = F.relu(self.fc1(clip1))
        clip2 = F.relu(self.fc1(clip2))

        clip1 = F.relu(self.fc2(clip1))
        clip2 = F.relu(self.fc2(clip2))  # torch.Size([10, 84])

        # print('classification result: ', self.classification(clip1, clip2))
        # start_time1 = time.time()
        # classification1 = self.classification1(clip1, clip2)
        # end_time1 = time.time()
        # print('1111111 spent time: ', end_time1 - start_time1)
        # print('classification1111: ', classification1)

        # start_time2 = time.time()
        classification2 = self.classification2(clip1, clip2)
        # end_time2 = time.time()
        # print('2222222 spent time: ', end_time2 - start_time2)
        # print('classification2222: ', classification2)

        return classification2

class ToySynNet(nn.Module):
    def __init__(self, clip_length):
        super().__init__()
        self.clip_length = clip_length

        self.conv1 = nn.Conv2d(3, 6, 3, 1)
        self.conv2 = nn.Conv2d(6, 16, 3, 1)
        # self.fc1 = nn.Linear(54*54*16, 120)
        self.fc1 = nn.Linear(198 * 198 * 16, 120)
        self.fc2 = nn.Linear(120, 84)

        # self.classification1 = Classification(self.clip_length)
        self.classification2 = Classification2(self.clip_length)
        # self.conv_encoder = ConvEncoder()

    def encoder(self, frame):
        frame = F.relu(self.conv1(frame))
        frame = F.max_pool2d(frame, 2, 2)
        frame = F.relu(self.conv2(frame))
        frame = F.max_pool2d(frame, 2, 2)
        frame = frame.view(-1, 198 * 198 * 16)
        frame = F.relu(self.fc1(frame))
        frame = F.relu(self.fc2(frame))
        return frame

    def forward(self, clip1, clip2):
        clip1 = torch.stack([self.encoder(torch.unsqueeze(frame, 0)) for frame in clip1], dim=0)
        clip2 = torch.stack([self.encoder(torch.unsqueeze(frame, 0)) for frame in clip2], dim=0)
        clip1 = torch.squeeze(clip1)
        clip2 = torch.squeeze(clip2)

        classification2 = self.classification2(clip1, clip2)

        return classification2

class ToySynNetOnlyClassification(nn.Module):
    def __init__(self, clip_length):
        super().__init__()
        self.clip_length = clip_length
        self.classification = Classification2(clip_length)

    def forward(self, clip1, clip2):
        classification = self.classification(clip1, clip2)
        return classification


import json
import logging
import os

from django.core import serializers
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.db.models import Model

from kolibri.core.auth.models import FacilityUser
from kolibri.core.auth.serializers import FacilityUserSerializer
from kolibri.core.device.models import DevicePermissions

logger = logging.getLogger(__name__)

class Command(BaseCommand):
    help = "To allow administrators to comply with GDPR requests, this command dumps all associated data for a user to several files."

    def add_arguments(self, parser):
        parser.add_argument(
            "username", action="store", type=str, help="Username of user for data dump"
        )

    def handle(self, *args, **options):
        try:
            user = FacilityUser.objects.get(username=options["username"])
        except FacilityUser.DoesNotExist:
            raise CommandError(
                "User with username `{username}` does not exist.".format(
                    username=options["username"]
                )
            )

        # create username directory to hold associated files
        cwd = os.getcwd()
        directory_location = os.path.join(cwd, user.username)
        if not os.path.isdir(directory_location):
            os.makedirs(directory_location)

        # write basic user data to file
        file_name = "{user}.txt".format(user=user.username)
        file_location = os.path.join(directory_location, file_name)
        data = FacilityUserSerializer(user).data
        logger.info("Writing user data to {file}...".format(file=file_location))
        with open(file_location, "w") as outfile:
            json.dump(data, outfile, sort_keys=True, indent=4)

        # get related managers for user model
        managers = []
        for related_object in user._meta.related_objects:
            try:
                managers.append(getattr(user, related_object.get_accessor_name()))
            # regular users do not have device permissions
            except (DevicePermissions.DoesNotExist,):
                pass

        # write data for each model to a file
        for manager in managers:
            # currently accounts for one-to-one field on DevicePermissions
            if isinstance(manager, Model):
                file_name = "{model}.txt".format(
                    model=manager.__class__.__name__.lower()
                )
                models = [manager]
            else:
                file_name = "{model}.txt".format(model=manager.model.__name__.lower())
                models = manager.all()
            file_location = os.path.join(directory_location, file_name)
            # only create file if models exist
            if models:
                data = serializers.serialize("json", models, indent=4)
                logger.info("Writing data to {file}...".format(file=file_location))
                with open(file_location, "w") as outfile:
                    outfile.write(data)


# type: noqa
from copy import copy
from dataclasses import asdict
from datetime import datetime
import functools
import logging
import time
from typing import (
    Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple,
    Type, TypeVar, Union, cast, overload
)

F = TypeVar("F", bound=Callable[..., Any])

class DictConversionError(Exception):
    pass

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)

def generate_timestamp() -> str:
    return datetime.utcnow().isoformat(timespec='microseconds') + 'Z'

def pluck(obj, *args) -> Tuple[Optional[Any], ...]:
    return tuple([obj[key] if key in obj else None for key in args])

def filter_empty_properties(mapping: Mapping[Any, Any]) -> Dict[Any, Any]:
    return {k: v for k, v in mapping.items() if v}

def response_asdict(fn=None):
    @functools.wraps(fn)
    def _wrapper(*args, **kwargs):
        result = fn(*args, **kwargs)
        result = convert_to_dict(result)
        return result
    return _wrapper

def isnamedtupleinstance(x) -> bool:
    t = type(x)
    b = t.__bases__
    if (len(b) != 1
       or b[0] != tuple):
        return False
    f = getattr(t, '_fields', None)
    if not isinstance(f, tuple):
        return False
    return all(type(n) == str for n in f)

def is_dataclass_instance(obj):
    return hasattr(type(obj), '__dataclass_fields__')

def convert_to_dict(data) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
    if data is None:
        return data
    if isinstance(data, dict):
        return cast(Dict[str, Any], dict(data))
    if is_dataclass_instance(data):
        return asdict(data)
    if isnamedtupleinstance(data):
        return cast(NamedTuple, data)._asdict()
    if isinstance(data, list) or isinstance(data, tuple):
        return cast(List[Dict[str, Any]], [convert_to_dict(item) for item in data])
    raise DictConversionError(f'Failed to convert to dictionary: {data}')

@overload
def retry(_func: F) -> F:
    ...

@overload
def retry(*, max_retries: int = 3, delay_seconds: int = 5,
          allowable_exceptions: List[Type[Exception]] = []) -> Callable[[F], F]:
    ...

def retry(fn=None, *, max_retries=3, delay_seconds=5, allowable_exceptions: List[Type[Exception]] = []):

    @functools.wraps(fn)
    def retry_decorator(_fn):

        @functools.wraps(_fn)
        def _wrapper(*args, **kwargs):
            num_tries = 1
            while True:
                try:
                    return _fn(*args, **kwargs)
                except Exception as ex:
                    if num_tries >= max_retries:
                        raise ex
                    if not allowable_exceptions or any([exc == type(ex) for exc in allowable_exceptions]):
                        time.sleep(delay_seconds)
                        LOGGER.warning(f'Calling {_fn.__name__}, Number of tries: {num_tries}')
                        LOGGER.warning(f'{ex}')
                        num_tries += 1
                        continue
                    raise ex

        return _wrapper

    if fn:
        return retry_decorator(fn)
    else:
        return retry_decorator

def walk_keys(mappings: Union[Mapping[str, Any], List[Mapping[str, Any]]],
              apply_fn: Callable[[str, Any], Optional[Tuple[Optional[str], Any]]]
              ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
    if isinstance(mappings, Mapping):
        new_mappings = {}
        for key, value in mappings.items():
            new_value = copy(value)
            item = apply_fn(key, new_value)
            if not item:
                continue

            next_key = item[0] if item[0] else key
            next_value = item[1] if item[1] else value
            if isinstance(next_value, Mapping) or isinstance(next_value, List):
                new_mappings[next_key] = walk_keys(next_value, apply_fn)
            else:
                new_mappings[next_key] = next_value

        return new_mappings

    elif isinstance(mappings, List):
        new_mappings = []
        for value in mappings:
            new_mappings.append(walk_keys(value, apply_fn))

        return new_mappings

    return mappings


# -*- coding: utf-8 -*-
import imp
import mmap
import sys

from toolz import memoize

from .discovery import find_transforms

class TransformFinderLoader(object):

    def find_module(self, fullname, path=None):
        self.fullname = fullname
        self.path = path

        try:
            self.found = imp.find_module(fullname.rpartition('.')[-1], path)
            (
                self.file,
                self.pathname,
                (self.suffix, self.mode, self.type)
            ) = self.found
        except Exception:
            return None

        if self.type in (
            imp.C_BUILTIN,
            imp.C_EXTENSION,
            imp.PY_COMPILED,
            imp.PY_FROZEN,
        ):
            return None

        if not self.file:
            return None

        if self.mode and (
            not self.mode.startswith(('r', 'U'))
            or '+' in self.mode
        ):
            return None

        triggered_transforms = self.get_triggered_transforms()

        if triggered_transforms:
            self.triggered_transforms = triggered_transforms
            return self

        return None

    def get_kwargs(self):
        return {
            key: getattr(self, key)
            for key in (
                'fullname', 'path', 'file', 'pathname', 'suffix',
                'mode', 'type', 'found', 'mmaped_file',
            )
        }

    def get_triggered_transforms(self):
        self.mmaped_file = mmap.mmap(
            self.file.fileno(),
            0,
            prot=mmap.PROT_READ
        )

        triggered_transforms = set()
        for transform in self.transforms:
            self.file.seek(0)
            self.mmaped_file.seek(0)

            if transform.trigger(**self.get_kwargs()):
                triggered_transforms.add(transform)

        self.mmaped_file.close()

        return triggered_transforms

    @property
    @memoize
    def transforms(self):
        return find_transforms()

    def is_package(self, name):
        if not name == self.fullname:
            raise ImportError('package name does not match')
        return self.type == imp.PKG_DIRECTORY

    def get_code(self, name):
        if not name == self.fullname:
            raise ImportError('package name does not match')

        data = self.file.read()

        for transform in self.triggered_transforms:
            data = transform.transform(data, **self.get_kwargs())

        code = compile(data, self.pathname, 'exec')
        return code

    def load_module(self, fullname):
        try:
            return sys.modules[fullname]
        except KeyError:
            pass

        try:
            module = imp.new_module(fullname)
            sys.modules[fullname] = module
            module.__file__ = self.pathname
            module.__loader__ = self

            if self.type == imp.PKG_DIRECTORY:
                module.__path__ = [self.pathname]
                module.__package__ = fullname
            elif self.type == imp.PY_SOURCE:
                module.__package__ = fullname.rpartition('.')[0]

            exec(self.get_code(self.fullname), module.__dict__)

        except Exception:
            if fullname in sys.modules:
                del sys.modules[fullname]
            raise

        return module

TRANSFORM_FINDER_LOADER = TransformFinderLoader()

def setup_meta_path():
    if TRANSFORM_FINDER_LOADER not in sys.meta_path:
        sys.meta_path.insert(0, TRANSFORM_FINDER_LOADER)


# 동적 페이지 : 스크롤을 넘길 때 마다 로딩되어 아래에 데이터가 나타남
# 현재, 구글 무비에서는 화살표 버튼 클릭을 통해 다른 영화 정보 확인 가능하도록

from selenium import  webdriver
browser = webdriver.Chrome('./chromedriver')
browser.maximize_window()

# 페이지 이동
url = 'https://play.google.com/store/movies'
browser.get(url)

# 지정한 위치로 스크롤 내리기 -> 넘기기
# 모니터(해상도) 높이인 1080 위치로 스크롤 내리기
# browser.execute_script("window.scrollTo(0, 2080)")

# 화면 가장 아래로 스크롤 내리기 -> 현재 문서의 총 높이만큼
browser.execute_script("window.scrollTo(0, document.body.scrollHeight)")

# 높이가 변하지 않을 때까지
import time
interval = 2   # 2초에 한 번씩 스크롤 내림

# 현재 문서 높이를 가져와 저장
prevHeight = browser.execute_script("return document.body.scrollHeight")

# 반복 수행
while True:
    # 스크롤을 가장 아래로 내리기
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight)")

    # 페이지 로딩 대기
    time.sleep(interval)

    # 현재 문서 높이를 가져와서 저장
    currnetHeight = browser.execute_script("return document.body.scrollHeight")

    if currnetHeight == prevHeight:
        break

    prevHeight = currnetHeight

print('스크롤 완료')

import requests
from bs4 import BeautifulSoup

soup = BeautifulSoup(browser.page_source, 'lxml')

movies = soup.find_all('div', attrs = {'class' : 'ULeU3b neq64b'})
# print(len(movies))

for movie in movies:
    title = movie.find('div', attrs= {'class' : 'Epkrse'})
    if title != None:
        title = title.get_text()
    else:
        continue

    # 할인 전 가격
    originPrice = movie.find('span', attrs={'class': 'SUZt4c P8AFK'})
    if originPrice:
        originPrice = originPrice.get_text()
    else:
        # print(title, ' < 할인되지 않은 영화 제외 > ')
        continue

    # 할인된 가격
    price = movie.find('span', attrs={'class': 'VfPpfd VixbEe'}).get_text()

    # 링크
    link = movie.find('a', attrs={'class':'Si6A0c ZD8Cqc'})['href']
    # 올바른 링크 : 'https://play.google.com + link
    print(f'제목 : {title}')
    print(f'할인 전 가격 : {originPrice}')
    print(f'할인 후 가격 : {price}')
    print('링크 : ', 'https://play.google.com' + link)
    print('-' * 100)

browser.quit()
# 다른 로직에 의해 페이지 로딩하는 경우 존재 class 다른 경우 존재
# movies = soup.find_all('div', attrs={'class' : [ 'ImZGtf mpg5gc', 'Vpfmgd' ]})


from functools import reduce

with open('input') as f:
    chal_input = [l.rstrip() for l in f.readlines()]

maxx = len(chal_input[0])
maxy = len(chal_input)

forest = {(x, y): char for y, row in enumerate(chal_input) for x, char in enumerate(row)}
trees = 0

slope_map = [
    {'o': 1+1j, 'p': 0, 't': 0},
    {'o': 3+1j, 'p': 0, 't': 0},
    {'o': 5+1j, 'p': 0, 't': 0},
    {'o': 7+1j, 'p': 0, 't': 0},
    {'o': 1+2j, 'p': 0, 't': 0}
]

for y in range(maxy):
    for slope in slope_map:
        p = (slope['p'].real % maxx, slope['p'].imag)
        if p in forest and forest[p] == '#': slope['t'] += 1
        slope['p'] += slope['o']

print('Part 1:', slope_map[1]['t'])
print('Part 2:', reduce(lambda c, n: c*n['t'], slope_map, 1))


import sys
sys.path.append('../backup_lhcscrub_python/LHCMeasurementTools')
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as sio
import argparse

import myfilemanager as mlo
import mystyle as ms
from d000_analysis.LHC_Heatloads import magnet_length
from RcParams import init_pyplot
init_pyplot()

parser = argparse.ArgumentParser(description='Show bunch by bunch power losses from PyECLOUD simulations and measurements.')
parser.add_argument('-q', help='Quadrupole SEY', metavar='Quad SEY', type=float)
parser.add_argument('-d', help='Drift SEY', metavar='Drift SEY', type=float)
parser.add_argument('beam', help='B1 | B2', type=str, metavar='BEAM')
args = parser.parse_args()

if args.beam != 'B1' and args.beam != 'B2':
    raise ValueError('Incorrect specification of beam!')

assumed_quad_sey = args.q
assumed_drift_sey = args.d

T_rev = 88.9e-6
qe = 1.60217657e-19

main_folder = './all_data'
beam_snapshot_folder = './beam_snapshots'

machine_name = 'LHC'
devices = ['Drift', 'ArcDipReal', 'ArcQuadReal']
device_name = 'ArcDipReal'
beam_name = '6500GeV'

coast_strs = ['1.0', '0.5', '0.0']
coast_linestyle_dict = {\
        '1.0': '-',
        '0.5': '-.',
        '0.0': ':'
        }

l_hc = magnet_length['AVG_ARC'][0]
l_dip = 3 * magnet_length['special_HC_D2'][0]
l_quad = magnet_length['special_HC_Q1'][0]
l_drift = l_hc - l_dip - l_quad

#beam_snapshot = 'Fill5219_cut0.920h_450GeV_for_triplets_B1.mat'
beam_snapshot = 'Fill5219_cut1.800h_6500GeV_for_triplets_%s.mat' % args.beam

ob_snapshot = mlo.myloadmat_to_obj(beam_snapshot_folder+'/'+beam_snapshot)
meas_loss_per_meter = ob_snapshot.bunch_power_loss/23./2./8./l_hc
meas_loss_per_hc = meas_loss_per_meter * l_hc

sey_vect = np.arange(1.10,1.501,.05)

if 'B1' in beam_snapshot:
    colorbeam = 'b'
elif 'B2' in beam_snapshot:
    colorbeam = 'r'
else:
    raise ValueError('Beam type (B1 or B2) could not be infered from the name of the snapshot')

N_sims = len(sey_vect)

def get_simulation_ob(device_name, sey, coast_str='0.5'):

    sim_ident = '%s_%s_%s_%s_sey%.2f_coast%s' % (beam_snapshot.split('.mat')[0], machine_name, device_name, beam_name, sey, coast_str)
    ob = mlo.myloadmat_to_obj(main_folder + '/' + sim_ident+'/Pyecltest.mat')
    t_bun = np.arange(0.,np.max(ob.t),  ob.b_spac)
    L_imp_t = np.cumsum(ob.En_imp_eV_time-ob.En_emit_eV_time); #in eV
    L_imp = np.interp(t_bun, ob.t, L_imp_t) #in eV
    Ek = np.interp(t_bun, ob.t, ob.En_kin_eV_time);
    sim_loss_per_meter = (np.diff(L_imp) + np.diff(Ek))*qe/T_rev; #watt/m

    return sim_loss_per_meter, ob, t_bun

quad_contribution_per_meter, quad_ob,t_bun = get_simulation_ob('ArcQuadReal', assumed_quad_sey)
drift_contribution_per_meter, drift_ob,t_bun = get_simulation_ob('Drift', assumed_drift_sey)

plt.close('all')
fig = plt.figure()
title_str = 'Comparison of measured to simulated bunch by bunch power losses.'
fig.canvas.set_window_title(title_str)
title_str += '\nAssumed Quad/Drift SEY: %.2f/%.2f' % (assumed_quad_sey,assumed_drift_sey)
plt.suptitle(title_str,fontsize=25)
sp0 = plt.subplot(3,1,1)
sp1 = plt.subplot(3,1,2, sharex=sp0)

# plot simulation data
for coast_ctr, coast_str in enumerate(coast_strs):
    for sey_ctr, sey in enumerate(sey_vect):
        #print beam_snapshot, device_name, sey

        try:
            dip_contribution_per_meter, ob, t_bun = get_simulation_ob('ArcDipReal', sey, coast_str)
            sim_loss_per_hc = dip_contribution_per_meter * l_dip + quad_contribution_per_meter * l_quad + drift_contribution_per_meter * l_drift

            if sey_ctr == 0:
                label = coast_str + ' e9 coasting'
            else:
                label = None
            color_curr = ms.colorprog(sey_ctr, N_sims)

            sp1.semilogy(ob.t/25e-9, ob.Nel_timep, color=color_curr, label=label, ls=coast_linestyle_dict[coast_str])

            if coast_ctr == 0:
                label = sey
            else:
                label = None

            sp2 = plt.subplot(3,1,3, sharex=sp1)
            ms.sciy()
            sp2.plot(t_bun[:-1]/25e-9, sim_loss_per_hc, '.-', color=color_curr, label=label, ls=coast_linestyle_dict[coast_str])

        except IOError as err:
            print('Got:', err)

sp0.plot(ob_snapshot.ppb_vect, color=colorbeam,label='Measured')
sp0.set_ylabel('Bunch intensity [p$^+$]')
sp0.set_xlim(0,3500)

sp1.set_ylabel('Electrons [1/m]')

sp2.plot(meas_loss_per_hc, '.-', color='k', label='Measured')
sp2.set_ylabel('Power loss [W/hc]')
sp2.set_xlabel('25 ns slot')

sp1.legend(bbox_to_anchor=(1, 1.02),  loc='upper left')
sp2.legend(bbox_to_anchor=(1, 1.02),  loc='upper left')

# Figure for devices only
fig = plt.figure()
title_str = 'Simulated bunch by bunch power losses for different devices.'
fig.canvas.set_window_title(title_str)
plt.suptitle(title_str,fontsize=25)

sp = None
for dev_ctr, device in enumerate(devices):
    sp_ctr = dev_ctr +1
    sp = plt.subplot(3,1,sp_ctr, sharex=sp)
    sp.set_title(device)
    ms.sciy()
    sp.set_ylabel('Power loss [W/m]')
    if sp_ctr == 3:
        sp.set_xlabel('25 ns slot')

    for coast_ctr, coast_str in enumerate(coast_strs):
       for sey_ctr, sey in enumerate(sey_vect):
           try:
               contribution_per_meter, ob, t_bun = get_simulation_ob(device, sey, coast_str)

               color_curr = ms.colorprog(sey_ctr, N_sims)
               if sp_ctr == 1 and coast_ctr == 0:
                   label = sey
               elif sp_ctr == 2 and sey_ctr == 0:
                   label = coast_str + ' e9 coasting'
               else:
                   label = None

               sp.plot(t_bun[:-1]/25e-9, contribution_per_meter, '.-', color=color_curr, label=label, ls=coast_linestyle_dict[coast_str])

           except IOError as err:
                print('Got:', err)

    if sp_ctr == 1 or sp_ctr == 2:
        sp.legend(bbox_to_anchor=(1, 1.02),  loc='upper left')

plt.show()


from flask import Blueprint, render_template, request, flash, jsonify, url_for, redirect, send_from_directory, current_app
from flask_login import login_required, current_user
from models import Note, Restaurant
from . import db, generate_text, generate_image, transcribe_audio, generate_speech, generate_chaos, your_friend_snoopy
import json
import time
import os
import random

views = Blueprint('views', __name__)

@views.route('/', methods=['GET', 'POST'])
@login_required
def home():
    images = ['1.png', '2.png', '3.png', '4.png', '5.png', '6.png', '7.png', '8.png', '9.png']
    if request.method == 'POST':
        note = request.form.get('note')
        if len(note) < 1:
            flash('Note is too short!', category='error')
        else:
            new_note = Note(data=note, user_id=current_user.id)
            db.session.add(new_note)
            db.session.commit()
            flash('Note added!', category='success')

    return render_template("home.html", user=current_user, images=images)

@views.route('/chaos', methods=['GET', 'POST'])
@login_required
def chaos():
    ends = ["Explosion","Pandemic","Invasion","Apocalypse","Cataclysm","Annihilation","Extinction","Obliteration","Destruction","Collapse"]
    if request.method == 'POST':
        text = request.form.get("text")
        if text:
            response = generate_chaos(text)

        image = generate_image(f"Realistic, Cinematic, 8k, Real Textures, {response}")
        final_text = generate_text(f"Generate a short back story of this character: {response} and the end of the story should be about {random.choice(ends)}")
        audio = generate_speech(final_text)
        return render_template("chaos.html", user=current_user, image=image, text=final_text, audio=audio )
    else:
        return render_template("chaos.html", user=current_user, image=None, text=None, audio=None)

@views.route('/texttos', methods=['GET', 'POST'])
def texttos():
    if request.method == 'POST':
        text = request.form.get("text")
        response = None
        if text:
            file_name = generate_speech(text)
            response =  f"static/audio_files/{file_name}"
        return render_template("texttos.html", user=current_user, response=response)
    else:
        return render_template("texttos.html", user=current_user, response=None)

@views.route('/davince', methods=['GET', 'POST'])
@login_required
def index():
    text = request.form.get("text")
    if text:
        response = generate_text(text)
    else:
        response = None
    return render_template("davince.html", user=current_user, response=response )

@views.route('/future', methods=['GET', 'POST'])
@login_required
def future():
    text = request.form.get("text")
    if text:
        response = your_friend_snoopy(text)
    else:
        response = None
    return render_template("future.html", user=current_user, response=response )

@views.route('/dalle', methods=['GET', 'POST'])
@login_required
def dalle():
    text = request.form.get("text")
    if text:
        response = generate_image(text)
    else:
        response = None
    return render_template("dalle.html", user=current_user, response=response, text=text)

@views.route("/rev", methods=['GET','POST'])
@login_required
def upload_file():
    if request.method == 'POST':
        f = request.files['file']
        transcript = transcribe_audio(f)
    else:
        transcript = None
    return render_template("rev.html", user=current_user, transcript=transcript)

@views.route('/delete-note', methods=['POST'])
def delete_note():
    note = json.loads(request.data)
    noteId = note['noteId']
    note = Note.query.get(noteId)
    if note:
        if note.user_id == current_user.id:
            db.session.delete(note)
            db.session.commit()

    return jsonify({})

@views.route('/restaurant/add', methods=['GET', 'POST'])
@login_required
def add_restaurant():
    form = RestaurantForm()
    if form.validate_on_submit():
        restaurant = Restaurant(name=form.name.data, description=form.description.data, rating=form.rating.data)
        db.session.add(restaurant)
        db.session.commit()
        flash('Restaurant added successfully', 'success')
        return redirect(url_for('dashboard'))
    return render_template('add_restaurant.html', form=form, user=current_user)

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, FloatField
from wtforms.validators import DataRequired

class RestaurantForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    description = TextAreaField('Description', validators=[DataRequired()])
    rating = FloatField('Rating', validators=[DataRequired()])


import argparse
import shlex

from primaires.interpreteur.masque.parametre import Parametre

class PrmEditer(Parametre):

    """Commande 'navire éditer'."""

    def __init__(self):
        """Constructeur de la commande"""
        Parametre.__init__(self, "éditer", "edit")
        self.schema = "<texte_libre>"
        self.aide_courte = "ouvre l'éditeur de modèle de navires"
        self.aide_longue = \
            "Cette commande ouvre l'éditeur de prototype de navire. " \
            "Le terme modèle est également utilisé. Vous devez préciser " \
            "en paramètre la clé du modèle (par exemple |cmd|voilier|ff|). " \
            "Si le modèle n'existe pas, vous devrez préciser l'option " \
            "|cmd|--creer|ff| dans la commande (par exemple %navire% " \
            "%navire:éditer%|cmd| --creer voilier|ff|)."

    def interpreter(self, personnage, dic_masques):
        """Méthode d'interprétation de commande"""
        def n_exit(code, msg):
            """Ne quitte pas Python."""
            raise ValueError(msg)
        options = dic_masques["texte_libre"].texte

        # Création de l'interpréteur d'option
        parser = argparse.ArgumentParser()
        parser.exit = n_exit
        parser.add_argument("cle")
        parser.add_argument("--creer", action="store_true")

        try:
            args = parser.parse_args(shlex.split(options))
        except ValueError as err:
            personnage << "|err|Les options n'ont pas été interprétées " \
                    "correctement : {}.|ff|".format(err)
            return

        cle = args.cle
        if cle in importeur.navigation.modeles:
            modele = type(self).importeur.navigation.modeles[cle]
        elif not args.creer:
            personnage << "|err|Ce modèle n'existe pas. Utilisez " \
                    "l'option |cmd|--creer|ff||err|.|ff|"
            return
        else:
            modele = importeur.navigation.creer_modele(cle)

        editeur = type(self).importeur.interpreteur.construire_editeur(
                "shedit", personnage, modele)
        personnage.contextes.ajouter(editeur)
        editeur.actualiser()


from django.urls import path
from .views import *

urlpatterns = [
    path('', userLogin, name='login'),
    path('signup', userSignup, name='signup'),
    path('userpage', userPage, name='userpage'),
    path('userpage/<int:id>', deleteUser, name='userpage'),
    path('edit/<int:id>', editUser, name='editUser'),
    path('logout', logout, name='logout'),

]


import logging
from aio_pika import IncomingMessage, Message, connect_robust as pika_connect
from fastapi import Body, HTTPException

exchange_name = 'teste'
routing_key = 'teste'
logger = logging.getLogger('uvicorn')

async def publish_message(msg: IncomingMessage = Body(...)):
    try:
        connection = await pika_connect("amqp://guest:guest@localhost:5672/teste")
        async with connection:
            channel = await connection.channel()
            queue = await channel.declare_queue("teste", durable=True)
            await channel.declare_exchange(name=exchange_name, type='topic', durable=True)
            await channel.default_exchange.publish(Message(bytes(msg,'UTF-8')),routing_key=queue.name,)
            logger.info("%r sent to exchange %r with data: %r" % (routing_key, exchange_name, msg))
    except Exception as ex:
        logger.error("Problema para postar a mensagem %r" % (ex))
        raise HTTPException(
            status_code=400,
            detail="Problema para postar a mensagem: " + ex,)


import logging
from itertools import chain

import numpy as np
from scipy.stats import halfnorm

from hyperspy.external.progressbar import progressbar
from hyperspy.misc.math_tools import check_random_state

_logger = logging.getLogger(__name__)

def _thresh(X, lambda1, vmax):
    """Soft-thresholding with clipping."""
    res = abs(X) - lambda1
    np.maximum(res, 0.0, out=res)
    res *= np.sign(X)
    np.clip(res, -vmax, vmax, out=res)
    return res

def _mrdivide(B, A):
    """Solves xB = A as per Matlab."""
    if isinstance(B, np.ndarray):
        if len(B.shape) == 2 and B.shape[0] == B.shape[1]:
            # square array
            return np.linalg.solve(A.T, B.T).T
        else:
            # Set rcond default value to match numpy 1.14 default value with
            # previous numpy version
            rcond = np.finfo(float).eps * max(A.shape)
            return np.linalg.lstsq(A.T, B.T, rcond=rcond)[0].T
    else:
        return B / A

def _project(W):
    newW = W.copy()
    np.maximum(newW, 0, out=newW)
    sumsq = np.sqrt(np.sum(W ** 2, axis=0))
    np.maximum(sumsq, 1, out=sumsq)
    return _mrdivide(newW, np.diag(sumsq))

def _solveproj(v, W, lambda1, kappa=1, h=None, e=None, vmax=None):
    m, n = W.shape
    v = v.T
    if vmax is None:
        vmax = v.max()

    if len(v.shape) == 2:
        batch_size = v.shape[1]
        eshape = (m, batch_size)
        hshape = (n, batch_size)
    else:
        eshape = (m,)
        hshape = (n,)
    if h is None or h.shape != hshape:
        h = np.zeros(hshape)
    if e is None or e.shape != eshape:
        e = np.zeros(eshape)

    eta = kappa / np.linalg.norm(W, "fro") ** 2

    maxiter = 1e6
    iters = 0

    while True:
        iters += 1
        # Solve for h
        htmp = h
        h = h - eta * W.T @ (W @ h + e - v)
        np.maximum(h, 0.0, out=h)

        # Solve for e
        etmp = e
        e = _thresh(v - W @ h, lambda1, vmax)

        # Stop conditions
        stoph = np.linalg.norm(h - htmp, 2)
        stope = np.linalg.norm(e - etmp, 2)
        stop = max(stoph, stope) / m
        if stop < 1e-5 or iters > maxiter:
            break

    return h, e

class ORNMF:
    """Performs Online Robust NMF with missing or corrupted data.

    The ORNMF code is based on a transcription of the online proximal gradient
    descent (PGD) algorithm MATLAB code obtained from the authors of [Zhao2016]_.
    It has been updated to also include L2-normalization cost function that
    is able to deal with sparse corruptions and/or outliers slightly faster
    (please see ORPCA implementation for details). A further modification
    has been made to allow for a changing subspace W, where X ~= WH^T + E
    in the ORNMF framework.

    Read more in the :ref:`User Guide <mva.rnmf>`.

    References
    ----------
    .. [Zhao2016] Zhao, Renbo, and Vincent YF Tan. "Online nonnegative matrix
        factorization with outliers." Acoustics, Speech and Signal Processing
        (ICASSP), 2016 IEEE International Conference on. IEEE, 2016.

    """

    def __init__(
        self,
        rank,
        store_error=False,
        lambda1=1.0,
        kappa=1.0,
        method="PGD",
        subspace_learning_rate=1.0,
        subspace_momentum=0.5,
        random_state=None,
    ):
        """Creates Online Robust NMF instance that can learn a representation.

        Parameters
        ----------
        rank : int
            The rank of the representation (number of components/factors)
        store_error : bool, default False
            If True, stores the sparse error matrix.
        lambda1 : float
            Nuclear norm regularization parameter.
        kappa : float
            Step-size for projection solver.
        method : {'PGD', 'RobustPGD', 'MomentumSGD'}, default 'PGD'
            * 'PGD' - Proximal gradient descent
            * 'RobustPGD' - Robust proximal gradient descent
            * 'MomentumSGD' - Stochastic gradient descent with momentum
        subspace_learning_rate : float
            Learning rate for the 'MomentumSGD' method. Should be a
            float > 0.0
        subspace_momentum : float
            Momentum parameter for 'MomentumSGD' method, should be
            a float between 0 and 1.
        random_state : None or int or RandomState instance, default None
            Used to initialize the subspace on the first iteration.

        """
        self.n_features = None
        self.iterating = False
        self.t = 0

        if store_error:
            self.E = []
        else:
            self.E = None

        self.rank = rank
        self.robust = False
        self.subspace_tracking = False
        self.lambda1 = lambda1
        self.kappa = kappa
        self.subspace_learning_rate = subspace_learning_rate
        self.subspace_momentum = subspace_momentum
        self.random_state = check_random_state(random_state)

        # Check options are valid
        if method not in ("PGD", "RobustPGD", "MomentumSGD"):
            raise ValueError("'method' not recognised")

        if method == "RobustPGD":
            self.robust = True

        if method == "MomentumSGD":
            self.subspace_tracking = True
            if subspace_momentum < 0.0 or subspace_momentum > 1:
                raise ValueError("'subspace_momentum' must be a float between 0 and 1")

    def _setup(self, X):
        self.h, self.e, self.v = None, None, None
        if isinstance(X, np.ndarray):
            n, m = X.shape
            avg = np.sqrt(X.mean() / m)
            iterating = False
        else:
            x = next(X)
            m = len(x)
            avg = np.sqrt(x.mean() / m)
            X = chain([x], X)
            iterating = True

        self.n_features = m
        self.iterating = iterating

        self.W = halfnorm.rvs(
            size=(self.n_features, self.rank), random_state=self.random_state
        )
        self.W = abs(avg * self.W / np.sqrt(self.rank))
        self.H = []

        if self.subspace_tracking:
            self.vnew = np.zeros_like(self.W)
        else:
            self.A = np.zeros((self.rank, self.rank))
            self.B = np.zeros((self.n_features, self.rank))

        return X

    def fit(self, X, batch_size=None):
        """Learn NMF components from the data.

        Parameters
        ----------
        X : {numpy.ndarray, iterator}
            [n_samples x n_features] matrix of observations
            or an iterator that yields samples, each with n_features elements.
        batch_size : {None, int}
            If not None, learn the data in batches, each of batch_size samples
            or less.

        """
        if self.n_features is None:
            X = self._setup(X)

        num = None
        prod = np.outer
        if batch_size is not None:
            if not isinstance(X, np.ndarray):
                raise ValueError("can't batch iterating data")
            else:
                prod = np.dot
                length = X.shape[0]
                num = max(length // batch_size, 1)
                X = np.array_split(X, num, axis=0)

        if isinstance(X, np.ndarray):
            num = X.shape[0]
            X = iter(X)

        h, e = self.h, self.e

        for v in progressbar(X, leave=False, total=num, disable=num == 1):
            h, e = _solveproj(v, self.W, self.lambda1, self.kappa, h=h, e=e)
            self.v = v
            self.e = e
            self.h = h
            self.H.append(h)
            if self.E is not None:
                self.E.append(e)

            self._solve_W(prod(h, h.T), prod((v.T - e), h.T))
            self.t += 1

        self.h = h
        self.e = e

    def _solve_W(self, A, B):
        if not self.subspace_tracking:
            self.A += A
            self.B += B
            eta = self.kappa / np.linalg.norm(self.A, "fro")

        if self.robust:
            # exactly as in the Zhao & Tan paper
            n = 0
            lasttwo = np.zeros(2)
            while n <= 2 or (
                abs((lasttwo[1] - lasttwo[0]) / lasttwo[0]) > 1e-5 and n < 1e9
            ):
                self.W -= eta * (self.W @ self.A - self.B)
                self.W = _project(self.W)
                n += 1
                lasttwo[0] = lasttwo[1]
                lasttwo[1] = 0.5 * np.trace(
                    self.W.T.dot(self.W).dot(self.A)
                ) - np.trace(self.W.T.dot(self.B))
        else:
            # Tom Furnival (@tjof2) approach
            # - copied from the ORPCA implementation
            #   of gradient descent in ./rpca.py
            if self.subspace_tracking:
                learn = self.subspace_learning_rate * (
                    1 + self.subspace_learning_rate * self.lambda1 * self.t
                )
                vold = self.subspace_momentum * self.vnew
                self.vnew = (self.W @ A - B) / learn
                self.W -= vold + self.vnew
            else:
                self.W -= eta * (self.W @ self.A - self.B)

            np.maximum(self.W, 0.0, out=self.W)
            self.W /= max(np.linalg.norm(self.W, "fro"), 1.0)

    def project(self, X, return_error=False):
        """Project the learnt components on the data.

        Parameters
        ----------
        X : {numpy.ndarray, iterator}
            [n_samples x n_features] matrix of observations
            or an iterator that yields n_samples, each with n_features elements.
        return_error : bool
            If True, returns the sparse error matrix as well. Otherwise only
            the weights (loadings)

        """
        H = []
        if return_error:
            E = []

        num = None
        if isinstance(X, np.ndarray):
            num = X.shape[0]
            X = iter(X)
        for v in progressbar(X, leave=False, total=num):
            h, e = _solveproj(v, self.W, self.lambda1, self.kappa, vmax=np.inf)
            H.append(h.copy())
            if return_error:
                E.append(e.copy())

        H = np.stack(H, axis=-1)
        if return_error:
            return H, np.stack(E, axis=-1)
        else:
            return H

    def finish(self):
        """Return the learnt factors and loadings."""
        if len(self.H) > 0:
            if len(self.H[0].shape) == 1:
                H = np.stack(self.H, axis=-1)
            else:
                H = np.concatenate(self.H, axis=1)
            return self.W, H
        else:
            return self.W, 1

def ornmf(
    X,
    rank,
    store_error=False,
    project=False,
    batch_size=None,
    lambda1=1.0,
    kappa=1.0,
    method="PGD",
    subspace_learning_rate=1.0,
    subspace_momentum=0.5,
    random_state=None,
):
    """Perform online, robust NMF on the data X.

    This is a wrapper function for the ORNMF class.

    Parameters
    ----------
    X : numpy array
        The [n_samples, n_features] input data.
    rank : int
        The rank of the representation (number of components/factors)
    store_error : bool, default False
        If True, stores the sparse error matrix.
    project : bool, default False
        If True, project the data X onto the learnt model.
    batch_size : {None, int}, default None
        If not None, learn the data in batches, each of batch_size samples
        or less.
    lambda1 : float
        Nuclear norm regularization parameter.
    kappa : float
        Step-size for projection solver.
    method : {'PGD', 'RobustPGD', 'MomentumSGD'}, default 'PGD'
        * 'PGD' - Proximal gradient descent
        * 'RobustPGD' - Robust proximal gradient descent
        * 'MomentumSGD' - Stochastic gradient descent with momentum
    subspace_learning_rate : float
        Learning rate for the 'MomentumSGD' method. Should be a
        float > 0.0
    subspace_momentum : float
        Momentum parameter for 'MomentumSGD' method, should be
        a float between 0 and 1.
    random_state : None or int or RandomState instance, default None
        Used to initialize the subspace on the first iteration.

    Returns
    -------
    Xhat : numpy array
        is the [n_features x n_samples] non-negative matrix
        Only returned if store_error is True.
    Ehat : numpy array
        is the [n_features x n_samples] sparse error matrix
        Only returned if store_error is True.
    W : numpy array, shape [n_features, rank]
        is the non-negative factors matrix
    H : numpy array, shape [rank, n_samples]
        is the non-negative loadings matrix

    """
    X = X.T

    _ornmf = ORNMF(
        rank,
        store_error=store_error,
        lambda1=lambda1,
        kappa=kappa,
        method=method,
        subspace_learning_rate=subspace_learning_rate,
        subspace_momentum=subspace_momentum,
        random_state=random_state,
    )
    _ornmf.fit(X, batch_size=batch_size)

    if project:
        W = _ornmf.W
        H = _ornmf.project(X)
    else:
        W, H = _ornmf.finish()

    if store_error:
        Xhat = W @ H
        Ehat = np.array(_ornmf.E).T

        return Xhat, Ehat, W, H
    else:
        return W, H