import pprint
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import tensorflow_ranking as tfr
import tensorflow_recommenders as tfrs
import collections

def _create_feature_dict():
  """Helper function for creating an empty feature dict for defaultdict."""
  return {"embeddings": [], "ranking": []}

def _sample_list(
    feature_lists,
    num_examples_per_list,
    random_state,
):
  """Function for sampling a list example from given feature lists."""
  if random_state is None:
    random_state = np.random.RandomState()

  sampled_indices = random_state.choice(
      range(len(feature_lists["embeddings"])),
      size=num_examples_per_list,
      replace=False,
  )
  sampled_embeddings = [
      feature_lists["embeddings"][idx] for idx in sampled_indices
  ]
  sampled_rankings = [
      feature_lists["ranking"][idx]
      for idx in sampled_indices
  ]

  return (
      sampled_embeddings,
      tf.concat(sampled_rankings, 0),
  )

def sample_listwise(
    ranking_dataset,
    num_list_per_cc,
    num_examples_per_list,
    seed,
):
  """Function for converting the rankings dataset to a listwise dataset.
  Args:
      ranking_dataset:
        The training dataset with [CC,embeddinga,rank] for the specified time period
      num_list_per_cc:
        An integer representing the number of lists that should be sampled for
        each cc in the training dataset.
      num_examples_per_list:
        An integer representing the number of store ranks to be sampled for each list
        from the list of stores ranked "by" the cc.  Like a user ranking movies.
      seed:
        An integer for creating `np.random.RandomState.
  Returns:
      A tf.data.Dataset containing list examples.
      Each example contains three keys: "cc_id", "embeddings", and
      "ranking". "cc_id" maps to a integer tensor that represents the
      cc_id for the example. "embeddings" maps to a tensor of shape
      [sum(num_example_per_list)] with dtype tf.Tensor. It represents the list
      of store,cc embedding descriptions. "ranking" maps to a tensor of shape
      [sum(num_example_per_list)] with dtype tf.float32. It represents the
      ranking of each store attached to the cc_id in the candidate list.
  """
  random_state = np.random.RandomState(seed)

  example_lists_by_cc = collections.defaultdict(_create_feature_dict)

  for example in ranking_dataset:
    user_id = example["cc_id"].numpy()
    example_lists_by_cc[user_id]["embeddings"].append(
        example["embeddings"])
    example_lists_by_cc[user_id]["ranking"].append(
        example["ranking"])

  tensor_slices = {"cc_id": [], "embeddings": [], "ranking": []}

  for cc_id, feature_lists in example_lists_by_cc.items():
    for _ in range(num_list_per_cc):

      # Drop the user if they don't have enough ratings.
      if len(feature_lists["embeddings"]) < num_examples_per_list:
        continue

      sampled_embeddings, sampled_rankings = _sample_list(
          feature_lists,
          num_examples_per_list,
          random_state=random_state,
      )
      tensor_slices["cc_id"].append(cc_id)
      tensor_slices["embeddings"].append(sampled_embeddings)
      tensor_slices["ranking"].append(sampled_rankings)

  return tf.data.Dataset.from_tensor_slices(tensor_slices)


import pytest
from django.urls import reverse
from mixer.backend.django import mixer

pytestmark = [pytest.mark.django_db]

def test_get_user_list(api_client):
    """Получение списка пользователей."""
    url = reverse('users')
    response = api_client.get(url)

    assert response.status_code == 200

def test_new_user_in_list(api_client):
    """Появление нового пользователя."""
    url = reverse('users')
    response = api_client.get(url)

    mixer.blend('users.User')
    new_response = api_client.get(url)

    assert response.content != new_response.content


# -*- coding:utf-8 -*-
from scrapy import Spider
from scrapy.selector import Selector

from qhpage.items import QhpageItem

class QhpageSpider(Spider):
    name = "qhpage"
    allowed_domains = ["stackoverflow.com"]
    start_urls = [
        "http://stackoverflow.com/questions?pagesize=50&sort=newest",
    ]
    def parse(self, response):
        questions = Selector(response).xpath('//div[@class="summary"]')

        for question in questions:
            item = QhpageItem()
            item['name'] = question.xpath('./h3/a[@class="question-hyperlink"]/text()').extract()[0]
            item['url'] = question.xpath('./h3/a[@class="question-hyperlink"]/@href').extract()[0]
            item['title'] = question.xpath('./div[@class="excerpt"]/text()').extract()[0]
            yield item


from fastapi import APIRouter, Depends, HTTPException, UploadFile
from sqlalchemy.orm import Session
from typing import List

from db.database import get_db
from security.auth import oauth2_scheme, get_current_user

from . import schemas, crud

router = APIRouter()

