"""
    gitdata utils
"""

import collections
import datetime
import decimal
import inspect
import logging
import os
import string
import uuid

logger = logging.getLogger(__name__)

chars = ''.join(map(chr, range(256)))
keep_these = string.ascii_letters + string.digits + '-_ '
delete_these = chars.translate(str.maketrans(chars, chars, keep_these))
allowed = str.maketrans(keep_these, keep_these, delete_these)

def new_uid():
    """returns a unique id"""
    return uuid.uuid4().hex

def test_uid_maker(start=0):
    """return a simple uid generator for testing

    Returns a simple uid generator that with a given integer
    and increments by one each time its called which allows
    predictable uid values for testing.

    >>> id_maker = test_uid_maker(4)
    >>> id_maker()
    '5'
    >>> id_maker()
    '6'

    """
    n = [start]
    def _new_id():
        n[0] += 1
        return str(n[0])
    return _new_id

def id_for(*args):
    """Calculates a valid hyphenated id given an arbitrary string.

    >>> id_for('Test 123')
    'test-123'
    >>> id_for('New Record')
    'new-record'
    >>> id_for('New "special" Record')
    'new-special-record'
    >>> id_for("hi", "test")
    'hi~test'
    >>> id_for("hi test")
    'hi-test'
    >>> id_for("hi-test")
    'hi-test'
    >>> id_for(1234)
    '1234'
    >>> id_for('this %$&#@^is##-$&*!it')
    'this-is-it'
    >>> id_for('test-this')
    'test-this'
    """
    def id_(text):
        return str(text).strip().translate(allowed).lower().replace(' ', '-')

    return '~'.join([id_(arg) for arg in args])

def name_for(text):
    """Calculates a valid underscored name given an arbitrary string.

    >>> name_for('Test 123')
    'test_123'
    >>> name_for('New Record')
    'new_record'
    >>> name_for('New "special" Record')
    'new_special_record'
    >>> name_for("hi test")
    'hi_test'
    >>> name_for("hi-test")
    'hi_test'
    >>> name_for(1234)
    '1234'
    >>> name_for('this %$&#@^is##-$&*!it')
    'this_is_it'
    >>> name_for('test-this')
    'test_this'
    """
    return id_for(text).replace('-', '_')

def libpath(*args):
    """Returns the location of a standard gitdata-lib asset

    >>> import os
    >>> asset_path = libpath('assets', 'README.md')
    >>> os.path.exists(asset_path)
    True

    """
    realpath = os.path.realpath
    dirname = os.path.dirname
    join = os.path.join
    return realpath(join(realpath(dirname(__file__)), *args))

def thispath(*args):
    """Returns a location in the same directory as the

    >>> path = thispath('file.py')

    """
    realpath = os.path.realpath
    join = os.path.join
    root = realpath(os.path.dirname(inspect.stack()[1][1]))
    return join(root, *args)

def obfuscate(text):
    """obfuscate text so it is recognizable without divulging it

    >>> obfuscate('12345')
    '1***5'

    >>> obfuscate('')
    ''

    """
    return text[:1] + '*' * (len(text) - 2) + text[-1:]

def parents(path):
    """Return list of parent directories"""
    if not os.path.isdir(path):
        return parents(os.path.split(os.path.abspath(path))[0])
    parent = os.path.abspath(os.path.join(path, os.pardir))
    if path == parent:
        return []
    else:
        return [path] + parents(parent)

def trim(text):
    """Remove excess spaces from a block of text

    >>> trim('remove right ')
    'remove right'

    >>> trim(' remove left')
    'remove left'

    >>> print(trim(' remove spaces\\n    from block\\n    of text'))
    remove spaces
       from block
       of text

    >>> print(
    ...     trim(
    ...     '    \\n'
    ...     '    remove spaces\\n'
    ...     '        from block\\n'
    ...     '        of text\\n'
    ...     '    \\n'
    ...     '\\n'
    ...     )
    ... )
    <BLANKLINE>
    remove spaces
        from block
        of text
    <BLANKLINE>
    <BLANKLINE>

    >>> print(trim('    remove spaces\\n  from block\\n  of text\\n    '))
      remove spaces
    from block
    of text
    <BLANKLINE>

    >>> print(trim('    remove spaces\\n  from block\\n  of text'))
      remove spaces
    from block
    of text

    >>> print(trim('\\n  remove spaces\\n    from block\\n  of text'))
    <BLANKLINE>
    remove spaces
      from block
    of text

    >>> text = '\\nremove spaces  \\n    from block\\nof text'
    >>> print('\\n'.join(repr(t) for t in trim(text).splitlines()))
    'remove spaces  '
    '    from block'
    'of text'

    >>> text = (
    ...     '\\nremove spaces'
    ...     '\\n    from block'
    ... )
    >>> print(trim(text))
    remove spaces
        from block

    """
    trim_size = None
    lines = text.splitlines()
    for line in lines:
        if not line or line.isspace():
            continue
        n = len(line) - len(line.lstrip())
        trim_size = min([trim_size, n]) if trim_size is not None else n
    if trim_size:
        result = []
        for line in lines:
            result.append(line[trim_size:])
        return '\n'.join(result)
    else:
        return text.strip()

def sorted_column_names(names):
    """Return column names sorted in a more typically expected order"""

    def looks_like_an_id(text):
        return text.endswith('_id')

    id_names = sorted(name for name in names if looks_like_an_id(name))
    special_names = id_names + [
        'id', 'userid', 'groupid', 'key',
        'name', 'title', 'description',
        'first_name', 'middle_name', 'last_name', 'fname', 'lname'
    ]
    result = []
    for name in special_names:
        if name in names:
            result.append(name)
    for name in sorted(names, key=lambda a: (len(a), a)):
        if name not in result:
            result.append(name)
    return result

def kind(o):
    """
    returns a text version of an object class name
    """
    n = []
    for c in o.__class__.__name__:
        if c.isalpha() or c == '_':
            if c.isupper() and len(n):
                n.append('_')
            n.append(c.lower())
    return ''.join(n)