@router.post("/events/add")
async def add_event(text: str, image: UploadFile, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
    user = get_current_user(db, token)
    if not user.is_admin:
        raise HTTPException(status_code=400, detail="No permision")
    return crud.add_event(db, text, image)

@router.delete("/events/{event_id}/delete")
async def delete_event(event_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
    user = get_current_user(db, token)
    if not user.is_admin:
        raise HTTPException(status_code=400, detail="No permision")
    return crud.delete_event(db, event_id)

@router.get("/events", response_model=List[schemas.Events])
async def read_events(db: Session = Depends(get_db)):
    return crud.get_events(db)


"""
CartoonPhoto
Yotam Levit
Date: 13/11/2020
"""
import cv2

def read_image(image_name):
    return cv2.imread(image_name)

def get_edged(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    grey = cv2.medianBlur(gray, 5)
    edges = cv2.adaptiveThreshold(gray, 255,
                                  cv2.ADAPTIVE_THRESH_MEAN_C,
                                  cv2.THRESH_BINARY, 9, 9)
    return edges

def cartoonization(image, edges):
    color = cv2.bilateralFilter(image, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask=edges)
    return cartoon

def show_images(image, edges, cartoon):
    cv2.imshow("Image" ,image)
    cv2.imshow("edges", edges)
    cv2.imshow("Cartoon", cartoon)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def convert(image_name):
    image = read_image(image_name)
    edges = get_edged(image)
    cartoon = cartoonization(image, edges)
    show_images(image, edges, cartoon)

convert("dana4.png")


import abc
import collections
import copyreg
import enum
import functools
import inspect
import logging
import operator
import random
import string
import typing

from .. import _exception, _struct
from . import series

if typing.TYPE_CHECKING:
    from forml.io import dsl

LOGGER = logging.getLogger(__name__)

class Rows(typing.NamedTuple):
    """Row limit spec container.

    Attention:
        Instances are expected to be created internally via :meth:`dsl.Queryable.limit
        <forml.io.dsl.Queryable.limit>`.
    """

    count: int
    """Number of rows to return."""
    offset: int = 0
    """Skip the given number of rows."""

    def __repr__(self):
        return f'{self.offset}:{self.count}'

class Source(tuple, metaclass=abc.ABCMeta):
    """Base class of the *tabular* data frame sources.

    A *Source* is anything that can be used to obtain tabular data *FROM*. It is a logical
    collection of :class:`dsl.Feature <forml.io.dsl.Feature>` instances represented by its
    :attr:`schema`.
    """

    class Schema(type):
        """Meta-class for schema types construction.

        It guarantees consistent hashing and comparability for equality of the produced schema
        classes.

        Attention:
            This meta-class is used internally, for schema frontend API see the :class:`dsl.Schema
            <forml.io.dsl.Schema>`.
        """

        def __new__(mcs, name: str, bases: tuple[type], namespace: dict[str, typing.Any]):
            seen = set()
            existing = collections.ChainMap(
                *(
                    {f.name: k}
                    for b in bases
                    if isinstance(b, Source.Schema)
                    for c in reversed(inspect.getmro(b))
                    for k, f in c.__dict__.items()
                    if isinstance(f, _struct.Field) and k not in seen and not seen.add(k)
                )
            )
            if existing and len(existing.maps) > len(existing):
                raise _exception.GrammarError(f'Colliding base classes in schema {name}')
            for key, field in namespace.items():
                if not isinstance(field, _struct.Field):
                    continue
                if not field.name:
                    namespace[key] = field = field.renamed(key)  # to normalize so that hash/eq is consistent
                if field.name in existing and existing[field.name] != key:
                    raise _exception.GrammarError(f'Colliding field name {field.name} in schema {name}')
                existing[field.name] = key
            cls = super().__new__(mcs, name, bases, namespace)
            cls.__qualname__ = f'{name}.schema'
            return cls

        def __hash__(cls):
            # pylint: disable=not-an-iterable
            return functools.reduce(operator.xor, (hash(f) for f in cls), 0)

        def __eq__(cls, other: 'dsl.Source.Schema'):
            return (
                isinstance(other, cls.__class__) and len(cls) == len(other) and all(c == o for c, o in zip(cls, other))
            )

        def __len__(cls):
            return sum(1 for _ in cls)  # pylint: disable=not-an-iterable

        def __repr__(cls):
            return f'{cls.__module__}:{cls.__qualname__}'

        @functools.lru_cache
        def __getitem__(cls, name: str) -> 'dsl.Field':
            try:
                item = getattr(cls, name)
            except AttributeError:
                for field in cls:  # pylint: disable=not-an-iterable
                    if name == field.name:
                        return field
            else:
                if isinstance(item, _struct.Field):
                    return item
            raise KeyError(f'Unknown field {name}')

        def __iter__(cls) -> typing.Iterator['dsl.Field']:
            return iter(
                {
                    k: f
                    for c in reversed(inspect.getmro(cls))
                    for k, f in c.__dict__.items()
                    if isinstance(f, _struct.Field)
                }.values()
            )

    copyreg.pickle(
        Schema,
        lambda s: (
            Source.Schema,
            (s.__name__, s.__bases__, {k: f for k, f in s.__dict__.items() if isinstance(f, _struct.Field)}),
        ),
    )

    class Visitor:
        """Source visitor."""

        def visit_source(self, source: 'dsl.Source') -> None:  # pylint: disable=unused-argument
            """Generic source hook.

            Args:
                source: Source instance to be visited.
            """

        def visit_table(self, source: 'dsl.Table') -> None:
            """Table hook.

            Args:
                source: Source instance to be visited.
            """
            self.visit_source(source)

        def visit_reference(self, source: 'dsl.Reference') -> None:
            """Reference hook.

            Args:
                source: Instance to be visited.
            """
            source.instance.accept(self)
            self.visit_source(source)

        def visit_join(self, source: 'dsl.Join') -> None:
            """Join hook.

            Args:
                source: Instance to be visited.
            """
            source.left.accept(self)
            source.right.accept(self)
            self.visit_source(source)

        def visit_set(self, source: 'dsl.Set') -> None:
            """Set hook.

            Args:
                source: Instance to be visited.
            """
            source.left.accept(self)
            source.right.accept(self)
            self.visit_source(source)

        def visit_query(self, source: 'dsl.Query') -> None:
            """Query hook.

            Args:
                source: Instance to be visited.
            """
            source.source.accept(self)
            self.visit_source(source)

    def __new__(cls, *args):
        return super().__new__(cls, args)

    def __getnewargs__(self):
        return tuple(self)

    def __hash__(self):
        return hash(self.__class__.__module__) ^ hash(self.__class__.__qualname__) ^ super().__hash__()

    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(repr(a) for a in self)})'

    def __getattr__(self, name: str) -> 'dsl.Feature':
        try:
            return self[name]
        except KeyError as err:
            raise AttributeError(f'Invalid feature {name}') from err

    @functools.lru_cache
    def __getitem__(self, name: typing.Union[int, str]) -> typing.Any:
        try:
            return super().__getitem__(name)
        except (TypeError, IndexError) as err:
            name = self.schema[name].name
            for field, feature in zip(self.schema, self.features):
                if name == field.name:
                    return feature
            raise RuntimeError(f'Inconsistent {name} lookup vs schema iteration') from err

    @abc.abstractmethod
    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        """Visitor acceptor.

        Args:
            visitor: Visitor instance.
        """

    @functools.cached_property
    def schema(self) -> 'dsl.Source.Schema':
        """Schema type representing this source.

        Returns:
            Schema type.
        """
        return self.Schema(
            self.__class__.__name__,
            (_struct.Schema.schema,),
            {(c.name or f'_{i}'): _struct.Field(c.kind, c.name) for i, c in enumerate(self.features)},
        )

    @functools.cached_property
    @abc.abstractmethod
    def features(self) -> typing.Sequence['dsl.Feature']:
        """List of features logically contained in or potentially produced by this Source.

        Returns:
            Sequence of contained features.
        """

    @property
    def query(self) -> 'dsl.Query':
        """Query equivalent of this Source.

        Returns:
            Query instance.
        """
        return Query(self)

    @property
    def statement(self) -> 'dsl.Statement':
        """Statement equivalent of this Source.

        Returns:
            Statement instance.
        """
        return self.query

    @property
    def instance(self) -> 'dsl.Source':
        """Return the source instance.

        Apart from the ``Reference`` type is the Source itself.

        Returns:
            Source instance.
        """
        return self

    def reference(self, name: typing.Optional[str] = None) -> 'dsl.Reference':
        """Get an independent reference to this Source (e.g. for self-join conditions).

        Args:
            name: Optional alias to be used for this reference (random by default).

        Returns:
            New reference to this Source.

        Examples:
            >>> manager = staff.Employee.reference('manager')
            >>> subs = (
            ...     manager.join(staff.Employee, staff.Employee.manager == manager.id)
            ...     .select(manager.name, function.Count(staff.Employee.id).alias('subs'))
            ...     .groupby(manager.id)
            ... )
        """
        return Reference(self, name)

    def union(self, other: 'dsl.Source') -> 'dsl.Set':
        """Create a new Source as a set union of this and the other Source.

        Args:
            other: Source to union with.

        Returns:
            Set instance.

        Examples:
            >>> barbaz = (
            ...     foo.Bar.select(foo.Bar.X, foo.Bar.Y)
            ...     .union(foo.Baz.select(foo.Baz.X, foo.Baz.Y))
            ... )
        """
        return Set(self, other, Set.Kind.UNION)

    def intersection(self, other: 'dsl.Source') -> 'dsl.Set':
        """Create a new Source as a set intersection of this and the other Source.

        Args:
            other: Source to intersect with.

        Returns:
            Set instance.

        Examples:
            >>> barbaz = (
            ...     foo.Bar.select(foo.Bar.X, foo.Bar.Y)
            ...     .intersection(foo.Baz.select(foo.Baz.X, foo.Baz.Y))
            ... )
        """
        return Set(self, other, Set.Kind.INTERSECTION)

    def difference(self, other: 'dsl.Source') -> 'dsl.Set':
        """Create a new Source as a set difference of this and the other Source.

        Args:
            other: Source to difference with.

        Returns:
            Set instance.

        Examples:
            >>> barbaz = (
            ...     foo.Bar.select(foo.Bar.X, foo.Bar.Y)
            ...     .difference(foo.Baz.select(foo.Baz.X, foo.Baz.Y))
            ... )
        """
        return Set(self, other, Set.Kind.DIFFERENCE)

class Statement(Source, metaclass=abc.ABCMeta):
    """Base class for complete statements.

    Complete statements are:

    * :class:`forml.io.dsl.Query`
    * :class:`forml.io.dsl.Set`.
    """

class Set(Statement):
    """Source made of two set-combined sub-statements with the same schema.

    Attention:
        Instances are expected to be created internally via:

        * :meth:`dsl.Source.union() <forml.io.dsl.Source.union>`
        * :meth:`dsl.Source.intersection() <forml.io.dsl.Source.intersection>`
        * :meth:`dsl.Source.difference() <forml.io.dsl.Source.difference>`
    """

    @enum.unique
    class Kind(enum.Enum):
        """Set type enum."""

        UNION = 'union'
        """Union set operation type."""
        INTERSECTION = 'intersection'
        """Intersection set operation type."""
        DIFFERENCE = 'difference'
        """Difference set operation type."""

    left: 'dsl.Statement' = property(operator.itemgetter(0))
    """Left side of the set operation."""
    right: 'dsl.Statement' = property(operator.itemgetter(1))
    """Right side of the set operation."""
    kind: 'dsl.Set.Kind' = property(operator.itemgetter(2))
    """Set operation enum type."""

    def __new__(cls, left: 'dsl.Source', right: 'dsl.Source', kind: 'dsl.Set.Kind'):
        if left.schema != right.schema:
            raise _exception.GrammarError('Incompatible sources')
        return super().__new__(cls, left.statement, right.statement, kind)

    def __repr__(self):
        return f'{repr(self.left)} {self.kind.value} {repr(self.right)}'

    @property
    def statement(self) -> 'dsl.Statement':
        return self

    @functools.cached_property
    def features(self) -> typing.Sequence['dsl.Feature']:
        return self.left.features + self.right.features

    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        visitor.visit_set(self)

class Queryable(Source, metaclass=abc.ABCMeta):
    """Base class for any *Source* that can be queried directly."""

    def select(self, *features: 'dsl.Feature') -> 'dsl.Query':
        """Specify the output features to be provided (projection).

        Repeated calls to ``.select`` replace the earlier selection.

        Args:
            features: Sequence of features.

        Returns:
            Query instance.

        Examples:
            >>> barxy = foo.Bar.select(foo.Bar.X, foo.Bar.Y)
        """
        return self.query.select(*features)

    def where(self, condition: 'dsl.Predicate') -> 'dsl.Query':
        """Add a row-filtering condition that's evaluated before any aggregations.

        Repeated calls to ``.where`` combine all the conditions (logical AND).

        Args:
            condition: Boolean feature expression.

        Returns:
            Query instance.

        Examples:
            >>> barx10 = foo.Bar.where(foo.Bar.X == 10)
        """
        return self.query.where(condition)

    def having(self, condition: 'dsl.Predicate') -> 'dsl.Query':
        """Add a row-filtering condition that's applied to the evaluated aggregations.

        Repeated calls to ``.having`` combine all the conditions (logical AND).

        Args:
            condition: Boolean feature expression.

        Returns:
            Query instance.

        Examples:
            >>> bargy10 = foo.Bar.groupby(foo.Bar.X).having(function.Count(foo.Bar.Y) == 10)
        """
        return self.query.having(condition)

    def groupby(self, *features: 'dsl.Operable') -> 'dsl.Query':
        """Aggregation grouping specifiers.

        Repeated calls to ``.groupby`` replace the earlier grouping.

        Args:
            features: Sequence of aggregation features.

        Returns:
            Query instance.

        Examples:
            >>> bargbx = foo.Bar.groupby(foo.Bar.X).select(foo.Bar.X, function.Count(foo.Bar.Y))
        """
        return self.query.groupby(*features)

    def orderby(self, *terms: 'dsl.Ordering.Term') -> 'dsl.Query':
        """Ordering specifiers.

        Default direction is *ascending*.

        Repeated calls to ``.orderby`` replace the earlier ordering.

        Args:
            terms: Sequence of feature and direction tuples.

        Returns:
            Query instance.

        Examples:
            >>> barbyx = foo.Bar.orderby(foo.Bar.X)
            >>> barbyxd = foo.Bar.orderby(foo.Bar.X, 'desc')
            >>> barbxy = foo.Bar.orderby(foo.Bar.X, foo.Bar.Y)
            >>> barbxdy = foo.Bar.orderby(
            ...     foo.Bar.X, dsl.Ordering.Direction.DESCENDING, foo.Bar.Y, 'asc'
            ... )
            >>> barbydxd = foo.Bar.orderby(
            ...     (foo.Bar.X, 'desc'),
            ...     (foo.Bar.Y, dsl.Ordering.Direction.DESCENDING),
            ... )
        """
        return self.query.orderby(*terms)

    def limit(self, count: int, offset: int = 0) -> 'dsl.Query':
        """Restrict the result rows by its max *count* with an optional *offset*.

        Repeated calls to ``.limit`` replace the earlier restriction.

        Args:
            count: Number of rows to return.
            offset: Skip the given number of rows.

        Returns:
            Query instance.

        Examples:
            >>> bar10 = foo.Bar.limit(10)
        """
        return self.query.limit(count, offset)

class Origin(Queryable, metaclass=abc.ABCMeta):
    """Origin is a queryable Source with some handle.

    Its features are represented using :class:`dsl.Element <forml.io.dsl.Element>`.
    """

    @property
    @abc.abstractmethod
    def features(self) -> typing.Sequence['dsl.Element']:
        """Origin features are instances of ``dsl.Element``.

        Returns:
            Sequence of ``dsl.Element`` instances.
        """

    def inner_join(self, other: 'dsl.Origin', condition: 'dsl.Predicate') -> 'dsl.Join':
        """Construct an *inner* join with the other *origin* using the provided *condition*.

        Args:
            other: Source to join with.
            condition: Feature expression as the join condition.

        Returns:
            Join instance.

        Examples:
            >>> barbaz = foo.Bar.inner_join(foo.Baz, foo.Bar.baz == foo.Baz.id)
        """
        return Join(self, other, Join.Kind.INNER, condition)

    def left_join(self, other: 'dsl.Origin', condition: 'dsl.Predicate') -> 'dsl.Join':
        """Construct a *left* join with the other *origin* using the provided *condition*.

        Args:
            other: Source to join with.
            condition: Feature expression as the join condition.

        Returns:
            Join instance.

        Examples:
            >>> barbaz = foo.Bar.left_join(foo.Baz, foo.Bar.baz == foo.Baz.id)
        """
        return Join(self, other, Join.Kind.LEFT, condition)

    def right_join(self, other: 'dsl.Origin', condition: 'dsl.Predicate') -> 'dsl.Join':
        """Construct a *right* join with the other *origin* using the provided *condition*.

        Args:
            other: Source to join with.
            condition: Feature expression as the join condition.

        Returns:
            Join instance.

        Examples:
            >>> barbaz = foo.Bar.right_join(foo.Baz, foo.Bar.baz == foo.Baz.id)
        """
        return Join(self, other, Join.Kind.RIGHT, condition)

    def full_join(self, other: 'dsl.Origin', condition: 'dsl.Predicate') -> 'dsl.Join':
        """Construct a *full* join with the other *origin* using the provided *condition*.

        Args:
            other: Source to join with.
            condition: Feature expression as the join condition.

        Returns:
            Join instance.

        Examples:
            >>> barbaz = foo.Bar.full_join(foo.Baz, foo.Bar.baz == foo.Baz.id)
        """
        return Join(self, other, Join.Kind.FULL, condition)

    def cross_join(self, other: 'dsl.Origin') -> 'dsl.Join':
        """Construct a *cross* join with the other *origin*.

        Args:
            other: Source to join with.

        Returns:
            Join instance.

        Examples:
            >>> barbaz = foo.Bar.cross_join(foo.Baz)
        """
        return Join(self, other, kind=Join.Kind.CROSS)

class Join(Origin):
    """Source made of two join-combined sub-sources.

    Attention:
        Instances are expected to be created internally via:

        * :meth:`dsl.Origin.inner_join() <forml.io.dsl.Origin.inner_join>`
        * :meth:`dsl.Origin.left_join() <forml.io.dsl.Origin.left_join>`
        * :meth:`dsl.Origin.right_join() <forml.io.dsl.Origin.right_join>`
        * :meth:`dsl.Origin.full_join() <forml.io.dsl.Origin.full_join>`
        * :meth:`dsl.Origin.cross_join() <forml.io.dsl.Origin.cross_join>`
    """

    @enum.unique
    class Kind(enum.Enum):
        """Join type enum."""

        INNER = 'inner'
        """Inner join type (default if *condition* is provided)."""
        LEFT = 'left'
        """Left outer join type."""
        RIGHT = 'right'
        """Right outer join type."""
        FULL = 'full'
        """Full join type."""
        CROSS = 'cross'
        """Cross join type (default if *condition* is not provided)."""

        def __repr__(self):
            return f'<{self.value}-join>'

    left: 'dsl.Origin' = property(operator.itemgetter(0))
    """Left side of the join operation."""
    right: 'dsl.Origin' = property(operator.itemgetter(1))
    """Right side of the join operation."""
    kind: 'dsl.Join.Kind' = property(operator.itemgetter(2))
    """Join type."""
    condition: typing.Optional['dsl.Predicate'] = property(operator.itemgetter(3))
    """Join condition (invalid for *CROSS*-join)."""

    def __new__(
        cls,
        left: 'dsl.Origin',
        right: 'dsl.Origin',
        kind: typing.Union['dsl.Join.Kind', str],
        condition: typing.Optional['dsl.Predicate'] = None,
    ):
        if (kind is cls.Kind.CROSS) ^ (condition is None):
            raise _exception.GrammarError('Illegal use of condition and join type')
        if condition is not None:
            condition = series.Cumulative.ensure_notin(series.Predicate.ensure_is(condition))
            if not series.Element.dissect(condition).issubset(series.Element.dissect(*left.features, *right.features)):
                raise _exception.GrammarError(
                    f'({condition}) not a subset of source features ({left.features}, {right.features})'
                )
        return super().__new__(cls, left, right, kind, condition)

    def __repr__(self):
        return f'{repr(self.left)}{repr(self.kind)}{repr(self.right)}'

    @functools.cached_property
    def features(self) -> typing.Sequence['dsl.Element']:
        return self.left.features + self.right.features

    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        visitor.visit_join(self)

class Reference(Origin):
    """Wrapper around any *Source* associating it with a (possibly random) name.

    Attention:
        Instances are expected to be created internally via :meth:`dsl.Source.reference
        <forml.io.dsl.Source.reference>`.
    """

    _NAMELEN: int = 8
    instance: 'dsl.Source' = property(operator.itemgetter(0))
    """Wrapped *Source* instance."""
    name: str = property(operator.itemgetter(1))
    """Reference name."""

    def __new__(cls, instance: 'dsl.Source', name: typing.Optional[str] = None):
        if not name:
            name = ''.join(random.choice(string.ascii_lowercase) for _ in range(cls._NAMELEN))
        return super().__new__(cls, instance.instance, name)

    def __repr__(self):
        return f'{self.name}=[{repr(self.instance)}]'

    @functools.cached_property
    def features(self) -> typing.Sequence['dsl.Element']:
        return tuple(series.Element(self, c.name) for c in self.instance.features)

    @property
    def schema(self) -> 'dsl.Source.Schema':
        return self.instance.schema

    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        """Visitor acceptor.

        Args:
            visitor: Visitor instance.
        """
        visitor.visit_reference(self)

class Table(Origin):
    """Table based *Source* with an explicit *schema*.

    Attention:
        The primary way of creating ``Table`` instances is by inheriting the :class:`dsl.Schema
        <forml.io.dsl.Schema>` which is using this type as a meta-class.
    """

    class Meta(abc.ABCMeta):
        """Metaclass for dynamic parent classes."""

    copyreg.pickle(
        Meta,
        lambda c: (
            Table.Meta,
            (c.__name__, c.__bases__, {}),
        ),
    )

    @typing.overload
    def __new__(  # pylint: disable=bad-classmethod-argument
        mcs,
        name: str,
        bases: tuple[type],
        namespace: dict[str, typing.Any],
    ):
        """Meta-class mode constructor.

        Args:
            name: Table class name.
            bases: Table base classes.
            namespace: Class namespace container.
        """

    @typing.overload
    def __new__(cls, schema: 'dsl.Source.Schema'):
        """Standard class mode constructor.

        Args:
            schema: Table *schema* type.
        """

    def __new__(mcs, schema, bases=None, namespace=None):  # pylint: disable=bad-classmethod-argument
        if isinstance(schema, str):  # used as metaclass
            if bases:
                bases = tuple(b.schema for b in bases if isinstance(b, Table))
                # strip the parent base class and namespace
                mcs = mcs.Meta(schema, mcs.__bases__, {})  # pylint: disable=self-cls-assignment
            elif not any(isinstance(a, _struct.Field) for a in namespace.values()):
                # used as a base class definition - let's propagate the namespace
                mcs = mcs.Meta(schema, (mcs,), namespace)  # pylint: disable=self-cls-assignment
            schema = mcs.Schema(schema, bases, namespace)
        elif bases or namespace:
            raise TypeError('Unexpected use of schema table')
        return super().__new__(mcs, schema)  # used as constructor

    def __repr__(self):
        return self.schema.__name__

    @property
    def schema(self) -> 'dsl.Source.Schema':
        return self[0]

    @functools.cached_property
    def features(self) -> typing.Sequence['dsl.Column']:
        return tuple(series.Column(self, f.name) for f in self.schema)

    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        visitor.visit_table(self)

class Query(Queryable, Statement):
    """Query based *Source*.

    Container for holding all the parameters supplied via the :class:`dsl.Queryable
    <forml.io.dsl.Queryable>` interface.

    Attention:
        Instances are expected to be created internally via the ``dsl.Queryable`` interface methods.
    """

    source: 'dsl.Source' = property(operator.itemgetter(0))
    """Base *Source* to query *FROM*."""
    selection: tuple['dsl.Feature'] = property(operator.itemgetter(1))
    """Result projection features."""
    prefilter: typing.Optional['dsl.Predicate'] = property(operator.itemgetter(2))
    """Row-filtering condition to be applied before potential aggregations."""
    grouping: tuple['dsl.Operable'] = property(operator.itemgetter(3))
    """Aggregation grouping specifiers."""
    postfilter: typing.Optional['dsl.Predicate'] = property(operator.itemgetter(4))
    """Row-filtering condition to be applied after aggregations."""
    ordering: tuple['dsl.Ordering'] = property(operator.itemgetter(5))
    """Ordering specifiers."""
    rows: typing.Optional['dsl.Rows'] = property(operator.itemgetter(6))
    """Row restriction limit."""

    def __new__(
        cls,
        source: 'dsl.Source',
        selection: typing.Optional[typing.Iterable['dsl.Feature']] = None,
        prefilter: typing.Optional['dsl.Predicate'] = None,
        grouping: typing.Optional[typing.Iterable['dsl.Operable']] = None,
        postfilter: typing.Optional['dsl.Predicate'] = None,
        ordering: typing.Optional[typing.Sequence['dsl.Ordering.Term']] = None,
        rows: typing.Optional['dsl.Rows'] = None,
    ):
        def ensure_subset(*features: 'dsl.Feature') -> typing.Sequence['dsl.Feature']:
            """Ensure the provided features is a valid subset of the available Source features.

            Args:
                *features: List of features to validate.

            Returns:
                Original list of features if all valid.
            """
            if not series.Element.dissect(*features).issubset(superset):
                raise _exception.GrammarError(f'{features} not a subset of source features: {superset}')
            return features

        superset = series.Element.dissect(*source.features)
        selection = tuple(ensure_subset(*(series.Feature.ensure_is(c) for c in selection or [])))
        if prefilter is not None:
            prefilter = series.Cumulative.ensure_notin(
                series.Predicate.ensure_is(*ensure_subset(series.Operable.ensure_is(prefilter)))
            )
        if grouping:
            grouping = ensure_subset(*(series.Cumulative.ensure_notin(series.Operable.ensure_is(g)) for g in grouping))
            for aggregate in {c.operable for c in selection or source.features}.difference(grouping):
                series.Aggregate.ensure_in(aggregate)
        if postfilter is not None:
            postfilter = series.Window.ensure_notin(
                series.Predicate.ensure_is(*ensure_subset(series.Operable.ensure_is(postfilter)))
            )
        ordering = tuple(series.Ordering.make(*(ordering or [])))
        ensure_subset(*(o.feature for o in ordering))
        return super().__new__(cls, source, selection, prefilter, tuple(grouping or []), postfilter, ordering, rows)

    def __repr__(self):
        value = repr(self.source)
        if self.selection:
            value += f'[{", ".join(repr(c) for c in self.selection)}]'
        if self.prefilter:
            value += f'.where({repr(self.prefilter)})'
        if self.grouping:
            value += f'.groupby({", ".join(repr(c) for c in self.grouping)})'
        if self.postfilter:
            value += f'.having({repr(self.postfilter)})'
        if self.ordering:
            value += f'.orderby({", ".join(repr(c) for c in self.ordering)})'
        if self.rows:
            value += f'[{repr(self.rows)}]'
        return value

    @property
    def query(self) -> 'dsl.Query':
        return self

    @functools.cached_property
    def features(self) -> typing.Sequence['dsl.Feature']:
        """Get the list of features supplied by this query.

        Returns:
            A sequence of supplying features.
        """
        return self.selection if self.selection else self.source.features

    def accept(self, visitor: 'dsl.Source.Visitor') -> None:
        visitor.visit_query(self)

    def select(self, *features: 'dsl.Feature') -> 'dsl.Query':
        return Query(self.source, features, self.prefilter, self.grouping, self.postfilter, self.ordering, self.rows)

    def where(self, condition: 'dsl.Predicate') -> 'dsl.Query':
        if self.prefilter is not None:
            condition &= self.prefilter
        return Query(self.source, self.selection, condition, self.grouping, self.postfilter, self.ordering, self.rows)

    def having(self, condition: 'dsl.Predicate') -> 'dsl.Query':
        if self.postfilter is not None:
            condition &= self.postfilter
        return Query(self.source, self.selection, self.prefilter, self.grouping, condition, self.ordering, self.rows)

    def groupby(self, *features: 'dsl.Operable') -> 'dsl.Query':
        return Query(self.source, self.selection, self.prefilter, features, self.postfilter, self.ordering, self.rows)

    def orderby(self, *terms: 'dsl.Ordering.Term') -> 'dsl.Query':
        return Query(self.source, self.selection, self.prefilter, self.grouping, self.postfilter, terms, self.rows)

    def limit(self, count: int, offset: int = 0) -> 'dsl.Query':
        return Query(
            self.source,
            self.selection,
            self.prefilter,
            self.grouping,
            self.postfilter,
            self.ordering,
            Rows(count, offset),
        )


import network  # Importar librerías necesarias
import socket
import time
import secrets  # Librería con las credenciales de tu red Wi-Fi

from machine import Pin

#Asignación de pin para el LED
led = Pin(15, Pin.OUT)

# Configuración de red Wi-Fi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.config(pm = 0xa11140)

wlan.connect(secrets.SSID, secrets.PASSWORD)

# Página HTML
html_on = """<!DOCTYPE html>

     <title>Raspberry pi Pico W</title>

Server on a Pico W

        <form>
             <button class="buttonRed" name="Apagar" value="Off" formaction="/light/off" type="submit">Apagar LED </button>

%s

        </form>

"""

html_off = """<!DOCTYPE html>

     <title>Raspberry pi Pico W</title>

Server on a Pico W

        <form>
             <button class="button" name="Encender" value="On" formaction="/light/on" type="submit">Encender LED </button>

%s

        </form>

"""

#Esperamos que la conexion WiFi se establezca o falle
max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

# Error al conectar la red WiFi
if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    print( 'Subnet = ' + status[1] )
    print( 'Gateway = ' + status[2] )
    print( 'DNS = ' + status[3] )

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

#Esperamos una conexion a nuestra pagina
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        print(request)

        request = str(request)
        led_on = request.find('/light/on')
        led_off = request.find('/light/off')
        print( 'led on = ' + str(led_on))
        print( 'led off = ' + str(led_off))

        if led_on == 6:
            print("led on")
            led.value(1)
            stateis = "Encendido"
            response = html_on % stateis

        if led_off == 6:
            print("led off")
            led.value(0)
            stateis = "Apagado"
            response = html_off % stateis

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')


from accounts.serializers import UserSerializer
from django.shortcuts import redirect
from django.conf import settings
from django.contrib.auth import get_user_model

from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework_jwt.settings import api_settings

from .utils import google_obtain_access_token, google_get_user_info, user_get_or_create, jwt_login, get_user_info

BASE_URL = settings.BASE_URL
BASE_FRONTEND_URL= settings.BASE_FRONTEND_URL
LOGIN_URL = f'{BASE_URL}/accounts/auth/login'

UserModel = get_user_model()

class GetUserApi(APIView):
    """
    Determine current user. Return user name, email
    and profile image
    """
    permission_classes = [AllowAny]

    def get(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            return Response(get_user_info(user=request.user))
        return Response(status=status.HTTP_204_NO_CONTENT)

class GoogleLoginAPI(APIView):
    """
    Manage login with Google
    Get token from request and obtain user information: email,
    user name and profile image
    """
    permission_classes = []

    class InputSerializer(serializers.Serializer):
        code = serializers.CharField(required=True)

    def get(self, request, *args, **kwargs):
        input_serializer = self.InputSerializer(data=request.GET)
        input_serializer.is_valid(raise_exception=True)

        validated_data = input_serializer.validated_data
        code = validated_data.get('code')

        if not code:
            return redirect(f'{LOGIN_URL}?error')

        redirect_uri = f'{LOGIN_URL}/google'
        access_token = google_obtain_access_token(
            code=code, redirect_uri=redirect_uri)

        user_info = google_get_user_info(access_token)

        profile_data = {
            'first_name': user_info.get('given_name'),
            'last_name': user_info.get('family_name'),
            'email': user_info.get('email'),
            'profile_image': user_info.get('picture'),
        }

        user = user_get_or_create(profile_data)

        res = redirect(BASE_FRONTEND_URL)
        res = jwt_login(response=res, user=user)
        return res

class LogoutAPI(APIView):
    """
    Log out user by removing JWT cookie header
    """
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        response = Response(status=status.HTTP_202_ACCEPTED)
        params = {
            'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
            'domain': api_settings.JWT_AUTH_COOKIE_DOMAIN,
            'path': api_settings.JWT_AUTH_COOKIE_PATH,
            'secure': api_settings.JWT_AUTH_COOKIE_SECURE,
            'samesite': api_settings.JWT_AUTH_COOKIE_SAMESITE,
            'httponly': True
        }
        response.set_cookie(api_settings.JWT_AUTH_COOKIE, **params)
        return response

class SignUpUserApi(CreateAPIView):
    """
    Create new user with email and password and log user in
    """
    serializer_class = UserSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)

        email = serializer.data['email']
        user = UserModel.objects.get(email=email)

        res = Response(serializer.data, status=status.HTTP_201_CREATED)
        res = jwt_login(response=res, user=user)

        return res


""" Module containing implementation of evolutionary computation algorithms, such as:
      - basic Evolutionary Algorithm
      - Genetic Programming
      - Evolutionary Strategies

    for solving the cases (see 'cases' module).
"""
import random
import copy
from typing import Tuple, Union, Dict, Any, List

from deap import base, creator, tools

import evolution.solution as sol
import evolution.evaluator as evaluator
import evolution.ec_utils as ecu
from utils.logger import glob_logger

# Some default values used in the algorithms
# Population size, No. Generations, Mutation prob., Crossover prob.
NPOP, NGEN, PMUT, PCX = 20, 100, 0.5, 0.2

class EvolutionParameters:
    """ Class containing parameters of the evolution process. The class generally assumes only the
    most generic parameters employed in all evolutionary techniques:
      - Population size
      - Number of generations
      - Probability of mutation
      - Probability of crossover

    Additional parameters can be supplied as keyword arguments and will be made available in
    the object through the . (dot) notation, i.e.:
      - EvolutionParameters(..., my_parameter=13.5) -> evo_param.my_parameter

    The general rules are that 1) each case should provide parameter values that are different
    from the default ones and 2) each algorithm should contain an initialization phase where all
    required parameters are set to their default value if no parameter value was supplied.

    :ivar pop: the requested population size
    :ivar gen: the number of generations
    :ivar pmut: the probability of mutation
    :ivar pcx: the probability of crossover
    """
    def __init__(
        self, popsize: int = NPOP, generations: int = NGEN, prob_mut: float = PMUT,
        prob_cx: float = PCX, **kwargs: Any
    ) -> None:
        """ Constructor

        :param popsize: the requested population size
        :param generations: the number of generations
        :param prob_mut: the probability of mutation
        :param prob_cx: the probability of crossover
        """
        self.pop = popsize
        self.gen = generations
        self.pmut = prob_mut
        self.pcx = prob_cx
        # Set the additional kwargs parameters as attributes
        for attr, value in kwargs.items():
            setattr(self, attr, value)

    def update_defaults(self, defaults: Dict[str, Any]) -> None:
        """ Set default parameter values for those parameters that are not present in the object.
        We do not impose any restriction on the type of the parameters.

        :param defaults: map of default parameters and their values
        """
        for attr, value in defaults.items():
            if not hasattr(self, attr):
                setattr(self, attr, value)

    def __setattr__(self, name: str, value: Any) -> None:
        """ Setattr override to allow for dynamic attributes type checking.
        https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html#when-you-re-puzzled-or-when-things-are-complicated

        :param name: name of the attribute
        :param value: value of the attribute
        """
        super().__setattr__(name, value)

    def __getattr__(self, name: str) -> Any:
        """ Getattr override to allow for dynamic attributes type checking.
        https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html#when-you-re-puzzled-or-when-things-are-complicated

        :param name: name of the attribute

        :return: value of the attribute
        """
        return super().__getattribute__(name)

# def opt_ga(
#     solutions: sol.SolutionSet,
#     evo_params: EvolutionParameters,
#     apply_func: sol.ApplySignature,
#     fitness_func: sol.FitnessSignature,
#     workers: int,
#     **apply_params: Union[int, float]
# ) -> ecu.OptIndividual:
#     """ TODO: doc
#     """
#     evo_params.update_defaults({
#             'pop_lambda': evo_params.pop,
#             'tourn_size': 4
#         })

#     hof = ecu.BestTracker()
#     creator.create('FitnessMax', base.Fitness, weights=(1.0,))
#     str_limits = solutions.get_strength_limits()

#     try:
#         # Create an evaluator context that handles the multiprocess / single process evaluation
#         with evaluator.Evaluator(workers, solutions, apply_func, fitness_func) as ev:
#             # Initialize the population
#             population = [
#                 ecu.OptIndividual(str_limits, creator.FitnessMax())
#                 for _ in range(evo_params.pop)
#             ]
#             # Evaluate the initial population
#             for ind in population:
#                 # Make it tuple of one element
#                 ind.fitness.values = ev.evaluate(
#                     None, None, strength=ind.str_map, **apply_params
#                 )[0],
#             hof.update(population)

#             for g in range(evo_params.gen):
#                 # Randomly select 'lambda' parents and deepcopy them => offsprings
#                 chosen = tools.selTournament(population, k=evo_params.pop_lambda, tournsize=evo_params.tourn_size)
#                 offsprings: List[ecu.OptIndividual] = list(map(copy.deepcopy, chosen))
#                 # Perform the crossover (mating) among the offsprings
#                 for o1, o2 in zip(offsprings[::2], offsprings[1::2]):
#                     if random.random() < evo_params.pcx:
#                         ecu.crossover_strength(o1, o2)
#                 # Mutate some of the offsprings
#                 for o in offsprings:
#                     ecu.mutate_strength(o, evo_params.pmut)
#                 # Recalculate the fitness for offsprings that have changed
#                 for ind in [o for o in offsprings if not o.fitness.valid]:
#                     ind.fitness.values = ev.evaluate(
#                         None, None, strength=ind.str_map, **apply_params
#                     )[0],
#                 # Select 'mu' best offsprings and make them the new population
#                 population[:] = tools.selBest(offsprings, evo_params.pop)
#                 hof.update(population)
#                 print(f'Gen {g}: fitness={hof[0].fitness:.2f}; str=[{",".join(hof[0].str_map.values())}]')
#             # Re-evaluate the solutions to contain the all-time best results
#             ev.evaluate(None, None, strength=hof[0].str_map, **apply_params)
#         return hof[0]
#     finally:
#         # Make sure we remove the created FitnessMax class when multiple algorithms
#         # are run back to back in one session
#         del creator.FitnessMax

def opt_es_plus(
    solutions: sol.SolutionSet,
    evo_params: EvolutionParameters,
    apply_func: sol.ApplySignature,
    fitness_func: sol.FitnessSignature,
    workers: int,
    **apply_params: Union[int, float]
) -> ecu.OptIndividual:
    """ TODO: doc
    """
    evo_params.update_defaults({
            'pop_lambda': evo_params.pop * 2
        })

    hof = ecu.BestTracker()
    creator.create('FitnessMax', base.Fitness, weights=(1.0,))
    str_limits = solutions.get_strength_limits()
    sol_count = len(solutions)
    opt_goal = next(iter(solutions)).result.opt_goal

    try:
        # Create an evaluator context that handles the multiprocess / single process evaluation
        with evaluator.Evaluator(workers, solutions, apply_func, fitness_func) as ev:
            # Initialize the population
            population = [
                ecu.OptIndividual(str_limits, creator.FitnessMax())
                for _ in range(evo_params.pop)
            ]
            # Evaluate the initial population
            for ind in population:
                # Make it tuple of one element
                ind.fitness.values = ev.evaluate(
                    None, None, strength=ind.str_map, **apply_params
                )[0],
            hof.update(population)

            for g in range(evo_params.gen):
                gen_best = ecu.BestTracker()
                # Randomly select 'lambda' parents and deepcopy them => offsprings
                offsprings: List[ecu.OptIndividual] = list(
                    map(copy.deepcopy, random.choices(population, k=evo_params.pop_lambda))
                )
                # Mutate some of the offsprings
                for o in offsprings:
                    ecu.mutate_strength(o)
                # Recalculate the fitness for offsprings that have changed
                for ind in [o for o in offsprings if not o.fitness.valid]:
                    ind.fitness.values = ev.evaluate(
                        None, None, strength=ind.str_map, **apply_params
                    )[0],
                # Select 'mu' best individuals and make them the new population
                population[:] = tools.selBest(population + offsprings, evo_params.pop)
                hof.update(population)
                gen_best.update(population)
                best = gen_best.get_best()
                ev.evaluate(None, None, strength=best.str_map, **apply_params)
                # Gen, goal, fitness, data_ratio, time_ratio
                r_time, r_data = 0.0, 0.0
                for sol in solutions:
                    r_time += sol.result.time_ratio
                    r_data += sol.result.data_ratio
                glob_logger.add_record(
                    (g, opt_goal, best.fitness.values[0], best.get_str(), r_data / sol_count, r_time / sol_count)
                )

                # print(
                #     f'Gen {g}: fitness={best.fitness.values[0]}; '\
                #     f'str=[{",".join(map(str, best.str_map.values()))}]'
                # )
            # Re-evaluate the solutions to contain the all-time best results
            ev.evaluate(None, None, strength=hof.get_best().str_map, **apply_params)
        best = hof.get_best()
        print("Strength legend: [CGP, SB, DT, DB, DS]")
        print(
            f'ALL TIME BEST: fitness={best.fitness.values[0]}; '\
            f'str=[{",".join(map(str, best.str_map.values()))}]'
        )
        print(best.str_map)
        for sol in solutions:
            print(f'[{sol.workload.name}]({sol.result.opt_goal}); Time ratio: {sol.result.time_ratio:.8f}; Data ratio: {sol.result.data_ratio:.8f}')
            sol.result.print_effect()
        return hof.get_best()
    finally:
        # Make sure we remove the created FitnessMax class when multiple algorithms
        # are run back to back in one session
        del creator.FitnessMax

def opt_es_comma(
    solutions: sol.SolutionSet,
    evo_params: EvolutionParameters,
    apply_func: sol.ApplySignature,
    fitness_func: sol.FitnessSignature,
    workers: int,
    **apply_params: Union[int, float]
) -> ecu.OptIndividual:
    """ TODO: doc
    """
    evo_params.update_defaults({
            'pop_lambda': evo_params.pop * 2
        })

    hof = ecu.BestTracker()
    creator.create('FitnessMax', base.Fitness, weights=(1.0,))
    str_limits = solutions.get_strength_limits()
    sol_count = len(solutions)
    opt_goal = next(iter(solutions)).result.opt_goal

    try:
        # Create an evaluator context that handles the multiprocess / single process evaluation
        with evaluator.Evaluator(workers, solutions, apply_func, fitness_func) as ev:
            # Initialize the population
            population = [
                ecu.OptIndividual(str_limits, creator.FitnessMax())
                for _ in range(evo_params.pop)
            ]
            # Evaluate the initial population
            for ind in population:
                # Make it tuple of one element
                ind.fitness.values = ev.evaluate(
                    None, None, strength=ind.str_map, **apply_params
                )[0],
            hof.update(population)

            for g in range(evo_params.gen):
                gen_best = ecu.BestTracker()
                # Randomly select 'lambda' parents and deepcopy them => offsprings
                offsprings: List[ecu.OptIndividual] = list(
                    map(copy.deepcopy, random.choices(population, k=evo_params.pop_lambda))
                )
                # Mutate some of the offsprings
                for o in offsprings:
                    ecu.mutate_strength(o)
                # Recalculate the fitness for offsprings that have changed
                for ind in [o for o in offsprings if not o.fitness.valid]:
                    ind.fitness.values = ev.evaluate(
                        None, None, strength=ind.str_map, **apply_params
                    )[0],
                # Select 'mu' best individuals and make them the new population
                population[:] = tools.selBest(offsprings, evo_params.pop)
                hof.update(population)
                gen_best.update(population)
                best = gen_best.get_best()
                ev.evaluate(None, None, strength=best.str_map, **apply_params)
                # Gen, goal, fitness, data_ratio, time_ratio
                r_time, r_data = 0.0, 0.0
                for sol in solutions:
                    r_time += sol.result.time_ratio
                    r_data += sol.result.data_ratio
                glob_logger.add_record(
                    (g, opt_goal, best.fitness.values[0], best.get_str(), r_data / sol_count, r_time / sol_count)
                )

                # print(
                #     f'Gen {g}: fitness={best.fitness.values[0]}; '\
                #     f'str=[{",".join(map(str, best.str_map.values()))}]'
                # )
            # Re-evaluate the solutions to contain the all-time best results
            ev.evaluate(None, None, strength=hof.get_best().str_map, **apply_params)
        best = hof.get_best()
        print("Strength legend: [CGP, SB, DT, DB, DS]")
        print(
            f'ALL TIME BEST: fitness={best.fitness.values[0]}; '\
            f'str=[{",".join(map(str, best.str_map.values()))}]'
        )
        print(best.str_map)
        for sol in solutions:
            print(f'[{sol.workload.name}]({sol.result.opt_goal}); Time ratio: {sol.result.time_ratio:.8f}; Data ratio: {sol.result.data_ratio:.8f}')
            sol.result.print_effect()
        return hof.get_best()
    finally:
        # Make sure we remove the created FitnessMax class when multiple algorithms
        # are run back to back in one session
        del creator.FitnessMax

def basic_ea(
        solutions: sol.SolutionSet,
        evo_params: EvolutionParameters,
        apply_func: sol.ApplySignature,
        fitness_func: sol.FitnessSignature,
        workers: int,
        **apply_params: Union[int, float]
) -> Tuple[float, float]:
    """ Evolutionary algorithm for solving the 'basic' variants of the initial sampling function
    where we tune only the 'base' parameter.
    Heavily inspired by 'https://deap.readthedocs.io/en/master/overview.html'.

    :param solutions: a collection of solutions, one for each workload being solved
    :param evo_params: the supplied parameters for the evolution process
    :param apply_func: function to use for genotype -> fenotype mapping
    :param fitness_func: function to use for fitness evaluation
    :param workers: number of worker processes
    :param apply_params: additional parameters for the apply function

    :return: the best individual and its fitness value
    """

    # First make sure that we have all the parameters we need
    evo_params.update_defaults({
            'attr_low': 0.0, 'attr_high': 100.0,
            'cx_eta': 2.0,
            'mut_eta': 2.0, 'mut_mu': 1, 'mut_sigma': 5,
            'tourn_size': 3
        })

    # Store the all-time best individual
    hof = tools.HallOfFame(1)
    # Create maximization fitness and an individual class
    creator.create('FitnessMax', base.Fitness, weights=(1.0,))
    creator.create('Individual', list, fitness=creator.FitnessMax)

    try:
        # Create an evaluator context that handles the multiprocess / single process evaluation
        with evaluator.Evaluator(workers, solutions, apply_func, fitness_func) as ev:
            # Set the individual and population initializers
            toolbox = base.Toolbox()
            toolbox.register(
                'attribute',
                random.uniform, evo_params.attr_low, evo_params.attr_high
            )
            toolbox.register(
                'individual',
                tools.initRepeat, creator.Individual, toolbox.attribute, n=1
            )
            toolbox.register(
                'population',
                tools.initRepeat, list, toolbox.individual
            )
            # Set the evolution operators: crossover, mutation, selection
            # The evaluation will be performed by the evaluator
            toolbox.register(
                'mate',
                tools.cxSimulatedBinary, eta=evo_params.cx_eta
            )
            toolbox.register(
                'mutate',
                tools.mutGaussian, mu=evo_params.mut_mu, sigma=evo_params.mut_sigma,
                indpb=evo_params.pmut
            )
            toolbox.register(
                'select',
                tools.selTournament, tournsize=evo_params.tourn_size, k=evo_params.pop - 1
            )

            # Evaluate fitness of the initial random population
            pop = toolbox.population(evo_params.pop)
            for ind in pop:
                ind.fitness.values = tuple(ev.evaluate(None, None, base=ind[0], **apply_params))
            hof.update(pop)

            # Run all the generations
            for g in range(evo_params.gen):
                print(f'Generation: {g}')
                # Create new offsprings, always include the all-time best solution
                # The cloning is necessary since crossover and mutations work in-situ
                offsprings = list(map(toolbox.clone, toolbox.select(pop))) + [toolbox.clone(hof[0])]

                # Perform the crossover (mating) among the offsprings
                for o1, o2 in zip(offsprings[::2], offsprings[1::2]):
                    if random.random() < evo_params.pcx:
                        toolbox.mate(o1, o2)
                        del o1.fitness.values
                        del o2.fitness.values
                # Additionally mutate some of the new offsprings
                for mutant in offsprings:
                    toolbox.mutate(mutant)
                    del mutant.fitness.values

                # Recalculate the fitness for the modified offsprings (mating, mutation)
                for ind in [o for o in offsprings if not o.fitness.valid]:
                    ind.fitness.values = tuple(ev.evaluate(None, None, base=ind[0], **apply_params))

                # Update the population and all-time best
                pop[:] = offsprings
                hof.update(pop)

            # Re-evaluate the solutions to contain the all-time best results
            ev.evaluate(None, None, base=hof[0][0], **apply_params)
        return hof[0][0], hof[0].fitness.values[0]
    finally:
        # Make sure we remove the created Individual and Fitness classes when multiple algorithms
        # are run back to back in one session
        del creator.Individual
        del creator.FitnessMax


import discord
from utils.auth import AuthManager

class Account(discord.Cog):
    @discord.slash_command(name="register", description="Register using your discord username")
    async def register(self, ctx):
        AuthManager.registerGuild(ctx.author)
        AuthManager.registerUser(ctx.author)
        await ctx.response.send_message(content=AuthManager.signUp(ctx.author), ephemeral=True)

def setup(bot):  # this is called by Pycord to setup the cog
    bot.add_cog(Account(bot))  # add the cog to the bot


import sys
import os
from numpy.lib.arraysetops import isin
from argparse import ArgumentParser
sys.path.insert(1, './tendims')
sys.path.insert(2, './complexity')
sys.path.insert(3, './sentiment')
sys.path.insert(4, './empathy')

import logging
import json
import numpy as np
import wget
import pickle
import oyaml as yaml

from flask import Flask, request, redirect , jsonify, send_file, send_from_directory, safe_join, abort
from flask.json import JSONEncoder
from flask_cors import CORS
from flask_socketio import SocketIO, send, emit
import uuid
import pandas as pd
from cryptography.fernet import Fernet

from complexity import ComplexityClassifier
from sentiment import SentimentClassifier
from success import SuccessPredictor
from tendims import TenDimensionsClassifier
from empathy import empathy_processing
from werkzeug.utils import secure_filename
from werkzeug.datastructures import  FileStorage

from dh_encryption import DiffieHellman, decrypt_data, encrypt_data, decrypt_file, encrypt_file

import sys
import urllib
import urllib.request
from cryptography.fernet import Fernet
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

class CustomJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.integer):
            return int(obj)
        elif isinstance(obj, np.floating):
            return float(obj)
        elif isinstance(obj, np.ndarray):
            return obj.tolist()
        return JSONEncoder.default(self, obj)

class Engine():
    class Models:
        All = "all"
        Sentiment = "sentiment"
        TenDims = "tendims"
        Success = "success"
        Complexity = "complexity"
        Empathy = "empathy"

    def register_model(self, model_name, model_fun):
        self.models_map[model_name] = model_fun

    def get_model_methods(self, model_name):
        fun_list = []
        if model_name == Engine.Models.All:
            fun_list = list(self.models_map.values())
        else:
            fun_list = self.models_map[model_name]
        return fun_list

    def __init__(self, logger, load_ten_dims=True):
        self.models_map = {}
        self.ip_keys_dict = {}
        self.using_encryption = True
        self.no_key_error_msg = 'Connection is not secure, request a shared key first'
        self.wrong_key_error_msg = 'The shared key is not the same'
        self.dh = DiffieHellman()

        #### Complexity Models ####
        logger.info('Loading complexity models...')
        self.ic_model_file = 'complexity/models/Vocab+FullPOS_xbgoost.model'
        self.liwc_dictionary_file = 'complexity/data/LIWC2007_English100131.dic'
        self.model_complexity = ComplexityClassifier(self.ic_model_file, self.liwc_dictionary_file)
        self.register_model(Engine.Models.Complexity, self.get_complexity)
        logger.info('Complexity models loaded')
        #####################

        # #### Ten Dimensions Models ####
        if load_ten_dims:
            logger.info('Loading tenDims models...')
            self.models_dir = 'tendims/models/lstm_trained_models'
            self.embeddings_dir = 'tendims/embeddings'  # change urls to embeddings dir
            self.success_model_file = 'tendims/models/meeting_success/xgboost_10dims_success_prediction_model_v0.81.dat'
            # Success is not available
            self.model_tendim = TenDimensionsClassifier(models_dir=self.models_dir, embeddings_dir=self.embeddings_dir)
            self.success_predictor = SuccessPredictor(self.success_model_file) # Sucess prediction
            self.register_model(Engine.Models.TenDims, self.get_ten_dims)
            logger.info('Tend dims models loaded')
        #####################

        # self.empathy_model_file = './empathy/models/Vocab+FullPOS+LIWCtrained_XGboost_model_99perc.pickle'
        # self.empathy_ic_model_file = './empathy/models/Vocab+FullPOS_xbgoost.pickle'
        # self.empathy_scorer = empathy_processing.EmpathyScorer(self.empathy_model_file, self.empathy_ic_model_file)
        # self.register_model(Engine.Models.Empathy, self.empathyIC_from_texts)
        #####################

        #### Sentiment Models ####
        logger.info('Loading sentiment model...')
        self.model_sentim = SentimentClassifier()
        self.register_model(Engine.Models.Sentiment, self.get_sentiment)
        logger.info('Sentiment models loaded')
        #####################

    def generate_keys(self, ip_address, logger):
        self.ip_keys_dict[ip_address] = {}
        client_private_key, client_public_key = self.dh.get_private_key(), self.dh.gen_public_key()
        server_private_key, server_public_key = self.dh.get_private_key(), self.dh.gen_public_key()
        self.ip_keys_dict[ip_address]["client"] = {"private_key": client_private_key, "public_key": client_public_key}
        self.ip_keys_dict[ip_address]["server"] = {"private_key": server_private_key, "public_key": server_public_key}
        return {"private_key": client_private_key, "public_key": client_public_key, 'server_public_key':server_public_key}

    def generate_shared_keys(self, ip_address, local_private_key, remote_public_key, logger):
        client_shared_key = DiffieHellman.gen_shared_key_static(local_private_key, remote_public_key)
        server_shared_key = DiffieHellman.gen_shared_key_static(self.ip_keys_dict[ip_address]["server"]["private_key"], self.ip_keys_dict[ip_address]["server"]["public_key"])
        self.ip_keys_dict[ip_address]["client"]["shared_key"] = client_shared_key
        self.ip_keys_dict[ip_address]["server"]["shared_key"] = server_shared_key
        return client_shared_key

    # https://dev.to/ruppysuppy/implementing-end-to-end-encryption-in-your-cross-platform-app-3a2k
    # https://dev.to/ruppysuppy/implementing-end-to-end-encryption-in-your-cross-platform-app-part-2-cgg
    def check_request_key(self, ip_address, logger):
        if ip_address not in self.ip_keys_dict:
            logger.error(self.no_key_error_msg)
            return 400, self.no_key_error_msg
        elif "shared_key" not in self.ip_keys_dict[ip_address]["server"] or "shared_key" not in self.ip_keys_dict[ip_address]["client"]:
            logger.error(self.wrong_key_error_msg)
            return 400, self.wrong_key_error_msg
        return 200, None

    def encrypt_decrypt_file(self, ip_address, folder, filename, logger, new_prefix="", decrypt=False):
        code, error_text = self.check_request_key(ip_address, logger)
        if code >= 400:
            return code, error_text
        try:
            with open(os.path.join(folder, filename), 'rb') as enc_file:
                file_data = enc_file.read()
            if self.using_encryption:
                temp_filename = new_prefix+'_temp_file_data.csv'
                client_shared_key = self.ip_keys_dict[ip_address]["client"]["shared_key"]
                if decrypt:
                    new_data = decrypt_file(file_data, client_shared_key)
                else:
                    new_data = encrypt_file(file_data, client_shared_key)
                with open(os.path.join(folder, temp_filename), 'wb') as dec_file:
                    dec_file.write(new_data)
                try:
                    os.remove(os.path.join(folder, filename))
                except:
                    print(f"Error removing file {filename}")
                filename = temp_filename
                if decrypt:
                    logger.debug(f"\n\nReceived encrypted File, decrypted using {ip_address} key {client_shared_key}")
                else:
                    logger.debug(f"\n\nFile Encrypted using {ip_address} key {client_shared_key}")
            else:
                logger.debug(f"\n\n: Received non encrypted File from {ip_address}")
            return 200, filename
        except Exception as e:
            error_text = f"\n\n Something went wrong while decrypting/encrypting the file {filename}: {e}"
            logger.error(error_text)
            return 400, error_text

    def get_decrypted_text(self, ip_address, text, method, logger):
        code, error_text = self.check_request_key(ip_address, logger)
        if code >= 400:
            return code, error_text
        try:
            if engine.using_encryption:
                client_shared_key = engine.ip_keys_dict[ip_address]["client"]["shared_key"]
                text = decrypt_data(text, client_shared_key)
                logger.debug(f"\n\n{method}: Received encrypted Text, decrypted using {ip_address} key {client_shared_key}: {text}")
            else:
                logger.debug(f"\n\n{method}: Received plain Text from {ip_address}: {text}")
            return 200, text
        except Exception as e:
            error_text = f"\n\n{method}: Something went wrong while getting the request's text {e}"
            logger.error(error_text)
            return 400, error_text

    def get_ten_dims(self, text, logger):
        if USE_TEN_DIMS:
            # you can give in input one string of text
            # dimensions = None extracts all dimensions
            tendim_scores = engine.model_tendim.compute_score(text, dimensions=None)
            success_probability = engine.success_predictor.predict_success(tendim_scores)
            tendim_scores['success'] = float(success_probability)
        else:
            tendim_scores = {'conflict': 0, 'fun': 0, 'identity': 0, 'knowledge': 0, 'power': 0, 'romance': 0, 'similarity': 0, 'status': 0, 'support': 0, 'trust': 0}
            tendim_scores['success'] = 0
        return tendim_scores

    def get_sentiment(self, text, logger):
        return self.model_sentim.get_sentiment(text)

    def get_complexity(self, text, logger):
        return self.model_complexity.get_complexity(text)

    def get_empathy(self, text, logger):
        avg_empathy, avg_ic, scored_text_list = engine.empathy_scorer.empathyIC_from_texts(text)
        return {'Average_Empathy': avg_empathy , 'Average_IC':avg_ic}

    def calculate_stats(self, texts, text_ids, stat_method, logger):
        if not isinstance(stat_method, list):
            stat_method = [stat_method]
        returnAll = []
        for txt, txt_id in zip(texts,text_ids):
            return_data = {}
            return_data["server_text_id"] = txt_id
            # return_data["server_text_data"] = str(txt)
            for stat_fun in stat_method:
                return_data.update(stat_fun(txt, logger))
            returnAll.append(return_data)
        return returnAll

    def call_model_from_text(self, ip_address, text, no_encryption, method, logger):
        try:
            logger.debug(f"Text Getting decrypted text")
            if not isinstance(text, list):
                text = [text]

            retCode = 200
            if not no_encryption:
                retCode, text = engine.get_decrypted_text(ip_address, text, method, logger)

            if retCode == 200:
                text_id = range(0, len(text))
                ret = engine.calculate_stats(text, text_id, self.get_model_methods(method), logger)
                return ret, retCode
            else:
                error_msg = f"\n\nText {method}: Something went wrong while calculating {method} stats. Code: {retCode}"
                logger.error(f"Error {retCode}\n{error_msg}\n{text}")
                return {"message": error_msg, "error_info":text, "status": retCode}, retCode
        except Exception as e:
            logger.error(f"Exception in Text {method}:{e}")
            return {"message": f"Internal Server Error in Text {method}", "error_info":str(e), "status": 500}, 500

    def call_model_from_request(self, flask_request, method, logger):
        try:
            text = flask_request.form.getlist('text')
            if len(text) <= 0:
                text = [flask_request.form.get('text')]

            no_encryption = flask_request.form.get('no_encryption', False)
            retCode = 200
            if not no_encryption:
                retCode, text = engine.get_decrypted_text(flask_request.remote_addr, text, method, logger)

            text_id = flask_request.form.getlist('id')
            if len(text_id) <= 0:
                text_id = [flask_request.form.get('id')]

            logger.info(f"Text stats request from {flask_request.remote_addr}. Encrypted: {not no_encryption}. List len: {len(text)}")
            if retCode == 200:
                ret = engine.calculate_stats(text, text_id, self.get_model_methods(method), logger)
                return ret, retCode
            else:
                error_msg = f"\n\nRequest {method}: Something went wrong while calculating {method} stats. Code: {retCode}"
                logger.error(f"Error {retCode}\n{error_msg}\n{text}")
                return {"message": error_msg, "error_info":text, "status": retCode}, retCode
        except Exception as e:
            logger.error(f"Exception in Request {method}:{e}")
            return {"message": f"Internal Server Error in Request {method}", "error_info":str(e), "status": 500}, 500

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