class Bunch:
    """a handy bunch of variables

    >>> something = Bunch(name='Cat', colour='Brown')
    >>> something.name
    'Cat'
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

class Record(dict):
    """Record Class

    A dict that provides attribute access, printability and
    attribute access.

    >>> import decimal
    >>> class Person(Record):
    ...    @property
    ...    def full_name(self):
    ...        return ' '.join([self.name, self.surname])
    >>> joe = Person(name='Joe', surname='Smith')
    >>> joe.name
    'Joe'
    >>> joe['name']
    'Joe'

    >>> joe.age = 20
    >>> joe['age']
    20

    >>> joe.full_name
    'Joe Smith'

    >>> del joe.age
    >>> joe.age

    >>> try:
    ...     del joe.missing
    ... except AttributeError:
    ...     print('missing attribute cannot be deleted!')
    missing attribute cannot be deleted!

    >>> try:
    ...     joe.missing
    ... except KeyError:
    ...     print('missing attribute should not raise exception!')

    >>> joe.missing

    >>> print(Person(name='Pat', surname='Jones', age=21, salary=decimal.Decimal(1000)))
    Person
      name ................: 'Pat'
      age .................: 21
      salary ..............: Decimal('1000')
      surname .............: 'Jones'
      full_name ...........: 'Pat Jones'

    >>> Person(name='Pat', surname='Jones', age=21, salary=decimal.Decimal(1000))
    <Person {'name': 'Pat', 'age': 21, 'salary': Decimal('1000'), 'surname': 'Jones', 'full_name': 'Pat Jones'}>

    """

    __setattr__ = dict.__setitem__

    def __getitem__(self, name):
        try:
            value = dict.__getitem__(self, name)
            if hasattr(value, '__get__'):
                getter = getattr(value, '__get__')
                if getter is not None:
                    return getter(self)
                else:
                    return value
            else:
                return value
        except KeyError as k:
            try:
                value = self.__class__.__dict__[name]
                if hasattr(value, '__get__'):
                    getter = getattr(value, '__get__')
                    if getter:
                        return getter(self)
                    else:
                        return value
                else:
                    return value
            except KeyError as k:
                raise k

    def __getattr__(self, key):
        try:
            return self.__getitem__(key)
        except KeyError:
            return None

    def __delattr__(self, key):
        try:
            del self[key]
        except KeyError as k:
            raise AttributeError from k

    def attributes(self):
        """Return propoerties and members as one set"""
        return get_attributes(self)

    def __str__(self):
        name = self.__class__.__name__
        attributes = self.attributes()
        t = []

        items = [
            (key, getattr(self, key, None)) for key in attributes
            if not key.startswith('_')
        ]

        for key, value in items:
            if callable(value):
                v = value()
            else:
                v = value
            t.append('  {} {}: {!r}'.format(
                key,
                '.'*(20-len(key[:20])),
                v
            ))
        return '\n'.join([name] + t)

    def __repr__(self):
        name = self.__class__.__name__
        attributes = self.attributes()
        t = []

        items = [
            (key, getattr(self, key, None)) for key in attributes
            if not key.startswith('_')
        ]

        for key, value in items:
            if callable(value):
                v = value()
            else:
                v = value
            t.append((repr(key), repr(v)))
        return '<%s {%s}>' % (
            name, ', '.join('%s: %s' % (k, v) for k, v in t)
        )

    def save(self):
        """save record to originating store"""
        record_id = self['__store'].put(self)
        key = self['__store'].id_name
        logger.debug(
            'saved record %s(%s=%r) to %r',
            self.__class__.__name__,
            key,
            self[key],
            self['__store']
        )
        return record_id

class ItemList(list):
    """
    list of data items

    >>> items = ItemList()
    >>> items.append(['Joe', 12, 125])
    >>> items
    [['Joe', 12, 125]]
    >>> print(items)
    Column 0 Column 1 Column 2
    -------- -------- --------
    Joe            12      125

    >>> items.insert(0, ['Name', 'Score', 'Points'])
    >>> print(items)
    Name Score Points
    ---- ----- ------
    Joe     12    125

    >>> data = [
    ...     ['Joe', 12, 125],
    ...     ['Sally', 13, 1354],
    ... ]
    >>> items = ItemList(data)
    >>> print(items)
    Column 0 Column 1 Column 2
    -------- -------- --------
    Joe            12      125
    Sally          13    1,354

    >>> data = [
    ...     ['Joe', 12, 125],
    ...     ['Sally', 13, 135],
    ... ]
    >>> items = ItemList(data, labels=['Name', 'Score', 'Points'])
    >>> print(items)
    Name  Score Points
    ----- ----- ------
    Joe      12    125
    Sally    13    135

    >>> data = [
    ...     [10000, 'Joe', 12, 125],
    ...     [10001, 'Sally', 13, 135],
    ... ]
    >>> items = ItemList(data, labels=['_id', 'Name', 'Score', 'Points'])
    >>> print(items)
    _id   Name  Score Points
    ----- ----- ----- ------
    10000 Joe      12    125
    10001 Sally    13    135

    >>> now = datetime.date(2020, 2, 1)
    >>> data = [
    ...     [10000, 'Joe', now - datetime.date(1980, 1, 20)],
    ...     [10001, 'Sally', now - datetime.date(1984, 9, 20)],
    ... ]
    >>> items = ItemList(data, labels=['_id', 'Name', 'Age'])
    >>> print(items)
    _id   Name  Age
    ----- ----- -------------------
    10000 Joe   14622 days, 0:00:00
    10001 Sally 12917 days, 0:00:00

    >>> now = datetime.date(2020, 2, 1)
    >>> data = [
    ...     [10000, 'Joe', now - datetime.date(1980, 1, 20)],
    ...     [10001, 'Sally', now - datetime.date(1984, 9, 20)],
    ... ]
    >>> items = ItemList(data, labels=['user_id', 'Name', 'Age'])
    >>> print(items)
    user_id Name  Age
    ------- ----- -------------------
      10000 Joe   14622 days, 0:00:00
      10001 Sally 12917 days, 0:00:00

    >>> import datetime
    >>> now = datetime.date(2020, 2, 1)
    >>> data = [
    ...     [9000, 'Greens', 1234],
    ...     [10000, 'Browns', 1203],
    ... ]
    >>> items = ItemList(data, labels=['id', 'customer', 'invoice_number'])
    >>> print(items)
    id    customer invoice_number
    ----- -------- --------------
     9000 Greens             1234
    10000 Browns             1203
    """
    def __init__(self, *args, **kwargs):
        self.labels = kwargs.pop('labels', None)
        list.__init__(self, *args, **kwargs)

    def __str__(self):
        def is_text(value):
            return type(value) in [str, bytes]

        def name_column(number):
            return 'Column {}'.format(number)

        def is_homogeneous(values):
            return any([
                len(values) <= 1,
                all(type(values[0]) == type(i) for i in values[1:]),
            ])

        def get_format(label, values):
            first_non_null = list(
                map(type, [a for a in values if a is not None])
            )[:1]
            if first_non_null:
                data_type = first_non_null[0]
                if label in ['_id', 'userid']:
                    return '{:>{width}}'
                elif label == 'id':
                    return '{:>{width}}'
                elif label.endswith('_id'):
                    return '{:>{width}}'
                elif label.endswith('_number'):
                    return '{:>{width}}'
                elif data_type in [int, float, decimal.Decimal]:
                    return '{:{width},}'
                elif data_type in [datetime.date]:
                    return '{:%Y-%m-%d}'
                elif data_type in [datetime.timedelta]:
                    return '{:}'
                elif data_type in [datetime.datetime]:
                    return '{:%Y-%m-%d %H:%M:%S}'
            return '{:<{width}}'

        def nvl(value):
            if value is None:
                return 'None'
            elif isinstance(value, str):
                return value.strip()
            return value

        if len(self) == 0:
            return ''

        num_columns = len(list(self[0]))
        columns = list(range(num_columns))

        # calculate labels
        if self.labels:
            labels = self.labels
            offset = 0
        else:
            if not all(is_text(label) for label in self[0]):
                labels = [name_column(i) for i in range(num_columns)]
                offset = 0
            else:
                labels = self[0]
                offset = 1

        # rows containing data
        rows = [list(nvl(v) for v in row) for row in self[offset:]]

        # calculate formats
        formats = []
        for col in columns:
            values = [row[col] for row in rows]
            if is_homogeneous(values):
                formats.append(get_format(labels[col], values))
            else:
                formats.append('{}')

        # calulate formatted values
        formatted_values = [labels] + [
            [
                formats[col].format(row[col], width=0)
                for col in columns
            ] for row in rows
        ]

        # calculate column widths
        data_widths = {}
        for row in formatted_values:
            for col in columns:
                n = data_widths.get(col, 0)
                m = len(row[col])
                if n < m:
                    data_widths[col] = m

        label_format = '{:<{width}}'
        formatted_labels = [
            label_format.format(l, width=data_widths[i])
            for i, l in enumerate(labels)
        ]

        sorted_names = sorted_column_names(labels)

        for i, v in enumerate(formats):
            formats[i] = v if v != '{}' else '{:<{width}}'

        if not self.labels:
            columns = sorted(columns, key=lambda a: sorted_names.index(labels[a]))
        dashes = ['-' * data_widths[col] for col in columns]
        sorted_labels = [formatted_labels[col] for col in columns]

        aligned_rows = [sorted_labels] + [dashes] + [
            [
                formats[col].format(row[col], width=data_widths[col])
                for col in columns
            ] for row in rows
        ]

        lines = [' '.join(row).rstrip() for row in aligned_rows]

        return '\n'.join(lines)

def get_properties(obj):
    if type(obj) == dict:
        return []
    klass = obj.__class__
    return [
        i for i, j in (
            (n, getattr(klass, n)) for n in dir(klass)
        ) if type(j) == property
    ]

def get_attributes(obj):
    names = list(obj.keys()) + get_properties(obj)
    return sorted_column_names(names)

class RecordList(list):
    """a list of Records"""

    def __init__(self, *a, **k):
        list.__init__(self, *a, **k)
        self._n = 0

    def __iter__(self):
        self._n = 0
        return self

    def __next__(self):
        if self._n >= len(self):
            raise StopIteration
        else:
            result = self[self._n]
            self._n += 1
        return result

    def __str__(self):
        """
        represent as a string

        >>> import datetime
        >>> class Person(Record): pass
        >>> class People(RecordList): pass
        >>> people = People()
        >>> people.append(Person(_id=1, name='Joe', age=20,
        ...     birthdate=datetime.date(1992,5,5)))
        >>> people.append(Person(_id=2, name='Samuel', age=25,
        ...     birthdate=datetime.date(1992,4,5)))
        >>> people.append(Person(_id=3, name='Sam', age=35,
        ...     birthdate=datetime.date(1992,3,5)))
        >>> print(people)
        person
        _id name   age birthdate
        --- ------ --- ----------
          1 Joe     20 1992-05-05
          2 Samuel  25 1992-04-05
          3 Sam     35 1992-03-05
        3 person records

        >>> class Player(Person):
        ...    @property
        ...    def score(self):
        ...        return 10
        >>> people = People()
        >>> people.append(Player(userid=1, name='Joe', age=20,
        ...     birthdate=datetime.date(1992,5,5)))
        >>> people.append(Player(userid=2, name='Samuel', age=25,
        ...     birthdate=datetime.date(1992,4,5)))
        >>> people.append(Player(userid=3, name='Sam', age=35,
        ...     birthdate=datetime.date(1992,3,5)))
        >>> print(people)
        player
        userid name   age score birthdate
        ------ ------ --- ----- ----------
             1 Joe     20    10 1992-05-05
             2 Samuel  25    10 1992-04-05
             3 Sam     35    10 1992-03-05
        3 player records

        """

        def visible(name):
            hidden = getattr(self[0], 'hidden', None) or [] if self != [] else []
            return not name.startswith('__') and not name in hidden

        if not bool(self):
            return 'Empty list'

        title = '%s\n' % kind(self[0])

        keys = labels = list(filter(visible, get_attributes(self[0])))
        rows = [[getattr(record, key, record.get(key)) for key in keys] for record in self]

        footer = '\n{} {} records'.format(len(self), kind(self[0]))

        return title + str(ItemList(rows, labels=labels)) + footer

class OrderedSet(collections.MutableSet):
    """OrderedSet

    A set that preserves the order of the elements

    >>> s = OrderedSet('abracadaba')
    >>> t = OrderedSet('simsalabim')
    >>> print(s | t)
    OrderedSet(['a', 'b', 'r', 'c', 'd', 's', 'i', 'm', 'l'])
    >>> print(s & t)
    OrderedSet(['a', 'b'])
    >>> print(s - t)
    OrderedSet(['r', 'c', 'd'])
    >>> print(OrderedSet(reversed(s - t)))
    OrderedSet(['d', 'c', 'r'])
    >>> OrderedSet(['d', 'c', 'd']) == OrderedSet(['c', 'd', 'd'])
    False

    credit: http://code.activestate.com/recipes/576694/
    Licensed under MIT License
    """

    def __init__(self, iterable=None):
        self.end = end = []
        end += [None, end, end]         # sentinel node for doubly linked list
        self.map = {}                   # key --> [key, prev, next]
        if iterable is not None:
            self |= iterable

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

    def __contains__(self, key):
        """test if item is present

            >>> s = OrderedSet([1, 2, 3])
            >>> 'c' in s
            False
            >>> 2 in s
            True
        """
        return key in self.map

    def add(self, key):
        """add an item

            >>> s = OrderedSet([1, 2, 3])
            >>> s.add(4)
            >>> s
            OrderedSet([1, 2, 3, 4])
        """
        if key not in self.map:
            end = self.end
            curr = end[1]
            curr[2] = end[1] = self.map[key] = [key, curr, end]

    def discard(self, key):
        """discard an item by key

            >>> s = OrderedSet([1, 2, 3])
            >>> s.discard(1)
            >>> s
            OrderedSet([2, 3])
        """
        if key in self.map:
            key, prev, _next = self.map.pop(key)
            prev[2] = _next
            _next[1] = prev

    def __iter__(self):
        end = self.end
        curr = end[2]
        while curr is not end:
            yield curr[0]
            curr = curr[2]

    def __reversed__(self):
        end = self.end
        curr = end[1]
        while curr is not end:
            yield curr[0]
            curr = curr[1]

    def pop(self, last=True):
        """pop an item

            >>> s = OrderedSet([1, 2, 3])
            >>> s.pop(2)
            3
            >>> s
            OrderedSet([1, 2])
        """
        if not self:
            raise KeyError('set is empty')
        key = self.end[1][0] if last else self.end[2][0]
        self.discard(key)
        return key

    def __repr__(self):
        if not self:
            return '%s()' % (self.__class__.__name__,)
        return '%s(%r)' % (self.__class__.__name__, list(self))

    def __eq__(self, other):
        if isinstance(other, OrderedSet):
            return len(self) == len(other) and list(self) == list(other)
        return set(self) == set(other)


##
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

import time
import cupy as cp
import cudf
import util

def merge_test(data_size, ncols, number_of_tables):
    data_start = 0
    data_step = 30

    total_rows = util.get_rows(data_size, ncols)
    rows_per_table = total_rows / number_of_tables

    tables = []
    for i in range(number_of_tables):
        tbl = util.create_sorted_cudf_df(ncols, rows_per_table, start=data_start, step=data_step)
        data_start += ncols * rows_per_table * (data_step / 3)
        tables.append(tbl)
#        print(" \ntable: " + str(i) + "\n")
#        print(tbl)

    t0 = time.time()
    merged = cudf.merge_sorted(tables)
    t1 = time.time()

#    print("\nmerged table: \n")
#    print(merged)

    delay = (t1 - t0) * 1000
#    print("merge delay: " + str(delay))
    return delay

def sort_or_quantile_test(ncols, data_size, dtype="int64", task_type="sort"):
    if task_type != "sort" and task_type != "quantile":
        raise ValueError("task_type can be either 'sort' or 'quantile'")

    total_rows = util.get_rows(data_size, ncols)
    if dtype == "int64":
        df = util.create_random_data_df(ncols, total_rows)
    elif dtype == "str":
        df = util.create_random_str_df(ncols, total_rows);

    print("memory usage: ", df.memory_usage())

    if task_type == "quantile":
        quantile_points = cp.linspace(0, 1, 100)

    t0 = time.time()
    if task_type == "sort":
        result_df = df.sort_values(by=df.columns)
    elif task_type == "quantile":
        result_df = df._quantiles(quantile_points, "NEAREST")
    t1 = time.time()

#    print("\nresult table: \n")
#    print(result_df)

    delay = (t1 - t0) * 1000
#    print("sort delay: " + str(delay))
    del df, result_df
    return delay

#####################################################

data_size = "100MB"
ncols = 1
number_of_tables = 20

tasks = ["sort", "quantile"]
types = ["int64", "str"]

for ttype in tasks:
    for dtype in types:
        print(f"testing {ttype} and {dtype}")
        delay = sort_or_quantile_test(ncols, data_size, dtype, ttype)
        print(f"{ttype} {dtype} delay: ", delay)

# for number_of_tables in range(5, 30, 5):
#     duration = merge_test(data_size, ncols, number_of_tables)
#     print(number_of_tables, " merge duration: ", duration)


from functools import reduce

def run():

    my_list = [1, 4, 5, 6, 9, 13, 19, 21]
    #Usando List Comprehensions
    odd = [i for i in my_list if i % 2 != 0]
    print(odd)

    #Usando Filter
    filter_odd = list(filter(lambda x: x%2 != 0, my_list))  #La funcion filter devuelve T or F, en caso de F se reduce la lista por ese filtro
    print(filter_odd)
##---------------------------------------------------------------------
    my_list2 = [1, 2, 3, 4, 5]
    #Usando List Comprehensions
    squares = [i**2 for i in my_list2]
    print(squares)

    #Usando Map     #En map el output tiene la misma cantidad de elementos que el input.
    map_squares = list(map(lambda x: x**2, my_list2))   #x itera sobre el segundo elemento de map "my_list2"
    print(map_squares)
##---------------------------------------------------------------------
    my_list3 = [2, 2, 2, 2, 2]
    #Usando ciclo for
    all_multiplied = 1

    for i in my_list3:
        all_multiplied = all_multiplied * i

    print(all_multiplied)

    #Usando reduce
    reduce_all_multiplied = reduce(lambda a,b: a*b, my_list3)   #a toma el primer elemento de la lista, en la segunda iteracion, la operacion se veulve el primer elemento de la lista.
    print(reduce_all_multiplied)

if __name__ == '__main__':
    run()


from pyrogram import ContinuePropagation
from pyrogram.client import Client
from pyrogram.types.messages_and_media.message import Message

from bot import (
    SESSION_STRING, BOT_TOKEN, API_HASH, API_ID,
    logger
)
from bot.filters import CustomFilters
from bot.helpers.utils import is_admin, is_sudo_user

app = None
if SESSION_STRING != '':
    app = Client(
                SESSION_STRING,
                api_hash=API_HASH,
                api_id=API_ID,
            )
    logger.info("Logging in using SESSION_STRING")
else:
    app = Client(
                session_name='req_delete_bot',
                api_hash=API_HASH,
                api_id=API_ID,
                bot_token=BOT_TOKEN,
            )
    logger.info("Logging in using BOT_TOKEN")

@app.on_message(filters=CustomFilters.start_cmd_filter)
async def start_command(client: Client, message: Message):

    user = message.from_user
    if user is None:
        raise ContinuePropagation

    if (not await is_sudo_user(user)) and (not await is_admin(client, user)):
        raise ContinuePropagation

    await message.reply_text(
        text="Hi I am up and tracking the requests ðŸ˜Ž",
        quote=True
    )

    raise ContinuePropagation


# Zoey Samples
# Created: Jun 12, 2018
# CompareMagn.py
# Last Updated: Jun 21, 2018

import matplotlib.pyplot as plt
import matplotlib.colors as colors
from BinaryLens import BinaryLens as BL
import MulensModel as mm
import numpy as np

"""
Notes:
This code plots the magnification of points on a grid using the given
input parameters and compares it that determined by MulensModel.
"""

# Input parameters
s = 3
q = 1e-3
res = int(150)
origins = ['plan', 'geo_cent']
solvers = ['SG12']
"""
region = 'custom'
region_lim = [-400, 120, -200, 200]
"""
region='caustic'
region_lim=None
param = []
plot = []

for (j, solver) in enumerate(solvers):
	for (k, origin) in enumerate(origins):
		param.append({'s': s, 'q': q, 'res': res, 'origin': origin,
						'solver': solver})
		plot.append(BL(**param[-1]))
		plot[-1].get_position_arrays(region=region, region_lim=region_lim)
		plot[-1].get_magnification_array()

x_array = plot[0].x_array
y_array = plot[0].y_array
mag_BL = [None]*len(plot)
for (i,p) in enumerate(plot):
	mag_BL[i] = p.magn_array

blens = []
mag_MM = []

# Assuming the total system's mass is 1 unit:
blens = (mm.BinaryLens(mass_1 = 1./(1. + q), mass_2 = q/(1. + q),
									separation = s))
"""
# Assuming the star's mass is 1 unit:
blens = (mm.BinaryLens(mass_1=1, mass_2=q, separation=s))
"""

for i in range(len(x_array)):
	mag_MM.append(blens._point_source_WM95(source_x=-x_array[i], source_y=y_array[i]))

"""
plt.scatter(x_array, y_array, c = mag_BL[0])
plt.show()
plt.scatter(x_com, y_com, c = mag_MM[0])
plt.show()
"""

# This will get rid of extremely high errors so that we can focus on the
# common data. To look at all data including extremely high values, set
# to False.
remove_outliers = False
if remove_outliers:
	for (i, magn) in enumerate(mag_BL):
		for (j, m) in enumerate(magn):
			if m > max(mag_MM):
				mag_BL[i][j] = max(mag_MM)

# Plot the relative magnification
if False:
	for (i, p) in enumerate(plot):
		# This makes a plot of the difference in magnification from the parameters
		# at the top of the code vs. the MulensModel calculation
		print('The maximum value of magnification from MulensModel is',
			  max(mag_MM))
		plt.scatter(x_array, y_array, c = (mag_MM / mag_BL[i]),
					s=(400/res)**2, marker = 'o', cmap='plasma', lw=None)
		mag_plot = plt.colorbar()
		mag_plot.ax.tick_params(labelsize=10)
		mag_plot.set_label('Relative Magnification (MulensModel / BinaryLens)')
		plt.xlabel('X-position of source', fontsize = 12)
		plt.ylabel('Y-position of source', fontsize = 12)
		plt.xlim(min(x_array), max(x_array))
		plt.ylim(min(y_array), max(y_array))
		plt.title('Ratio of Magnification:\n{} Frame, {} Solver, q={}'.format(
								p.origin_title, p.solver_title, p.q))
		plt.gcf().set_size_inches(8, 6)
		plt.show()

# Plot the individual magnifications:
if True:
	for (i, p) in enumerate(plot):
		# This makes a plot of the difference in magnification from the parameters
		# at the top of the code vs. the MulensModel calculation
		print('The maximum value of magnification from MulensModel is',
			  max(mag_MM))
		p.plot_magnification(region=region, region_lim=region_lim, log_colorbar=False)
		caustic = mm.Caustics(s=s, q=p.q)
		caustic.plot(s=1)
		plt.show()

#	kwarg['norm'] = colors.LogNorm()
	plt.scatter(x_array, y_array, c=mag_MM, s=(400/res)**2,
				cmap='plasma', lw=None)
	mag_plot = plt.colorbar()
	mag_plot.ax.tick_params(labelsize=10)
	mag_plot.set_label('Magnification via MulensModel')
	plt.xlabel('X-position of source', fontsize = 12)
	plt.ylabel('Y-position of source', fontsize = 12)
	plt.xlim(min(x_array), max(x_array))
	plt.ylim(min(y_array), max(y_array))
	plt.title('Ratio of Magnification:\n{} Frame, {} Solver, q={}'.format(
							p.origin_title, p.solver_title, p.q))
	plt.gcf().set_size_inches(8, 6)
	caustic = mm.Caustics(s=s, q=p.q)
	caustic.plot(s=1)
	plt.show()


from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from metafeatures import get_metafeatures_dataset, get_histogram
from tensorflow.keras.models import load_model
from unary_transformations import Transformations as Unary_transformations
from binary_transformations import Transformations as Binary_transformations
from sklearn.preprocessing import LabelEncoder
from sklearn.neighbors import NearestNeighbors
import pandas as pd

trans_symbols = ['log', 'sqrt', 'sqr', 'round', 'freq', 'tanh', 'sig', 'z', 'reci', 'minmax', 'std_scaler']

operations = ['+','-','*','/','%']

def preprocess_dataset(df):
    # Preprocess data
    df = df.replace('?', np.nan)

    # df = df.fillna(df.mean())

    X_raw = df.iloc[:,0:-1]
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    X_raw = X_raw.select_dtypes(include=numerics)
    X_raw = X_raw.fillna(X_raw.mean())
    X_raw = X_raw.astype('float32')

    le = LabelEncoder()
    y_raw = df.iloc[:,-1].values
    y_raw = le.fit_transform(y_raw)

    return X_raw, y_raw

def get_dataset_unary_encoding(X, y):

    ds = {
        'X': X,
        'y': y,
        'dataset_name': 'test'
    }

    mf, hist = get_metafeatures_dataset(ds), get_histogram(ds)

    ds_encodings = []

    for f_i in range(len(ds['X'].T)):
        encoding = np.concatenate( (mf, hist[f_i]),axis = 0)
        ds_encodings.append(encoding)

    ds_encodings = np.array(ds_encodings)
    ds_encodings = StandardScaler().fit_transform(ds_encodings)
    return ds_encodings

def transform_unary(X, y, column_names, TRM_dataset):

    new_column_names = []
    new_features = []

    # Get the encodings of all the features in dataset
    ds_encodings = get_dataset_unary_encoding(X, y)

    # check for NaN values
    if ( np.isnan(ds_encodings).any() or np.isinf(ds_encodings).any()):
        ds_encodings = np.nan_to_num(ds_encodings)

    # Get 1 neighbor for each feature
    nbrs = NearestNeighbors(n_neighbors=1, algorithm='ball_tree',metric='euclidean', n_jobs = -1).fit(TRM_dataset[:, :-1])
    indices = nbrs.kneighbors(ds_encodings, return_distance = False)

    # Iterate in t indices
    for neigh_index, f_index in zip(indices, range(len(X.T))):

        t_index = int(TRM_dataset[neigh_index,-1])

        if t_index == len(Unary_transformations):
            # No suitable transformation found
            continue

        # The feature is already there
        new_column_name = f'{trans_symbols[t_index]}({column_names[f_index]})'
        if (new_column_name in new_column_names or new_column_name in column_names):
            continue

        success, t_values = Unary_transformations[t_index](X[:,f_index])

        if success and not np.isnan(t_values).any():
            new_features.append(t_values)
            new_column_names.append(new_column_name)

    if len(new_features) > 0:
        new_features = np.array(new_features)
        return new_features.T, new_column_names
    else:
        return None, None

def transform_binary(X, y, column_names, TRM_binary_dataset):
    ds = {
        'X': X,
        'y': y,
        'dataset_name': 'test'
    }

    new_column_names = []
    new_features = []

    nbrs = NearestNeighbors(n_neighbors=1, algorithm='ball_tree',metric='euclidean', n_jobs = -1).fit(TRM_binary_dataset[:, :-1])

    # Get the encodings of all the features in dataset
    mf, hist = get_metafeatures_dataset(ds), get_histogram(ds)

    for f_i in range(len(X.T)):
        for f_j in range(f_i + 1, len(X.T)):

            encoding = np.concatenate( (mf, hist[f_i], hist[f_j] ), axis = 0)

            # Get 1 neighbor for each feature
            neigh_index = nbrs.kneighbors([encoding], return_distance = False)

            t_index = int(TRM_binary_dataset[neigh_index, -1])

            if t_index == len(Binary_transformations):
                # No suitable transformation found
                continue

            # Test if the candidate feature is already there
            new_col_name = f"{operations[t_index]}({column_names[f_i]},{column_names[f_j]})"
            if (new_col_name in new_column_names or new_col_name in column_names):
                continue

            t_f = Binary_transformations[t_index](X[:,f_i], X[:,f_j])

            # if we made the transformation, then we check if there is any NaN or Inf value
            if ( np.isnan(t_f).any() or np.isinf(t_f).any()):
                continue

            new_features.append(t_f)
            new_column_names.append(new_col_name)
            break

    if len(new_features) > 0:
        new_features = np.array(new_features)
        return new_features.T, new_column_names
    else:
        return None, None

def transform_scaler(X, y, column_names, TRM_scaler):

    ds = {
        'X': X,
        'y': y,
        'dataset_name': 'test'
    }

    scalers = [RobustScaler, StandardScaler, MinMaxScaler]

    # Get the encoding of current dataset
    ds_encoding = get_metafeatures_dataset(ds)

    # check for NaN values
    if ( np.isnan(ds_encoding).any() or np.isinf(ds_encoding).any()):
        ds_encoding = np.nan_to_num(ds_encoding)

    # get similar dataset
    nbrs = NearestNeighbors(n_neighbors=1, algorithm='ball_tree',metric='euclidean', n_jobs = -1).fit(TRM_scaler[:, :-1])
    neigh_index = nbrs.kneighbors([ds_encoding], return_distance = False)[0]

    # get scaler index
    t_index = int(TRM_scaler[neigh_index,-1])

    # apply scaling
    scaler = scalers[t_index]()
    X_scaled = scaler.fit_transform(X)

    # rename columns
    scaler_prefix = ['robust_s', 'std_s', 'minmax_s']
    scaled_column_names = []
    for col_name in column_names:
        scaled_column_names.append(f"{scaler_prefix[t_index]}({col_name})")

    # create new scaled DF
    df_scaled = pd.DataFrame(X_scaled, columns = scaled_column_names)
    df_scaled['class'] = y

    return df_scaled


from flask import Blueprint, jsonify, request
from app.utils import crypto
from shop.services.shop_service import shop_service
from shop.validations.shop_validation import shop_data_validation

shop = Blueprint("shop", __name__)

@shop.route('/all-shops', method=["GET"])
def all_shops():
    try:
        shops = shop_service.all_shops()
    except Exception as e:

        return jsonify(dict(success=False, message=str(e)))

    return jsonify(shops)

@shop.route('/shops-by-user', method=["GET"])
def shops_by_user():
    try:
        shops = shop_service.get_shops_of_user(request.json.get("user_id"))
    except Exception as e:

        return jsonify(dict(success=False, message=str(e)))

    return jsonify(shops)

@shop.route('/filter-shops', method=["GET"])
def filter_shops():
    pass

@shop.route('/shop-details', method=["GET"])
def shop_details():
    try:
        decrypted_id = crypto.decrypt_id(request.json.get("shop_id"))
        shop_data = shop_service.shop_details(decrypted_id)
    except Exception as e:

        return jsonify(dict(success=False, message=str(e)))

    return jsonify(shop_data)

@shop.route('/create-shop', method=["POST"])
def create_shop():
    try:
        validation_response = shop_data_validation.shop_create_schema(
            request.json)
        if not validation_response["success"]:
            return validation_response

        response = shop_service.store_shop(
            request.json.get("name"),
            request.json.get("user_id"),
            request.json.get("category_id"),
            request.json.get("address"),
            request.json.get("size")
        )
    except Exception as e:

        return jsonify(dict(success=False, message=str(e)))

    return jsonify(response)

@shop.route('/edit-shop', method=["POST"])
def edit_shops():
    try:
        validation_response = shop_data_validation.shop_edit_data_validation(
            request.json)
        if not validation_response["success"]:
            return validation_response

        response = shop_service.update_shop(
            request.json.get("shop_id"),
            request.json.get("name"),
            request.json.get("user_id"),
            request.json.get("category_id"),
            request.json.get("address"),
            request.json.get("size")
        )
    except Exception as e:

        return jsonify(dict(success=False, message=str(e)))

    return jsonify(response)


import os
import argparse
import logging
import time
import numpy as np
import scipy as scp
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Dict
from sklearn.cluster import KMeans

from src.utils import *

import warnings
warnings.filterwarnings('ignore')

def str_to_bool(value: str) -> bool:
    """
    This method converts boolean strings to boolean.
    :param value: String argument from command line.
    :return: Boolean based on the value field.
    """
    if value in ['true', 'True']:
        return True
    else:
        return False

def get_summary_stats(data_sets: Dict,
                      n_clusters: int = 4,
                      summary_stats_plots_path: str = 'plots/summary_statistics') -> None:
    """
    This method generates the summary statistics for the dataset at our hand.
    :param data_sets: This is the list of full training data, training data, validation data and the evaluation data.
    :param n_clusters: Number of clusters to implement the channel wise clustering analysis.
    :param summary_stats_plots_path: Location where the summary statistics plots are stored.
    :return: None
    """
    # Create a directory for the plots if it doesn't exist
    create_directory(summary_stats_plots_path)

    # The Exploratory Data Analysis is done for every dataset
    for name, data_set in data_sets.items():
        print(f'\nSummary stats for {name} ------------------------------------------------------------------\n')

        print(f'Number of Samples - {len(data_set)}')
        print(f'Shape of an Instance - {data_set[0][0].shape}')

        classes = np.array([instance[1] for instance in data_set])
        print(f'Class Distribution - \n{np.unique(classes, return_counts=True)}')

        data = np.array([instance[0] for instance in data_set])
        concatenated_data = np.concatenate(data, axis=1)
        correlation_matrix = np.corrcoef(concatenated_data)

        # Plotting the channel wise correlation heatmaps
        plt.figure(figsize=(20, 15))
        sns.heatmap(correlation_matrix)
        plt.title(f"Channel Wise Correlation Map for {name}", fontdict={'fontsize': 30})
        plt.savefig(f"./{summary_stats_plots_path}/{name}_heatmap.png")
        plt.show()

        # Trying out KMeans Clustering to see which channels cluster together
        kmeans = KMeans(n_clusters=n_clusters, random_state=0)
        kmeans.fit(correlation_matrix)
        cluster_labels = kmeans.labels_
        clusters = {}
        for cluster in range(n_clusters):
            clusters[cluster + 1] = []
        for channel, label in enumerate(cluster_labels):
            clusters[label + 1].append(channel + 1)
        print(f'Clustered Channels - \n{clusters}')
        x = np.linspace(1, 22, 22)
        y = cluster_labels + 1
        plt.figure(figsize=(15, 10))
        plt.scatter(x=x, y=y)
        plt.xlabel('Channel Number')
        plt.ylabel('Cluster Number')
        plt.title(f"Channel Wise Cluster Relation for {name}", fontdict={'fontsize': 30})
        plt.savefig(f"./{summary_stats_plots_path}/{name}_cluster_analysis.png")
        plt.show()

        actions = np.unique(classes)
        for action in actions:
            data = np.array([instance[0] for instance in data_set if instance[1] == action])
            concatenated_data = np.concatenate(data, axis=1)
            correlation_matrix = np.corrcoef(concatenated_data)
            plt.figure(figsize=(20, 15))
            sns.heatmap(correlation_matrix)
            plt.title(f"Channel Wise Correlation Map for {name} and Action = {action}", fontdict={'fontsize': 30})
            plt.savefig(f"./{summary_stats_plots_path}/{name}_action_{action}_heatmap.png")
            plt.show()

if __name__ == '__main__':
    cmdline_parser = argparse.ArgumentParser('Transformers for EEG')
    cmdline_parser.add_argument('-d', '--dataset_name',
                                default='BNCI2014001',
                                help='The name of the dataset to fetch',
                                type=str)
    cmdline_parser.add_argument('-s', '--subject_ids',
                                default='1,2,3,4,5,6,7,8,9',
                                help='Data of the Subject IDs to fetch',
                                type=str)
    cmdline_parser.add_argument('-dfn', '--data_folder_name',
                                default='data',
                                help='Folder name to store the data',
                                type=str)
    cmdline_parser.add_argument('-lf', '--l_freq',
                                default=4.0,
                                help='The lower limit of the Bandpass Filter',
                                type=float)
    cmdline_parser.add_argument('-hf', '--h_freq',
                                default=38.0,
                                help='The higher limit of the Bandpass Filter',
                                type=float)
    cmdline_parser.add_argument('-emsf', '--ems_factor',
                                default=1e-3,
                                help='Factor to apply exponential moving standardization',
                                type=float)
    cmdline_parser.add_argument('-ibs', '--init_block_size',
                                default=1000,
                                help='Initial block size to implement exponential moving standardization',
                                type=int)
    cmdline_parser.add_argument('-tsos', '--trial_start_offset_seconds',
                                default=-0.5,
                                help='This represents the duration (in seconds) before the event of interest starts',
                                type=float)
    cmdline_parser.add_argument('-tss', '--training_set_size',
                                default=0.8,
                                help='Provide the training set size in percentage, like 0.7 or 0.8',
                                type=float)
    cmdline_parser.add_argument('-nc', '--n_clusters',
                                default=4,
                                help='Number of clusters to execute the channel wise clustering',
                                type=int)
    cmdline_parser.add_argument('-sspp', '--summary_stats_plots_path',
                                default='plots/summary_statistics',
                                help='Location where the summary statistics plots are stored',
                                type=str)
    cmdline_parser.add_argument('-v', '--verbose',
                                default='INFO',
                                choices=['INFO', 'DEBUG'],
                                help='verbosity')

    args, unknowns = cmdline_parser.parse_known_args()
    log_lvl = logging.INFO if args.verbose == 'INFO' else logging.DEBUG
    logging.basicConfig(level=log_lvl)

    # Below, we get the split datasets required for training purposes after fetching and preprocessing it
    full_train_set,\
        train_set,\
        valid_set,\
        eval_set = get_data_and_preprocess(dataset_dir=args.data_folder_name,
                                           dataset_name=args.dataset_name,
                                           subject_ids=args.subject_ids,
                                           l_freq=args.l_freq,
                                           h_freq=args.h_freq,
                                           ems_factor=args.ems_factor,
                                           init_block_size=args.init_block_size,
                                           trial_start_offset_seconds=args.trial_start_offset_seconds,
                                           training_set_size=args.training_set_size)

    # Now we generate the summary statistics for the split datasets
    datasets = {
        'Full Training Set': full_train_set,
        'Training Set': train_set,
        'Validation Set': valid_set,
        'Evaluation Set': eval_set
    }
    get_summary_stats(datasets,
                      n_clusters=args.n_clusters,
                      summary_stats_plots_path=args.summary_stats_plots_path)


from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import os
import codecs
import requests
#import mysql.connector
#import datetime
#import time

# Windows
#save_path = 'C:/Tmp/'
#path_to_chromedriver ='C:/Inst/chromedriver.exe'

# Linux
save_path = '/opt/dbrealtor/'
path_to_chromedriver ='/usr/bin/chromedriver'

def save_page(page_name, save_path, driver, link):
    file_name = page_name
    completeName = os.path.join(save_path, file_name)
    file_object = codecs.open(completeName, "w", "utf-8")
    driver.get(link)
    html = driver.page_source
    file_object.write(html)
    file_object.close()

chrome_options = Options()
chrome_options.add_argument("--headless")
#driver = webdriver.Chrome(
#    executable_path='C:/Inst/chromedriver.exe',
#    options=chrome_options)

driver = webdriver.Chrome(
    executable_path=path_to_chromedriver,
    options=chrome_options)

link = 'https://www.sreality.cz'
#link = 'https://www.javascript.com/'
#driver.get(link)
save_page('test_new.html', save_path, driver, link)
#save_page_get(link, save_path)
#driver.close()


from __future__ import annotations
from datetime import datetime
from functools import total_ordering
from qtpy.QtCore import QObject, Qt
from qtpy.QtGui import QBrush
from typing import Dict, List, Optional, Union
import enum
import logging

logger = logging.getLogger(__name__)

@total_ordering  # Fills in other ordering methods for us
class AlarmSeverity(enum.Enum):
    """
    An enum for the values that an alarm severity can take on. Not inheriting from str so we can do alarm logic
    based comparisons rather than string ones.
    """

    OK = "OK"
    MINOR_ACK = "MINOR_ACK"
    MAJOR_ACK = "MAJOR_ACK"
    INVALID_ACK = "INVALID_ACK"
    UNDEFINED_ACK = "UNDEFINED_ACK"
    MINOR = "MINOR"
    MAJOR = "MAJOR"
    INVALID = "INVALID"
    UNDEFINED = "UNDEFINED"

    def __lt__(self, other):
        """The order in which they are defined is in order of increasing severity for display"""
        if self.__class__ is other.__class__:
            values = [e for e in AlarmSeverity]
            return values.index(self) < values.index(other)
        return NotImplemented

class AlarmItem(QObject):
    """
    An AlarmItem represents the alarm status of a PV, along with all associated data.

    Parameters
    ----------
    name : str
        The name of the PV this alarm is associated with.
    path : str, optional
        The path to the alarm within its configured hierarchy. For example: Accelerator/Line 1/Voltage 2
    alarm_severity : AlarmSeverity, optional
        The current severity level of the alarm itself. May be higher than the PV if latching is enabled.
    alarm_status : str, optional
        The current status of the alarm itself.
    alarm_time : datetime, optional
        The date and time this alarm was raised.
    alarm_value : str, optional
        The current value of this alarm
    pv_severity : AlarmSeverity, optional
        The current severity level of the PV this alarm is associated with.
    pv_status : str, optional
        The current status of the PV this alarm is associated with.
    description : str, optional
        Additional description of this alarm specified by an end-user in a configuration file
    guidance : list, optional
        Any guidance on how to handle this alarm that would be helpful to a user viewing this alarm.
    displays : list, optional
        Any displays this alarm is associated with
    commands : list, optional
        Any commands that should be run in response to this alarm
    enabled : Union[bool, str], optional
        Whether or not this alarm is enabled. A bool in the simple case, a str here will also mean
        disabled, with the str being the date-time it should automatically re-enable.
    filtered: bool
        If true, this alarm is disabled via a filter rule, false otherwise.
    latching : bool, optional
        If set to true, this alarm will remain at the highest severity level it attains even if the PV it is monitoring
        returns to a lower severity level. If false, it will stay in sync with its associated PV.
    delay : int, optional
        The amount of time in seconds a PV must be in an alarm state before raising the alarm
        If not set, alarm is raised immediately.
    alarm_filter : str, optional
        An expression that allows an alarm to enable based on a different PV
    """

    def __init__(
        self,
        name: str,
        path: Optional[str] = None,
        alarm_severity: Optional[AlarmSeverity] = None,
        alarm_status: Optional[str] = None,
        alarm_time: Optional[datetime] = None,
        alarm_value: Optional[str] = None,
        pv_severity: Optional[AlarmSeverity] = None,
        pv_status: Optional[str] = None,
        description: Optional[str] = None,
        guidance: Optional[List[Dict]] = None,
        displays: Optional[List[Dict]] = None,
        commands: Optional[List[Dict]] = None,
        enabled: Optional[Union[bool, str]] = True,
        filtered: Optional[bool] = False,
        latching: Optional[bool] = False,
        annunciating: Optional[bool] = False,
        delay: Optional[int] = None,
        alarm_filter: Optional[str] = None,
    ):
        super().__init__()
        self.name = name
        self.path = path
        self.child_items = []
        self.parent_item = None
        self.alarm_severity = alarm_severity
        self.alarm_status = alarm_status
        self.alarm_time = alarm_time
        self.alarm_value = alarm_value
        self.pv_severity = pv_severity
        self.pv_status = pv_status
        self.description = description
        self.guidance = guidance
        self.displays = displays
        self.commands = commands
        if enabled is None:  # Protect against setting it explicitly to None
            enabled = True
        if filtered is None:
            filtered = False
        if latching is None:
            latching = False
        if annunciating is None:
            annunciating = False
        self.enabled = enabled
        self.filtered = filtered
        self.latching = latching
        self.annunciating = annunciating
        self.delay = delay
        self.alarm_filter = alarm_filter

    def is_leaf(self) -> bool:
        """Return whether or not this alarm is associated with a leaf node in its configured hierarchy"""
        return len(self.child_items) == 0

    def is_enabled(self) -> bool:
        """A convenience method for checking the enabled state of the alarm"""
        if self.filtered:
            return False
        if isinstance(self.enabled, bool):
            return self.enabled
        elif isinstance(self.enabled, str):
            if self.enabled:
                return False  # A non-empty string means a bypass until date has been set, so it is disabled
            else:
                return True
        else:
            logger.error(f"Enabled status for alarm: {self.path} is set to a bad value: {self.enabled}")

    def is_acknowledged(self) -> bool:
        """A convenience method for returning whether or not this item has been acknolwedged"""
        return self.alarm_severity in (
            AlarmSeverity.MINOR_ACK,
            AlarmSeverity.MAJOR_ACK,
            AlarmSeverity.INVALID_ACK,
            AlarmSeverity.UNDEFINED_ACK,
        )

    def is_in_active_alarm_state(self) -> bool:
        """A convenience method for returning whether or not this item is actively in an alarm state"""
        return self.alarm_severity in (
            AlarmSeverity.MINOR,
            AlarmSeverity.MAJOR,
            AlarmSeverity.INVALID,
            AlarmSeverity.UNDEFINED,
        )

    def display_color(self, severity) -> QBrush:
        """
        Return a QBrush with the appropriate color for drawing this alarm based on severity

        Parameters
        ----------
        severity : AlarmSeverity
            The severity to base the display color on
        """
        if not self.is_enabled() and self.is_leaf():
            return QBrush(Qt.gray)
        elif severity == AlarmSeverity.OK:
            return QBrush(Qt.darkGreen)
        elif severity == AlarmSeverity.UNDEFINED:
            return QBrush(Qt.magenta)
        elif severity == AlarmSeverity.MAJOR:
            return QBrush(Qt.red)
        elif severity == AlarmSeverity.MINOR:
            return QBrush(Qt.darkYellow)
        elif severity == AlarmSeverity.MAJOR_ACK:
            return QBrush(Qt.darkRed)
        elif severity == AlarmSeverity.MINOR_ACK:
            return QBrush(Qt.darkGray)
        elif severity == AlarmSeverity.UNDEFINED_ACK:
            return QBrush(Qt.darkMagenta)
        elif severity == AlarmSeverity.INVALID:
            return QBrush(Qt.magenta)
        elif severity == AlarmSeverity.INVALID_ACK:
            return QBrush(Qt.darkMagenta)

    def append_child(self, item: AlarmItem) -> None:
        """Add the input item as a child node for this alarm item"""
        self.child_items.append(item)

    def assign_parent(self, parent: AlarmItem) -> None:
        """Sets the input item as the parent of this alarm item"""
        self.parent_item = parent

    def child(self, row: int) -> AlarmItem:
        """Returns the child item at the input row, or None if nothing exists there"""
        if row < 0 or row >= len(self.child_items):
            return None
        return self.child_items[row]

    def child_count(self) -> int:
        """Return the number of children this item has"""
        return len(self.child_items)

    def row(self) -> int:
        """Return the row of this item relative to its parent, or zero if it has no parent"""
        if self.parent_item:
            return self.parent_item.child_items.index(self)
        return 0

    def column_count(self) -> int:
        """Return the column count of this item"""
        return 1

    def to_config_dict(self) -> Dict[str, any]:
        """Dump the current values of the alarm item into a dict that can be sent as a kafka alarm config message"""
        return {
            "description": self.description,
            "guidance": self.guidance,
            "displays": self.displays,
            "commands": self.commands,
            "enabled": self.enabled,
            "latching": self.latching,
            "annunciating": self.annunciating,
            "delta": self.delay,
            "filter": self.alarm_filter,
        }

    def __repr__(self) -> str:
        return (
            f'AlarmItem("{self.name}", {repr(self.path)}, {str(self.alarm_severity)}, {repr(self.alarm_status)}, '
            f"{repr(self.alarm_time)}, {repr(self.alarm_value)}, {str(self.pv_severity)}, {repr(self.pv_status)}, "
            f"{repr(self.description)}, {repr(self.guidance)}, {repr(self.displays)}, {repr(self.commands)}, "
            f"{self.enabled}, {self.filtered}, {self.latching}, {self.annunciating}, {self.delay}, {repr(self.alarm_filter)})"  # noqa 501
        )


""" Main runfile of the game """

import pygame
from menu_class import Menu
from level_class import Level
from dungeon_procedural_generation_algorithm import dungeon_generation

pygame.init()
pygame.mixer.init()

screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
clock = pygame.time.Clock()

FPS = 30
WIDTH, HEIGHT = pygame.display.get_surface().get_size()

TEXT_COLOR = (255, 255, 255)
arialblack_font = pygame.font.SysFont("arialblack", 40)

running = True
current_scene = "MenuScreen"

the_best_score = 0

enemies_quantity = 3
enemies_health = 10
enemies_damage = 7
enemies_vision = 225

potions_chance = enemies_quantity // 3
potions_heal = enemies_damage * 1.5

lava_damage = 0.1

hero_animations = [
    [
        'static/player/player_going_right_1.png',
        'static/player/player_going_right_2.png',
        'static/player/player_going_right_1.png',
        'static/player/player_going_right_2.png'
    ],
    [
        'static/player/player_standing_right_1.png',
        'static/player/player_standing_right_1.png',
        'static/player/player_standing_right_2.png',
        'static/player/player_standing_right_2.png'
    ]
]

dungeon_generation()

main_menu = Menu(screen)

current_level = Level(
    screen=screen, player_animations=hero_animations, enemies_quantity=enemies_quantity, FPS=FPS, clock=clock,
    enemies_stats={'quantity': enemies_quantity, 'health': enemies_health, 'damage': enemies_damage,
                   'vision': enemies_vision},
    potions_stats={'chance': potions_chance, 'heal': potions_heal}, lava_damage=lava_damage,
    the_best_score=the_best_score, player_current_health=100
)

def draw_text(text, font, text_color, x, y):
    img = font.render(text, True, text_color)
    screen.blit(img, (x, y))

def developers_page():
    draw_text("Это Лёша. Он фуллстекер", arialblack_font, TEXT_COLOR, 25, 25)
    zhabyich = pygame.image.load('static/zhabyich.png')
    screen.blit(zhabyich, (25, 100))
    draw_text("Это Варя. Она тоже фуллстекер", arialblack_font, TEXT_COLOR, 775, 25)
    zhabka = pygame.image.load('static/zhabka.png')
    screen.blit(zhabka, (900, 100))

def about_page():
    draw_text("Добро пожаловать в Самую долбанутую игру в мире!", arialblack_font, TEXT_COLOR, 25, 50)
    draw_text("В нашей игре есть ряд правил. Пожалуйста, соблюдайте их:", arialblack_font, TEXT_COLOR, 25, 100)
    draw_text("1. Попробуйте остаться в живых до самого конца и убить", arialblack_font, TEXT_COLOR, 40, 200)
    draw_text("как можно больше противников", arialblack_font, TEXT_COLOR, 50, 250)
    draw_text("2. Если вы нашли баг, то вас обманывают глаза (честно)", arialblack_font, TEXT_COLOR, 40, 300)
    draw_text("3. Я вас обманул, правил нет. Удачи!)", arialblack_font, TEXT_COLOR, 40, 350)
    player = pygame.transform.scale(pygame.image.load('static/player/player_standing_right_1.png'), (244, 175))
    mushroom = pygame.transform.scale(pygame.image.load('static/enemies/mushroom_1.png'), (134, 127))
    screen.blit(mushroom, ((screen.get_size()[0] - 134) // 2, screen.get_size()[1] - 300))
    screen.blit(mushroom, ((screen.get_size()[0] - 134) // 2 + 100, screen.get_size()[1] - 300))
    screen.blit(mushroom, ((screen.get_size()[0] - 134) // 2 + 200, screen.get_size()[1] - 300))
    screen.blit(mushroom, ((screen.get_size()[0] - 134) // 2 - 100, screen.get_size()[1] - 300))
    screen.blit(mushroom, ((screen.get_size()[0] - 134) // 2 - 200, screen.get_size()[1] - 300))
    screen.blit(player, ((screen.get_size()[0] - 244) // 2, screen.get_size()[1] - 200))
    draw_text("мама...", arialblack_font, TEXT_COLOR, (screen.get_size()[0] + 244) // 2 + 10, screen.get_size()[1] - 75)

def play_cycle():
    global current_level
    global enemies_quantity
    global enemies_health
    global enemies_damage
    global enemies_vision
    global potions_chance
    global potions_heal
    global the_best_score
    global lava_damage

    current_level.draw(screen, (WIDTH, HEIGHT))
    if len(current_level.enemies) == 0:
        if current_level.player.score > the_best_score:
            the_best_score = current_level.player.score if current_level.player.score > 0 else the_best_score

        enemies_quantity = round(enemies_quantity * 1.5) if round(enemies_quantity * 1.5) <= 20 else 20
        enemies_damage = round(enemies_damage * 1.5)
        enemies_vision = round(enemies_vision * 1.15)
        enemies_health = round(enemies_health * 1.5)
        potions_heal = enemies_damage * 1.5 if enemies_damage * 1.5 <= 20 else 20
        potions_chance += 0.5 if potions_heal + 0.5 <= 5 else 5
        lava_damage *= 1.05

        dungeon_generation()

        the_best_score = current_level.player.score if current_level.player.score > the_best_score else the_best_score

        current_level = Level(screen=screen, player_animations=hero_animations, enemies_quantity=enemies_quantity,
                              FPS=FPS, clock=clock,
                              enemies_stats={'quantity': enemies_quantity, 'health': enemies_health,
                                             'damage': enemies_damage, 'vision': enemies_vision},
                              potions_stats={'chance': potions_chance, 'heal': potions_heal},
                              lava_damage=lava_damage, the_best_score=the_best_score, player_current_health=current_level.player.hp)

    current_level.draw(screen, (WIDTH, HEIGHT))
    current_level.get_player().health_bar.render()
    current_level.get_player().stamina_bar.render()
    pygame.display.update()

def main_cycle():
    global running
    global current_scene

    while running:
        clock.tick(FPS)
        """Handler signals to exit from keyboard"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == 27:
                    if current_scene == "MenuScreen":
                        running = False
                    else:
                        current_scene = "MenuScreen"
        screen.fill('black')
        if current_scene == "MenuScreen":
            menu_context = main_menu.render()
            if menu_context["Start"]:
                current_scene = "GameScreen"
            if menu_context["Developers"]:
                current_scene = "DevelopersScreen"
            if menu_context["About"]:
                current_scene = "AboutScreen"
        elif current_scene == "GameScreen":
            play_cycle()
        elif current_scene == "DevelopersScreen":
            developers_page()
        elif current_scene == "AboutScreen":
            about_page()
        pygame.display.flip()

if __name__ == "__main__":
    main_cycle()