parser = ArgumentParser()
parser.add_argument('-c', nargs='?', const="config.yaml", type=str)
args = parser.parse_args()
config_filename = args.c
# config_filename = "config5000.yaml"
global config
try:
    config = yaml.safe_load(open(config_filename))
except:
    config = {}

UPLOAD_FOLDER = config.get("upload_folder", './uploaded_files/')
ALLOWED_EXTENSIONS = config.get("allowed_extensions", {'csv', 'txt', 'dat', 'json'})
IP = config.get("ip", "0.0.0.0")
PORT = config.get("port", 5000)
USE_TEN_DIMS = config.get("use_ten_dims", True)
LOG_FILENAME = config.get("log_filename", "flask_log.log")

app = Flask(__name__)
with open(LOG_FILENAME, 'w'):
    pass
handler = logging.FileHandler(LOG_FILENAME)  # Create the file logger
app.logger.addHandler(handler)             # Add it to the built-in logger
app.logger.setLevel(logging.DEBUG)         # Set the log level to debug
app.json_encoder = CustomJSONEncoder
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
socketio = SocketIO(app)
engine = Engine(app.logger, USE_TEN_DIMS)

@app.route("/request-keys", methods=["GET"])
def request_keys():
    method = "Request Keys"
    try:
        retCode = 200
        ip_address = request.remote_addr
        keys_dict = engine.generate_keys(ip_address, app.logger)
        keys_dict["status"] = retCode
        return jsonify(keys_dict), retCode
    except Exception as e:
        app.logger.error(f"Exception in {method}:{e}")
        return jsonify({"message": f"Internal Server Error in {method}", "error_info":str(e), "status": 500}), 500

@app.route("/request-shared-key", methods=["GET"])
def request_shared_key():
    method = "Request Shared Key"
    try:
        retCode = 200
        ip_address = request.remote_addr
        try:
            local_private_key = request.args.get("local_private_key")
            remote_public_key = request.args.get("remote_public_key")
            client_shared_key = engine.generate_shared_keys(ip_address, local_private_key, remote_public_key, app.logger)
        except Exception as e:
            retCode = 400
            return jsonify({"message": "Invalid shared key", "error_info":str(e), "status": retCode}), retCode
        return jsonify({"shared_key": client_shared_key, "status": retCode}), retCode
    except Exception as e:
        app.logger.error(f"Exception in {method}:{e}")
        return jsonify({"message": f"Internal Server Error in {method}", "error_info":str(e), "status": 500}), 500

@app.route("/getStats", methods=['POST'])
def getStats():
    ret_data, code = engine.call_model_from_request(request, Engine.Models.All, app.logger)
    return jsonify(ret_data), code

@app.route("/getStatsFile", methods=['POST'])
def getStatsFile():
    no_encryption = str(request.form.get('no_encryption')) != "False" # No clue why the boolean is returned as a string... But just in case I converted it to a string every time
    # check if the post request has the file part
    if 'file' not in request.files:
        return jsonify({"message": f"No file submitted", "error_info":f"No file submitted", "status": 400}), 400
    file = request.files['file']
    # If the user does not select a file, the browser submits an empty file without a filename.
    if file.filename == '':
        return redirect(request.url)
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        if not os.path.exists(UPLOAD_FOLDER):
            os.makedirs(UPLOAD_FOLDER)
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        if not no_encryption:
            code, filename = engine.encrypt_decrypt_file(request.remote_addr, UPLOAD_FOLDER, filename, app.logger, new_prefix="decrypted", decrypt=True)

    txt_col = request.form["txt_col_name"]

    amount = int(request.form.get("amount", 0))
    data_df = pd.read_csv(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    try:
        os.remove(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    except:
        print(f"Error removing file {filename}")
    # remove file
    data_df["idx"] = range(0,len(data_df))
    app.logger.info(f"File stats request from {request.remote_addr}. Encrypted: {not no_encryption}. Row col: {txt_col}, limit: {amount}, rows: {len(data_df)}")
    output_filename = os.path.splitext(filename)[0]
    output_filename = UPLOAD_FOLDER+output_filename+"_pandas_res.csv"
    initialized = False
    for index, row in data_df.iterrows():
        ret_data, code = engine.call_model_from_text(request.remote_addr, str(row[txt_col]), True, Engine.Models.All, app.logger)
        if code == 200 :
            for key, value in ret_data[0].items():
                if not initialized:
                    data_df[key] = 0 if type(value) == int or float else ""
                    initialized = True
                data_df.at[index, key] = value
        else:
            app.logger.error(f"{ret_data}\t{index}\t{row[txt_col]}")
        if amount > 0 and index >= amount:
            break
    data_df.to_csv(output_filename)
    if not no_encryption:
        code, output_filename = engine.encrypt_decrypt_file(request.remote_addr, UPLOAD_FOLDER, output_filename, app.logger, new_prefix="encrypted", decrypt=False)
    try:
        return send_file(output_filename, attachment_filename=output_filename+"_pandas_res.csv")
    except Exception as e:
        app.logger.error(f"Exception in files stats:{e}")
        return jsonify({"message": f"Internal Server Error in files stats", "error_info":str(e), "status": 500}), 500

@app.route("/tenDimensions", methods=['POST'])
def tenDimensions():
    ret_data, code = engine.call_model_from_request(request, Engine.Models.TenDims, app.logger)
    return jsonify(ret_data), code

@app.route("/complexity", methods=['POST'])
def complexity():
    ret_data, code = engine.call_model_from_request(request, Engine.Models.Complexity, app.logger)
    return jsonify(ret_data), code

@app.route("/sentiment", methods=['POST'])
def sentiment():
    ret_data, code = engine.call_model_from_request(request, Engine.Models.Sentiment, app.logger)
    return jsonify(ret_data), code

@app.route("/empathy", methods=['GET'])
def empathy():
    ret_data, code = engine.call_model_from_request(request, Engine.Models.Sentiment, app.logger)
    return jsonify(ret_data), code

@socketio.on('json')
def handle_json(json):
    app.logger.info('received json: ' + str(json))
    # data = engine.call_model(json, "All", app.logger)
    send(json.dumps({"test":0}), json=True)

@socketio.on('message')
def handle_message(message):
    app.logger.info('received message: ' + str(message))
    send(message)

if __name__ == '__main__':
    CORS(app)
    app.run(host="0.0.0.0",port=5000,threaded=True)
    socketio.run(app)
    app.run()

# Run gunicorn
# sudo nohup sudo gunicorn3 --workers 30 --timeout 0 --bind 0.0.0.0:5000 wsgi:app &
# sudo nohup sudo gunicorn3 --threads 100 --timeout 0 --bind 0.0.0.0:5000 wsgi:app &
# sudo pkill -P [PID]
# ps -ef | grep gun


import os.path
import bitarray

# Класс создан для хранения предыдущего блока и ксора переданного с предудущим для последующего сохранения
class CBCEncrypter:

    def __init__(self, init_key: bitarray.bitarray) -> None:
        super().__init__()
        # Ключ инициализации
        self.key = init_key
        self.prev_block: bitarray.bitarray = None

    def save_block(self, block):
        self.prev_block = block

    def xor_block(self, block: bitarray.bitarray):
        if self.prev_block is None:
            return block ^ self.key
        else:
            res = block ^ self.prev_block
            self.prev_block = block
            return res

class CBCDecrypter:

    def __init__(self, init_key: bitarray.bitarray) -> None:
        super().__init__()
        # Ключ инициализации
        self.key = init_key
        self.prev_block: bitarray.bitarray = None
        self.lazy_block = None

    def save_lazy_block(self, block):
        self.lazy_block = block

    def xor_block(self, block: bitarray.bitarray):
        if self.prev_block is None:
            self.prev_block = self.lazy_block
            return block ^ self.key
        else:
            res = block ^ self.prev_block
            self.prev_block = self.lazy_block
            return res

def read_file_to_bit_arr(filename: str) -> bitarray:
    """
    Считывание файла в битовый массив
    :param filename: имя файла
    :return: битовый массив текста с файла
    """
    filesize = os.path.getsize(filename)
    with open(filename, 'rb') as f:
        bit_arr = bitarray.bitarray(1)
        bit_arr.fromfile(f, filesize)
    return bit_arr[1:]
    pass

def write_bit_arr_to_file(crypto_msg, filename='output.crypto'):
    with open(filename, 'wb') as f:
        f.write(crypto_msg)

def read_key(filename: str) -> bitarray:
    """
    Считывание ключа из файла. Ожидается, что ключ 64 битный (8 байт)
    :param filename: имя файла
    :return: битовый массив ключа
    """
    filesize = os.path.getsize(filename)
    with open(filename, 'rb') as f:
        bit_arr = bitarray.bitarray(1)
        bit_arr.fromfile(f, filesize)
    return bit_arr[1:]

def ror(value: bitarray, shift: int) -> bitarray:
    """
    Циклический битовый сдвиг вправо
    :param value: битовый массив
    :param shift: значение сдвига
    :return: измененный битовый массив
    """
    shift = shift % len(value)
    right = value[:shift]
    left = value[:-shift]
    return right + left
    pass

def rol(value: bitarray, shift: int) -> bitarray:
    """
    Циклический битовый сдвиг влево
    :param value: битовый массив
    :param shift: значение сдвига
    :return: измененный битовый массив
    """
    shift = shift % len(value)
    left = value[:shift]
    right = value[shift:]
    return right + left
    pass

def gen_key_vector(secret_key: bitarray, round_col: int):
    res = []
    for i in range(round_col):
        step1 = rol(secret_key, i * 3)
        step2 = bitarray.bitarray(32)
        pointer = 0
        for i, elem in enumerate(step1):
            if i % 2 == 1:
                step2[pointer] = elem
                pointer += 1
        step3 = step2[16:]
        res.append(step3)
    return res
    pass

def encrypt(msg, init_key, iv, round_cols=1, block_size=64, minimum_bits_block=16):
    """
    Функция шифрования сообщиения
    :param minimum_bits_block: размер минимального блока для шифрования (кратно 8)
    :param block_size: размер блока для шифрования (кратно minimum_bytes_block)
    :param msg: сообщение для шифровки
    :param init_key: стартовый ключ для шифования
    :param round_cols: количество раундов шифрования
    :return: зашифрованное сообщение
    """

    # Добивка последнего блока до 64 битов
    if minimum_bits_block % 8 != 0 or block_size % minimum_bits_block != 0:
        raise Exception("Неверные размеры блоков для шифрования!!!")

    tail_cell_size = len(msg) % block_size
    if tail_cell_size != 0:
        msg += '0' * (block_size - tail_cell_size)

    block_col = len(msg) // block_size
    crypto_msg = bitarray.bitarray(0)
    key_vec = gen_key_vector(init_key, round_cols)
    cbc = CBCEncrypter(iv)
    for round_num in range(round_cols):
        for block_num in range(block_col):
            start = block_num * block_size
            end = block_num * block_size + block_size
            block = msg[start:end]

            # Реализация CBC
            block = cbc.xor_block(block)

            # Блок, разбитый на 4 подблока
            blocks = [block[i * minimum_bits_block: i * minimum_bits_block + minimum_bits_block] for i in range(4)]
            del block
            res_block = [None for i in range(4)]

            res_block[0] = blocks[1]
            res_block[1] = blocks[2] ^ blocks[0]
            res_block[2] = ((blocks[1] ^ key_vec[round_num]) ^ blocks[3]) ^ (blocks[2] ^ blocks[0])
            res_block[3] = blocks[0]

            res = bitarray.bitarray(0)
            for r in res_block:
                res += r

            cbc.save_block(res)
            crypto_msg += res
        msg = crypto_msg
    return msg

def decrypt(crypto_msg, init_key, iv, round_cols=1, block_size=64, minimum_bits_block=16, clear_output=True):
    """

    :param crypto_msg: зашифрованное сообщение
    :param init_key: начальный ключ инициализации
    :param round_cols: количество раундов
    :param block_size: размер блока для шифрования (кратен minimum_bits_block)
    :param minimum_bits_block: минимальный блок кодирования (кратен 8)
    :param clear_output: очищает вывод от NULL байтов (есть возможность выключить, если сообщение их намеренно содежит)
    :return:
    """
    if minimum_bits_block % 8 != 0 or block_size % minimum_bits_block != 0:
        raise Exception("Неверные размеры блоков для шифрования!!!")

    tail_cell_size = len(crypto_msg) % block_size
    if tail_cell_size != 0:
        crypto_msg += '0' * (block_size - tail_cell_size)

    block_col = len(crypto_msg) // block_size
    decrypt_msg = bitarray.bitarray(0)
    key_vec = gen_key_vector(init_key, round_cols)
    cbc = CBCDecrypter(iv)

    for round_num in range(round_cols):
        for block_num in range(block_col):
            start = block_num * block_size
            end = block_num * block_size + block_size

            block = crypto_msg[start:end]
            cbc.save_lazy_block(block)
            # Блок, разбитый на 4 подблока
            blocks = [block[i * minimum_bits_block: i * minimum_bits_block + minimum_bits_block] for i in range(4)]

            res_block = [None for i in range(4)]

            res_block[0] = blocks[3]
            res_block[1] = blocks[0]
            res_block[2] = blocks[1] ^ blocks[3]
            res_block[3] = (blocks[2] ^ blocks[1]) ^ (key_vec[round_num] ^ blocks[0])

            res = bitarray.bitarray(0)
            for r in res_block:
                res += r

            res = cbc.xor_block(res)

            decrypt_msg += res
        crypto_msg = decrypt_msg
        decrypt_msg = bitarray.bitarray(0)

    decrypt_msg = [crypto_msg[i * 8: i * 8 + 8] for i in range(len(crypto_msg) // 8)]
    NULL = bitarray.bitarray('0' * 8)
    while bitarray.bitarray(NULL) in decrypt_msg:
        decrypt_msg.remove(NULL)

    crypto_msg = bitarray.bitarray(0)
    for partition in decrypt_msg:
        crypto_msg += partition
    return crypto_msg
    pass

def get_iv(data: str):
    iv = bitarray.bitarray(1)
    iv.frombytes(data.encode())
    return iv[1:]

if __name__ == '__main__':
    f = open('iv.txt')
    iv_v = f.readline()
    iv_data = get_iv(iv_v)
    key = read_key('./key.txt')

    crypto = read_file_to_bit_arr('./flag.bin')
    new_text = decrypt(crypto, key, iv_data)
    print("Шифротекст: ", new_text)
    write_bit_arr_to_file(new_text, 'decrypt_message.txt')

    pass