#!/usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005-2010 (ita)

"""
Task generators

The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code)
The instances can have various parameters, but the creation of task nodes (Task.py)
is always postponed. To achieve this, various methods are called from the method "apply"

"""

import copy, re, os
from waflib import Task, Utils, Logs, Errors, ConfigSet, Node

feats = Utils.defaultdict(set)
"""remember the methods declaring features"""

class task_gen(object):
    """
    Instances of this class create :py:class:`waflib.Task.TaskBase` when
    calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
    A few notes:

    * The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..)
    * The 'features' are used to add methods to self.meths and then execute them
    * The attribute 'path' is a node representing the location of the task generator
    * The tasks created are added to the attribute *tasks*
    * The attribute 'idx' is a counter of task generators in the same path
    """

    mappings = {}
    prec = Utils.defaultdict(list)

    def __init__(self, *k, **kw):
        """
        The task generator objects predefine various attributes (source, target) for possible
        processing by process_rule (make-like rules) or process_source (extensions, misc methods)

        The tasks are stored on the attribute 'tasks'. They are created by calling methods
        listed in self.meths *or* referenced in the attribute features
        A topological sort is performed to ease the method re-use.

        The extra key/value elements passed in kw are set as attributes
        """

        # so we will have to play with directed acyclic graphs
        # detect cycles, etc
        self.source = ''
        self.target = ''

        self.meths = []
        """
        List of method names to execute (it is usually a good idea to avoid touching this)
        """

        self.prec = Utils.defaultdict(list)
        """
        Precedence table for sorting the methods in self.meths
        """

        self.mappings = {}
        """
        List of mappings {extension -> function} for processing files by extension
        """

        self.features = []
        """
        List of feature names for bringing new methods in
        """

        self.tasks = []
        """
        List of tasks created.
        """

        if not 'bld' in kw:
            # task generators without a build context :-/
            self.env = ConfigSet.ConfigSet()
            self.idx = 0
            self.path = None
        else:
            self.bld = kw['bld']
            self.env = self.bld.env.derive()
            self.path = self.bld.path # emulate chdir when reading scripts

        for key, val in list(kw.items()):
            setattr(self, key, val)

    def __str__(self):
        """for debugging purposes"""
        return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())

    def __repr__(self):
        """for debugging purposes"""
        lst = []
        for x in list(self.__dict__.keys()):
            if x not in ['env', 'bld', 'compiled_tasks', 'tasks']:
                lst.append("%s=%s" % (x, repr(getattr(self, x))))
        return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())

    def get_name(self):
        """
        If not set, the name is computed from the target name::

            def build(bld):
                x = bld(name='foo')
                x.get_name() # foo
                y = bld(target='bar')
                y.get_name() # bar

        :rtype: string
        :return: name of this task generator
        """
        try:
            return self._name
        except AttributeError:
            if isinstance(self.target, list):
                lst = [str(x) for x in self.target]
                name = self._name = ','.join(lst)
            else:
                name = self._name = str(self.target)
            return name
    def set_name(self, name):
        self._name = name

    name = property(get_name, set_name)

    def to_list(self, val):
        """
        Ensure that a parameter is a list

        :type val: string or list of string
        :param val: input to return as a list
        :rtype: list
        """
        if isinstance(val, str): return val.split()
        else: return val

    def post(self):
        """
        Create task objects. The following operations are performed:

        #. The body of this method is called only once and sets the attribute ``posted``
        #. The attribute ``features`` is used to add more methods in ``self.meths``
        #. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec`
        #. The methods are then executed in order
        #. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks`
        """

        # we could add a decorator to let the task run once, but then python 2.3 will be difficult to support
        if getattr(self, 'posted', None):
            #error("OBJECT ALREADY POSTED" + str( self))
            return False
        self.posted = True

        keys = set(self.meths)

        # add the methods listed in the features
        self.features = Utils.to_list(self.features)
        for x in self.features + ['*']:
            st = feats[x]
            if not st:
                if not x in Task.classes:
                    Logs.warn('feature %r does not exist - bind at least one method to it' % x)
            keys.update(list(st)) # ironpython 2.7 wants the cast to list

        # copy the precedence table
        prec = {}
        prec_tbl = self.prec or task_gen.prec
        for x in prec_tbl:
            if x in keys:
                prec[x] = prec_tbl[x]

        # elements disconnected
        tmp = []
        for a in keys:
            for x in list(prec.values()):
                if a in x: break
            else:
                tmp.append(a)

        tmp.sort()

        # topological sort
        out = []
        while tmp:
            e = tmp.pop()
            if e in keys: out.append(e)
            try:
                nlst = prec[e]
            except KeyError:
                pass
            else:
                del prec[e]
                for x in nlst:
                    for y in prec:
                        if x in prec[y]:
                            break
                    else:
                        tmp.append(x)

        if prec:
            raise Errors.WafError('Cycle detected in the method execution %r' % prec)
        out.reverse()
        self.meths = out

        # then we run the methods in order
        Logs.debug('task_gen: posting %s %d' % (self, id(self)))
        for x in out:
            try:
                v = getattr(self, x)
            except AttributeError:
                raise Errors.WafError('%r is not a valid task generator method' % x)
            Logs.debug('task_gen: -> %s (%d)' % (x, id(self)))
            v()

        Logs.debug('task_gen: posted %s' % self.name)
        return True

    def get_hook(self, node):
        """
        :param node: Input file to process
        :type node: :py:class:`waflib.Tools.Node.Node`
        :return: A method able to process the input node by looking at the extension
        :rtype: function
        """
        name = node.name
        for k in self.mappings:
            if name.endswith(k):
                return self.mappings[k]
        for k in task_gen.mappings:
            if name.endswith(k):
                return task_gen.mappings[k]
        raise Errors.WafError("File %r has no mapping in %r (did you forget to load a waf tool?)" % (node, list(task_gen.mappings.keys())))

    def create_task(self, name, src=None, tgt=None):
        """
        Wrapper for creating task instances. The classes are retrieved from the
        context class if possible, then from the global dict Task.classes.

        :param name: task class name
        :type name: string
        :param src: input nodes
        :type src: list of :py:class:`waflib.Tools.Node.Node`
        :param tgt: output nodes
        :type tgt: list of :py:class:`waflib.Tools.Node.Node`
        :return: A task object
        :rtype: :py:class:`waflib.Task.TaskBase`
        """
        task = Task.classes[name](env=self.env.derive(), generator=self)
        if src:
            task.set_inputs(src)
        if tgt:
            task.set_outputs(tgt)
        self.tasks.append(task)
        return task

    def clone(self, env):
        """
        Make a copy of a task generator. Once the copy is made, it is necessary to ensure that the
        it does not create the same output files as the original, or the same files may
        be compiled several times.

        :param env: A configuration set
        :type env: :py:class:`waflib.ConfigSet.ConfigSet`
        :return: A copy
        :rtype: :py:class:`waflib.TaskGen.task_gen`
        """
        newobj = self.bld()
        for x in self.__dict__:
            if x in ['env', 'bld']:
                continue
            elif x in ['path', 'features']:
                setattr(newobj, x, getattr(self, x))
            else:
                setattr(newobj, x, copy.copy(getattr(self, x)))

        newobj.posted = False
        if isinstance(env, str):
            newobj.env = self.bld.all_envs[env].derive()
        else:
            newobj.env = env.derive()

        return newobj

def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
    ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
    """
    Create a new mapping and a task class for processing files by extension.
    See Tools/flex.py for an example.

    :param name: name for the task class
    :type name: string
    :param rule: function to execute or string to be compiled in a function
    :type rule: string or function
    :param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
    :type reentrant: int
    :param color: color for the task output
    :type color: string
    :param ext_in: execute the task only after the files of such extensions are created
    :type ext_in: list of string
    :param ext_out: execute the task only before files of such extensions are processed
    :type ext_out: list of string
    :param before: execute instances of this task before classes of the given names
    :type before: list of string
    :param after: execute instances of this task after classes of the given names
    :type after: list of string
    :param decider: if present, use it to create the output nodes for the task
    :type decider: function
    :param scan: scanner function for the task
    :type scan: function
    :param install_path: installation path for the output nodes
    :type install_path: string
    """
    ext_in = Utils.to_list(ext_in)
    ext_out = Utils.to_list(ext_out)
    if not name:
        name = rule
    cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)

    def x_file(self, node):
        ext = decider and decider(self, node) or cls.ext_out
        if ext_in:
            _ext_in = ext_in[0]

        tsk = self.create_task(name, node)
        cnt = 0

        keys = list(self.mappings.keys()) + list(self.__class__.mappings.keys())
        for x in ext:
            k = node.change_ext(x, ext_in=_ext_in)
            tsk.outputs.append(k)

            if reentrant != None:
                if cnt < int(reentrant):
                    self.source.append(k)
            else:
                for y in keys: # ~ nfile * nextensions :-/
                    if k.name.endswith(y):
                        self.source.append(k)
                        break
            cnt += 1

        if install_path:
            self.bld.install_files(install_path, tsk.outputs)
        return tsk

    for x in cls.ext_in:
        task_gen.mappings[x] = x_file
    return x_file

def taskgen_method(func):
    """
    Decorator: register a method as a task generator method.
    The function must accept a task generator as first parameter::

        from waflib.TaskGen import taskgen_method
        @taskgen_method
        def mymethod(self):
            pass

    :param func: task generator method to add
    :type func: function
    :rtype: function
    """
    setattr(task_gen, func.__name__, func)
    return func

def feature(*k):
    """
    Decorator: register a task generator method that will be executed when the
    object attribute 'feature' contains the corresponding key(s)::

        from waflib.Task import feature
        @feature('myfeature')
        def myfunction(self):
            print('that is my feature!')
        def build(bld):
            bld(features='myfeature')

    :param k: feature names
    :type k: list of string
    """
    def deco(func):
        setattr(task_gen, func.__name__, func)
        for name in k:
            feats[name].update([func.__name__])
        return func
    return deco

def before_method(*k):
    """
    Decorator: register a task generator method which will be executed
    before the functions of given name(s)::

        from waflib.TaskGen import feature, before
        @feature('myfeature')
        @before_method('fun2')
        def fun1(self):
            print('feature 1!')
        @feature('myfeature')
        def fun2(self):
            print('feature 2!')
        def build(bld):
            bld(features='myfeature')

    :param k: method names
    :type k: list of string
    """
    def deco(func):
        setattr(task_gen, func.__name__, func)
        for fun_name in k:
            if not func.__name__ in task_gen.prec[fun_name]:
                task_gen.prec[fun_name].append(func.__name__)
                #task_gen.prec[fun_name].sort()
        return func
    return deco
before = before_method

def after_method(*k):
    """
    Decorator: register a task generator method which will be executed
    after the functions of given name(s)::

        from waflib.TaskGen import feature, after
        @feature('myfeature')
        @after_method('fun2')
        def fun1(self):
            print('feature 1!')
        @feature('myfeature')
        def fun2(self):
            print('feature 2!')
        def build(bld):
            bld(features='myfeature')

    :param k: method names
    :type k: list of string
    """
    def deco(func):
        setattr(task_gen, func.__name__, func)
        for fun_name in k:
            if not fun_name in task_gen.prec[func.__name__]:
                task_gen.prec[func.__name__].append(fun_name)
                #task_gen.prec[func.__name__].sort()
        return func
    return deco
after = after_method

def extension(*k):
    """
    Decorator: register a task generator method which will be invoked during
    the processing of source files for the extension given::

        from waflib import Task
        class mytask(Task):
            run_str = 'cp ${SRC} ${TGT}'
        @extension('.moo')
        def create_maa_file(self, node):
            self.create_task('mytask', node, node.change_ext('.maa'))
        def build(bld):
            bld(source='foo.moo')
    """
    def deco(func):
        setattr(task_gen, func.__name__, func)
        for x in k:
            task_gen.mappings[x] = func
        return func
    return deco

# ---------------------------------------------------------------
# The following methods are task generator methods commonly used
# they are almost examples, the rest of waf core does not depend on them

@taskgen_method
def to_nodes(self, lst, path=None):
    """
    Convert the input list into a list of nodes.
    It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
    It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:

    :param lst: input list
    :type lst: list of string and nodes
    :param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
    :type path: :py:class:`waflib.Tools.Node.Node`
    :rtype: list of :py:class:`waflib.Tools.Node.Node`
    """
    tmp = []
    path = path or self.path
    find = path.find_resource

    if isinstance(lst, self.path.__class__):
        lst = [lst]

    # either a list or a string, convert to a list of nodes
    error_detected = False
    for x in Utils.to_list(lst):
        if isinstance(x, str):
            node = find(x)
            if not node:
                node = self.bld.root.find_resource(x)
        else:
            node = x
        if not node:
            error_detected = True
            Logs.error("(%s): source not found: %r in %r (%r)" % (self.bld.cmd, x, self.target, os.path.normpath(self.path.abspath() + '/wscript')))
        else:
            tmp.append(node)
    if error_detected:
        raise Errors.WafError("One or more source files missing")
    return tmp

@feature('*')
def process_source(self):
    """
    Process each element in the attribute ``source`` by extension.

    #. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
    #. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
    #. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
    #. When called, the methods may modify self.source to append more source to process
    #. The mappings can map an extension or a filename (see the code below)
    """
    self.source = self.to_nodes(getattr(self, 'source', []))
    for node in self.source:
        self.get_hook(node)(self, node)

@feature('*')
@before_method('process_source')
def process_rule(self):
    """
    Process the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::

        def build(bld):
            bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
    """
    if not getattr(self, 'rule', None):
        return

    # create the task class
    name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))

    # or we can put the class in a cache for performance reasons
    try:
        cache = self.bld.cache_rule_attr
    except AttributeError:
        cache = self.bld.cache_rule_attr = {}

    cls = None
    if getattr(self, 'cache_rule', 'True'):
        try:
            cls = cache[(name, self.rule)]
        except KeyError:
            pass
    if not cls:
        cls = Task.task_factory(name, self.rule,
            getattr(self, 'vars', []),
            shell=getattr(self, 'shell', True), color=getattr(self, 'color', 'BLUE'),
            scan = getattr(self, 'scan', None))
        if getattr(self, 'scan', None):
            cls.scan = self.scan
        elif getattr(self, 'deps', None):
            def scan(self):
                nodes = []
                for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
                    node = self.generator.path.find_resource(x)
                    if not node:
                        self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
                    nodes.append(node)
                return [nodes, []]
            cls.scan = scan

        if getattr(self, 'update_outputs', None):
            Task.update_outputs(cls)

        if getattr(self, 'always', None):
            Task.always_run(cls)

        for x in ['after', 'before', 'ext_in', 'ext_out']:
            setattr(cls, x, getattr(self, x, []))

        if getattr(self, 'cache_rule', 'True'):
            cache[(name, self.rule)] = cls

    # now create one instance
    tsk = self.create_task(name)

    if getattr(self, 'target', None):
        if isinstance(self.target, str):
            self.target = self.target.split()
        if not isinstance(self.target, list):
            self.target = [self.target]
        for x in self.target:
            if isinstance(x, str):
                tsk.outputs.append(self.path.find_or_declare(x))
            else:
                x.parent.mkdir() # if a node was given, create the required folders
                tsk.outputs.append(x)
        if getattr(self, 'install_path', None):
            # from waf 1.5
            # although convenient, it does not 1. allow to name the target file and 2. symlinks
            # TODO remove in waf 1.7
            self.bld.install_files(self.install_path, tsk.outputs)

    if getattr(self, 'source', None):
        tsk.inputs = self.to_nodes(self.source)
        # bypass the execution of process_source by setting the source to an empty list
        self.source = []

    if getattr(self, 'cwd', None):
        tsk.cwd = self.cwd

@feature('seq')
def sequence_order(self):
    """
    Add a strict sequential constraint between the tasks generated by task generators.
    It works because task generators are posted in order.
    It will not post objects which belong to other folders.

    Example::

        bld(features='javac seq')
        bld(features='jar seq')

    To start a new sequence, set the attribute seq_start, for example::

        obj = bld(features='seq')
        obj.seq_start = True

    Note that the method is executed in last position. This is more an
    example than a widely-used solution.
    """
    if self.meths and self.meths[-1] != 'sequence_order':
        self.meths.append('sequence_order')
        return

    if getattr(self, 'seq_start', None):
        return

    # all the tasks previously declared must be run before these
    if getattr(self.bld, 'prev', None):
        self.bld.prev.post()
        for x in self.bld.prev.tasks:
            for y in self.tasks:
                y.set_run_after(x)

    self.bld.prev = self

re_m4 = re.compile('@(\w+)@', re.M)

class subst_pc(Task.Task):
    """
    Create *.pc* files from *.pc.in*. The task is executed whenever an input variable used
    in the substitution changes.
    """

    def run(self):
        "Substitutes variables in a .in file"

        if getattr(self.generator, 'is_copy', None):
            self.outputs[0].write(self.inputs[0].read('rb'), 'wb')
            if getattr(self.generator, 'chmod', None):
                os.chmod(self.outputs[0].abspath(), self.generator.chmod)
            return None

        if getattr(self.generator, 'fun', None):
            self.generator.fun(self)

        code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
        if getattr(self.generator, 'subst_fun', None):
            code = self.generator.subst_fun(self, code)
            if code:
                self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
            return

        # replace all % by %% to prevent errors by % signs
        code = code.replace('%', '%%')

        # extract the vars foo into lst and replace @foo@ by %(foo)s
        lst = []
        def repl(match):
            g = match.group
            if g(1):
                lst.append(g(1))
                return "%%(%s)s" % g(1)
            return ''
        code = re_m4.sub(repl, code)

        try:
            d = self.generator.dct
        except AttributeError:
            d = {}
            for x in lst:
                tmp = getattr(self.generator, x, '') or self.env.get_flat(x) or self.env.get_flat(x.upper())
                d[x] = str(tmp)

        code = code % d
        self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
        self.generator.bld.raw_deps[self.uid()] = self.dep_vars = lst

        # make sure the signature is updated
        try: delattr(self, 'cache_sig')
        except AttributeError: pass

        if getattr(self.generator, 'chmod', None):
            os.chmod(self.outputs[0].abspath(), self.generator.chmod)

    def sig_vars(self):
        """
        Compute a hash (signature) of the variables used in the substitution
        """
        bld = self.generator.bld
        env = self.env
        upd = self.m.update

        if getattr(self.generator, 'fun', None):
            upd(Utils.h_fun(self.generator.fun).encode())
        if getattr(self.generator, 'subst_fun', None):
            upd(Utils.h_fun(self.generator.subst_fun).encode())

        # raw_deps: persistent custom values returned by the scanner
        vars = self.generator.bld.raw_deps.get(self.uid(), [])

        # hash both env vars and task generator attributes
        act_sig = bld.hash_env_vars(env, vars)
        upd(act_sig)

        lst = [getattr(self.generator, x, '') for x in vars]
        upd(Utils.h_list(lst))

        return self.m.digest()

@extension('.pc.in')
def add_pcfile(self, node):
    """
    Process *.pc.in* files to *.pc*. Install the results to ``${PREFIX}/lib/pkgconfig/``

        def build(bld):
            bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
    """
    tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
    self.bld.install_files(getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), tsk.outputs)

class subst(subst_pc):
    pass

@feature('subst')
@before_method('process_source', 'process_rule')
def process_subst(self):
    """
    Define a transformation that substitutes the contents of *source* files to *target* files::

        def build(bld):
            bld(
                features='subst',
                source='foo.c.in',
                target='foo.c',
                install_path='${LIBDIR}/pkgconfig',
                VAR = 'val'
            )

    The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
    of the task generator object.

    This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
    """

    src = Utils.to_list(getattr(self, 'source', []))
    if isinstance(src, Node.Node):
        src = [src]
    tgt = Utils.to_list(getattr(self, 'target', []))
    if isinstance(tgt, Node.Node):
        tgt = [tgt]
    if len(src) != len(tgt):
        raise Errors.WafError('invalid number of source/target for %r' % self)

    for x, y in zip(src, tgt):
        if not x or not y:
            raise Errors.WafError('null source or target for %r' % self)
        a, b = None, None

        if isinstance(x, str) and isinstance(y, str) and x == y:
            a = self.path.find_node(x)
            b = self.path.get_bld().make_node(y)
            if not os.path.isfile(b.abspath()):
                b.sig = None
                b.parent.mkdir()
        else:
            if isinstance(x, str):
                a = self.path.find_resource(x)
            elif isinstance(x, Node.Node):
                a = x
            if isinstance(y, str):
                b = self.path.find_or_declare(y)
            elif isinstance(y, Node.Node):
                b = y

        if not a:
            raise Errors.WafError('cound not find %r for %r' % (x, self))

        has_constraints = False
        tsk = self.create_task('subst', a, b)
        for k in ('after', 'before', 'ext_in', 'ext_out'):
            val = getattr(self, k, None)
            if val:
                has_constraints = True
                setattr(tsk, k, val)

        # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
        if not has_constraints and b.name.endswith('.h'):
            tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]

        inst_to = getattr(self, 'install_path', None)
        if inst_to:
            self.bld.install_files(inst_to, b, chmod=getattr(self, 'chmod', Utils.O644))

    self.source = []


"""tests for vak.config.prep module"""
import vak.config.prep

def test_parse_prep_config_returns_PrepConfig_instance(
        configs_toml_path_pairs_by_model_factory,
):
    config_toml_path_pairs = configs_toml_path_pairs_by_model_factory("TweetyNet")
    for config_toml, toml_path in config_toml_path_pairs:
        prep_section = config_toml["PREP"]
        config = vak.config.prep.PrepConfig(**prep_section)
        assert isinstance(config, vak.config.prep.PrepConfig)


'''Solution to Project Euler Problem 35
Code by Timothy Virgil Payne Jr.
Started: 7/28/21
Completed:7/28/21'''

prime_count = 13 #The problem said there are 13 under 100

def primecheck(a):

    'This function checks to see if an integer is prime.'

    if a == 2 or a == 3:
        return True

    elif a < 2 or a % 2 == 0:
        return False

    else:
        for b in range(3, int(a**(0.5)) + 2, 2):

            if a % b == 0:
                return False

            else:
                continue

        else:
            return True

def dig_check(a):

    'Cycle the digits, check for primes'

    global prime_count

    str_prime = str(a)

    for c in range(len(str_prime)-1): #cycle for the rest of the primes
        check_str = ''

        for b in range((len(str_prime)-1),-1,-1):
            check_str += str_prime[-b]

        if primecheck(int(check_str)):
            str_prime = check_str
            continue

        else:
            return

    else:
        prime_count += 1

even_digs = ['2','4','6','8','0'] #skipping all evens saves like two seconds

for x in range(101,1_000_000,2):
    even_check = str(x)

    for dig in even_check:
        if dig in even_digs:
            break

    else:
        if primecheck(x):
            dig_check(x)

print(prime_count)


# -*- coding: utf-8 -*-
""" spfs/core/weighting_methods """

class WeightingMethod:
    """
    Hold the options and method to assign weights based on the current spatial pyramid level

    Usage:
        WeightingMethod.get_weighting_method(WeightingMethod.KEEP_FINEST_FEATS)(current_level, levels)
    """

    KEEP_FINEST_FEATS = 0
    GENERAL_WEIGHTING = 1
    KEEP_ALL_FEATS = 2

    CHOICES = {
        KEEP_FINEST_FEATS: 'keep_finest_feats',
        GENERAL_WEIGHTING: 'general_weighting',
        KEEP_ALL_FEATS: 'keep_all_feats'
    }

    @staticmethod
    def keep_finest_feats(current_level, total_levels=None):
        """
        Calculates and returns the spatial historgram weight for the current_level keeping
        the value of the finest features. E.g.: For total_levels=2 the weights for each level are:
        0->1/4, 1->1/2, 2->1

        Args:
            current_level (int): current level to calculate
            total_levels  (int): total number of levels to apply

        Returns:
            spatial historgram weight (float)
        """
        # avoiding circular reference
        import settings

        total_levels = settings.PYRAMID_LEVELS if total_levels is None else total_levels

        assert isinstance(current_level, int)
        assert isinstance(total_levels, int)
        assert total_levels >= current_level

        return 2**(current_level - total_levels)

    @staticmethod
    def general_weighting(current_level, total_levels=None):
        """
        Calculates and returns the spatial historgram weight for the current_level its general
        weighting formula. E.g.: For total_level=2 the weights for each lever are:
        0->1/4, 1->1/4, 2->1/2

        Args:
            current_level (int): current level to calculate
            total_levels  (int): total number of levels to apply

        Returns:
            spatial historgram weight (float)
        """
        # avoiding circular reference
        import settings

        total_levels = settings.PYRAMID_LEVELS if total_levels is None else total_levels

        assert isinstance(current_level, int)
        assert isinstance(total_levels, int)
        assert total_levels >= current_level

        if current_level == 0:
            return 1/2**total_levels

        return 1/2**(total_levels - current_level + 1)

    @staticmethod
    def keep_all_feats(current_level, total_levels=None):
        """
        Does not modify the vector quantizations per level. Thus, it always returns 1. for any level.

        Args:
            current_level (int): current level to calculate
            total_levels  (int): total number of levels to apply

        Returns:
            spatial historgram weight (float)
        """
        # avoiding circular reference
        import settings

        total_levels = settings.PYRAMID_LEVELS if total_levels is None else total_levels

        assert isinstance(current_level, int)
        assert isinstance(total_levels, int)
        assert total_levels >= current_level

        return 1.

    @classmethod
    def get_weighting_method(cls, option):
        """ description """
        assert option in cls.CHOICES.keys()

        return getattr(cls, cls.CHOICES[option])


myself = 10    # 我有10点血

def punch_someone(who):     # 打人循环
    if who:
        who -= 1
        print(who)
        punch_someone(who)
    else:
        print("die")

punch_someone(myself)   # 我打我自己

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

# # Definition for a binary tree node.
# # class TreeNode:
# #     def __init__(self, x):
# #         self.val = x
# #         self.left = None
# #         self.right = None

# class Solution:
#     def inorderTraversal(self, root: TreeNode) -> List[int]:

#         res = []
#         def travel(root):
#             if root:
#                 res.append(root.val)   # 此处返回 前序遍历
#                 travel(root.left)
#                 # res.append(root.val)   此处返回 中序遍历
#                 travel(root.right)
#                 # res.append(root.val)   此处返回 后序遍历
#             else:
#                 return

#         travel(root)

#         return res

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

# # Definition for a binary tree node.
# # class TreeNode(object):
# #     def __init__(self, x):
# #         self.val = x
# #         self.left = None
# #         self.right = None

# class Solution(object):

#     def buildTree(self, preorder, inorder):
#         """
#         :type preorder: List[int]
#         :type inorder: List[int]
#         :rtype: TreeNode
#         """
#         if len(inorder) == 0:
#             return None
#         root = TreeNode(preorder[0])        # 前序遍历第一个值为根节点
#         mid = inorder.index(preorder[0])        # 因为没有重复元素，所以可以直接根据值来查找根节点在中序遍历中的位置
#         root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])        # 构建左子树
#         root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])        # 构建右子树

#         return root


import numpy as np
from lib.config import cfg
from PIL import Image

def image_transform(img, crop_x, crop_y, crop_loc=None, color_tint=None):
    """
    Takes numpy.array img
    """

    # Slight translation
    if cfg.TRAIN.RANDOM_CROP and not crop_loc:
        crop_loc = [np.random.randint(0, crop_y), np.random.randint(0, crop_x)]

    if crop_loc:
        cr, cc = crop_loc
        height, width, _ = img.shape
        img_h = height - crop_y
        img_w = width - crop_x
        img = img[cr:cr + img_h, cc:cc + img_w]
        # depth = depth[cr:cr+img_h, cc:cc+img_w]

    flipped = 0
    if cfg.TRAIN.FLIP and np.random.rand() > 0.5:
        img = img[:, ::-1, ...]
        flipped = 1

    return img, cr, cc, flipped

def crop_center(im, new_height, new_width):
    height = im.shape[0]  # Get dimensions
    width = im.shape[1]
    left = (width - new_width) // 2
    top = (height - new_height) // 2
    right = (width + new_width) // 2
    bottom = (height + new_height) // 2
    return im[top:bottom, left:right], top, left, 0

def add_random_color_background(im, color_range):
    r, g, b = [np.random.randint(color_range[i][0], color_range[i][1] + 1) for i in range(3)]

    if isinstance(im, Image.Image):
        im = np.array(im)

    # Add color background. Preserve alpha channel as a ground-truth mask.
    assert im.shape[2] == 4, 'Image must have an alpha channel'
    bg = (np.expand_dims(im[:, :, 3], axis=2) == 0).astype(np.float)
    bg_color = np.array([[[r, g, b]]])
    im[:, :, :3] = bg * bg_color + (1 - bg) * im[:, :, :3]
    im[:, :, 3] = 1 - bg[..., 0]

    return im

def preprocess_img(im, train=True):
    # add random background
    im = add_random_color_background(im, cfg.TRAIN.NO_BG_COLOR_RANGE if train else
                                     cfg.TEST.NO_BG_COLOR_RANGE)

    if train:
        transformed = image_transform(im, cfg.TRAIN.PAD_X, cfg.TRAIN.PAD_Y)
    else:
        transformed = crop_center(im, cfg.CONST.IMG_H, cfg.CONST.IMG_W)

    t_im, cr, cc, flipped = transformed

    # Scale image
    t_im = t_im / 255.

    return t_im, cr, cc, flipped

def test(fn):
    import matplotlib.pyplot as plt
    cfg.TRAIN.RANDOM_CROP = True
    im = Image.open(fn)
    im = np.asarray(im)[:, :, :3]
    imt = image_transform(im, 10, 10)
    plt.imshow(imt)
    plt.show()


import tkinter as tk
from tkinter import filedialog, messagebox, PhotoImage
import pandas as pd
import data_prep_functions as prep
import pickle

# Chargement du modèle à partir du fichier
with open('svm_model.pkl', 'rb') as f:
    svm_model = pickle.load(f)

# Création de la fenêtre principale
window = tk.Tk()
window.title("GBmail")
window.geometry("700x500")
window.configure(bg="black")

# Création d'un canevas qui servira de fond noir
canvas = tk.Canvas(window, bg='black', width=700, height=500)
canvas.pack()

# Création d'un titre
label = tk.Label(canvas, text='GBmail', font=('helvetica', 16, 'bold'), bg='black', fg='white')
label.pack(pady=20)

# Configuration de l'image de fond
image = PhotoImage(file='fond.png')
image = image.zoom(2)
image = image.subsample(2)
fond = tk.Label(image=image)
fond.pack()

# Création du frame des boutons
button_frame = tk.Frame(window, bg='black')
button_frame.pack(pady=20)

# Création d'un bouton pour ouvrir une boîte de dialogue de chargement de fichier
def load_csv():
  filepath = filedialog.askopenfilename(filetypes=[("Fichiers CSV", "*.csv")])
  csv_path.set(filepath)

def clean_and_extract_features(df):

  new_df = prep.columns_treatment(df)

  for i in df.index:

    print(i)

    corps = prep.remove_https(str(new_df['corps'][i]))

    corps = prep.text_cleaning(corps)

    corps = prep.text_translation(corps)

    corps = prep.stop_words_english(corps)

    corps = prep.lemmatization(prep.nlp_en, corps)

    corps = ' '.join(corps)

    new_df['corps'][i] = corps

  return new_df['corps']

load_csv_button = tk.Button(button_frame, text="Charger un fichier CSV", command=load_csv)

# Configuration du style du bouton
load_csv_button.config(bg='#4285f4', fg='white', font=('helvetica', 12))

load_csv_button.pack(side='left', padx=5) # Ajout de l'espace entre les boutons

# Création d'un bouton pour lancer l'algorithme
def run_algorithm():
  filepath = csv_path.get()

  # Vérifiez si un fichier a bien été chargé
  if not filepath:
    tk.messagebox.showwarning("Aucun fichier chargé", "Vous devez charger un fichier CSV avant de lancer l'algorithme")
    return

  # Chargement du fichier CSV en utilisant Pandas
  df = pd.read_csv(filepath)

  # Traitement des données du DataFrame avec votre algorithme de nettoyage et de feature extraction
  features = clean_and_extract_features(df)

  # Appel de votre modèle SVM pour effectuer la classification
  prediction = svm_model.predict(features)

  # Affichage des résultats dans une fenêtre de message
  tk.messagebox.showinfo("Résultats", f"Prédiction : {prediction}")

run_algorithm_button = tk.Button(button_frame, text="Lancer l'algorithme", command=run_algorithm)

# Configuration du style du bouton
run_algorithm_button.config(bg='#4285f4', fg='white', font=('helvetica', 12))

run_algorithm_button.pack(side='left', padx=5) # Ajout de l'espace entre les boutons

# Création d'un bouton pour ouvrir un tutoriel d'utilisation
def open_tutorial():
  messagebox.showinfo("Tutoriel", "Ceci est un tutoriel d'utilisation de l'interface GBmail.\n\nPour charger un fichier CSV, cliquez sur le bouton 'Charger un fichier CSV'.\nPour lancer l'algorithme, cliquez sur le bouton 'Lancer l'algorithme'.\n\nAssurez-vous d'avoir chargé un fichier CSV avant de lancer l'algorithme.")

help_button = tk.Button(button_frame, text='?', command=open_tutorial)

# Configuration du style du bouton
help_button.config(bg='white', fg='#4285f4', font=('helvetica', 14, 'bold'))

help_button.pack(side='right') # Ajout du bouton à droite de la fenêtre

# Création d'une variable qui stockera le chemin du fichier CSV chargé
csv_path = tk.StringVar()

# Création d'un label qui affichera le chemin du fichier CSV chargé
csv_label = tk.Label(window, textvariable=csv_path, font=('helvetica', 12))
csv_label.pack()

window.mainloop()


import sys
sys.path.append("..")
# from LTD_utils import data_utils
from pathlib import Path
import numpy as np
import torch
import random
from dataset.base_dataset import BaseDataset
import scipy.interpolate as spi
from random import choice
from utils import str2bool
import pickle
import copy

class Pose3dMusicDataset(BaseDataset):

    def __init__(self, arg, split="train"):
        super().__init__()
        self.arg = arg
        data_path = Path(arg.data_dir)
        split_path = Path(arg.split_dir)
        music_data_path = Path(arg.music_data_dir)
        # foot_data_path = Path(arg.foot_data_dir)
        self.split = split

        random.seed(1111)

        if not data_path.is_dir():
            raise ValueError('Invalid directory:' + arg.data_dir)

        ignore_files = [x.strip() for x in open(split_path.joinpath("ignore_list.txt"), "r").readlines()]

        base_filenames = []

        if split == "train":
            base_filenames += [x.strip() for x in
                               open(split_path.joinpath("crossmodal_train.txt"), "r").readlines()]
        else:
            base_filenames += [x.strip() for x in
                               open(split_path.joinpath("crossmodal_test.txt"), "r").readlines()]
            base_filenames += [x.strip() for x in
                               open(split_path.joinpath("crossmodal_val.txt"), "r").readlines()]

        base_filenames = [x for x in base_filenames if x not in ignore_files]
        # temp_base_filenames = ["aistpp_" + x for x in base_filenames if x not in ignore_files]
        if self.arg.mirror_aug:
            base_filenames += [x+"mirror" for x in base_filenames]

        np.random.seed(1111)
        if arg.num_train_samples > 0 and arg.num_train_samples < len(base_filenames):
            base_filenames = np.random.choice(base_filenames, size=arg.num_train_samples,
                                                   replace=False).tolist()
        self.base_filenames = []
        self.pose_features = {}
        self.music_features = {}
        self.avg_beats = {}
        self.style2music = {}
        self.total_frames = 0
        self.frame_cum_sums = []
        self.input_length = arg.total_len

        frame_sum = 0

        # Get the list of files containing features (in numpy format for now), and populate the dictionaries of input and output features (separated by modality)
        for base_filename in base_filenames:
            ignore_file = False
            pose_feature_file = data_path.joinpath(base_filename + ".npy")
            audio_names = base_filename.split("_")[-2]
            if audio_names not in self.music_features.keys():
                music_feature_file = music_data_path.joinpath(audio_names + ".npy")
                music_features = np.load(music_feature_file)[:, 1:33]
                self.music_features[audio_names] = music_features
                style_name = audio_names[1:3]
                if style_name not in self.style2music.keys():
                    self.style2music[style_name] = set()
                self.style2music[style_name].add(audio_names)

            try:
                pose_features = np.load(pose_feature_file)
                frame_sum += pose_features.shape[0]
                pose_length = pose_features.shape[0]
                if pose_length < self.input_length:
                    print("Smol sequence " + base_filename + "." + arg.pose_mod + "; ignoring..")
                    ignore_file = True
            except Exception as e:
                print(e)
                continue

            if ignore_file: continue
            self.pose_features[base_filename] = pose_features
            self.base_filenames.append(base_filename)

        for k,v in self.style2music.items():
            self.style2music[k] = list(v)
        print("sequences added: " + str(len(self.base_filenames)))
        print(frame_sum)

    def process_beats(self, music_beats):
        beats_index = []
        for i in range(music_beats.shape[0]):
            if music_beats[i] > 0:
                beats_index.append(i)

        beats_offsets = [beats_index[i+1] - beats_index[i] for i in range(len(beats_index)-1)]
        avg_beats = sum(beats_offsets) // len(beats_offsets)
        return avg_beats

    @staticmethod
    def modify_commandline_options(parser):
        parser.add_argument("--output_n", type=int, default=7)
        parser.add_argument("--input_n", type=int, default=40)
        parser.add_argument("--history_n", type=int, default=40)
        parser.add_argument("--total_len", type=int, default=80)
        parser.add_argument('--split_dir', type=str,
                            default="path_to_anno_dir/aist_plusplus_final/splits")
        parser.add_argument('--data_dir', type=str,
                            default="path_to_aistpp_feature_mirror/motion_feature_20_beat_3")
        parser.add_argument('--music_data_dir', type=str,
                            default="path_to_aistpp_feature_mirror/audio_feature_20")
        parser.add_argument("--batch_size", type=int, default=128)
        parser.add_argument("--val_batch_size", type=int, default=128)
        parser.add_argument("--num_train_samples", type=int, default=0)
        parser.add_argument("--workers", type=int, default=2)
        parser.add_argument("--augment_rate", type=float, default=0.0)
        parser.add_argument("--joint_aug_rate", type=float, default=0.6)
        parser.add_argument("--mirror_aug", type=str2bool, default=True)
        parser.add_argument("--offset_aug", type=str2bool, default=False)
        parser.add_argument("--dataset_repeat", type=int, default=80)
        return parser

    def name(self):
        return "Music2PoseDataset"

    def __getitem__(self, item):
        idx = item % len(self.base_filenames)
        base_filename = self.base_filenames[idx]

        pose_features = self.pose_features[base_filename]

        audio_names = base_filename.split("_")[-2]
        style_names = audio_names[1:3]
        all_style = set(self.style2music.keys())
        all_style.remove(style_names)
        other_style = choice(list(all_style))

        style_music = choice(self.style2music[style_names])
        same_style_music = choice(self.style2music[style_names])
        other_style_music = choice(self.style2music[other_style])

        style_music_features = self.music_features[style_music]
        same_style_music_features = self.music_features[same_style_music]
        other_style_music_features = self.music_features[other_style_music]

        begin_index = np.random.randint(0, style_music_features.shape[0] - self.arg.input_n + 1)
        style_music_windows = torch.tensor(style_music_features[begin_index:begin_index + self.arg.input_n]).float()

        begin_index = np.random.randint(0, same_style_music_features.shape[0] - self.arg.input_n + 1)
        same_style_music_windows = torch.tensor(
            same_style_music_features[begin_index:begin_index + self.arg.input_n]).float()

        begin_index = np.random.randint(0, other_style_music_features.shape[0] - self.arg.input_n + 1)
        other_style_music_windows = torch.tensor(
            other_style_music_features[begin_index:begin_index + self.arg.input_n]).float()

        motion_sample_length = self.arg.input_n + self.arg.output_n

        sequence_length = pose_features.shape[0] - motion_sample_length
        begin_index = np.random.randint(0, sequence_length + 1)

        ## CONSTRUCT TENSOR OF INPUT FEATURES ##
        pose_windows = pose_features[begin_index:begin_index + motion_sample_length]
        beat_windows = copy.deepcopy(pose_windows[:, -1])
        pose_windows = pose_windows[:, :-1]
        dist = 0
        for i in range(begin_index + motion_sample_length - 1, pose_features.shape[0]):
            if pose_features[i][-1] > 0:
                break
            dist += 1
            if dist >= motion_sample_length - 1:
                break

        for i in range(motion_sample_length - 1, -1, -1):
            if beat_windows[i] > 0:
                dist = 0
            beat_windows[i] = dist
            dist += 1

        style_window = []
        if begin_index >= 40:
            style_window += range(0, begin_index-40+1)
        if begin_index+motion_sample_length+40 <= pose_features.shape[0]:
            style_window += range(begin_index+motion_sample_length, pose_features.shape[0]-40+1)

        style_index = random.sample(style_window, 1)[0]
        context_index = random.sample(style_window, 1)[0]

        style_windows = pose_features[style_index:style_index + 40, :-1]
        style_windows = torch.tensor(style_windows).float()

        context_windows = pose_features[context_index:context_index + 40, :-1]
        context_windows = torch.tensor(context_windows).float()

        pose_windows = torch.tensor(pose_windows).float()

        beat_windows = torch.tensor(beat_windows).long()

        return pose_windows, beat_windows, style_windows, context_windows, \
               style_music_windows, same_style_music_windows, other_style_music_windows

    def __len__(self):
        if self.split == "train":
            return len(self.base_filenames) * self.arg.dataset_repeat
        else:
            return len(self.base_filenames) * 4

import argparse
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser = Pose3dMusicDataset.modify_commandline_options(parser)
    dataset = Pose3dMusicDataset(parser.parse_args())
    a = dataset[1]
    print(a)


import json
import math

import torch.nn as nn
import torch
import bricks
import torch.nn.functional as F
from abc import *
import utils

"""
    [batch_size, in_channels, height, width] -> [batch_size, out_channels, height // 4, width // 4]
"""
class StemConv(nn.Module):

    def __init__(self, in_channels, out_channels, norm_layer=None):
        super(StemConv, self).__init__()

        self.proj = nn.Sequential(
            bricks.DownSampling(
                in_channels=in_channels,
                out_channels=out_channels // 2,
                kernel_size=(3, 3),
                stride=(2, 2),
                norm_layer=norm_layer
            ),
            bricks.DownSampling(
                in_channels=out_channels // 2,
                out_channels=out_channels,
                kernel_size=(3, 3),
                stride=(2, 2),
                norm_layer=norm_layer
            ),
        )

    def forward(self, x):
        out = self.proj(x)
        return out

class MSCA(nn.Module):

    def __init__(self, in_channels):
        super(MSCA, self).__init__()

        self.conv = bricks.DepthwiseConv(
            in_channels=in_channels,
            kernel_size=(5, 5),
            padding=(2, 2),
            bias=True
        )

        self.conv7 = nn.Sequential(
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(1, 7),
                padding=(0, 3),
                bias=True
            ),
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(7, 1),
                padding=(3, 0),
                bias=True
            )
        )

        self.conv11 = nn.Sequential(
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(1, 11),
                padding=(0, 5),
                bias=True
            ),
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(11, 1),
                padding=(5, 0),
                bias=True
            )
        )

        self.conv21 = nn.Sequential(
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(1, 21),
                padding=(0, 10),
                bias=True
            ),
            bricks.DepthwiseConv(
                in_channels=in_channels,
                kernel_size=(21, 1),
                padding=(10, 0),
                bias=True
            )
        )

        self.fc = nn.Conv2d(
            in_channels=in_channels,
            out_channels=in_channels,
            kernel_size=(1, 1)
        )

    def forward(self, x):
        u = x
        out = self.conv(x)

        branch1 = self.conv7(out)
        branch2 = self.conv11(out)
        branch3 = self.conv21(out)

        out = self.fc(out + branch1 + branch2 + branch3)
        out = out * u
        return out

class Attention(nn.Module):

    def __init__(self, in_channels):
        super(Attention, self).__init__()

        self.fc1 = nn.Conv2d(
            in_channels=in_channels,
            out_channels=in_channels,
            kernel_size=(1, 1)
        )
        self.msca = MSCA(in_channels=in_channels)
        self.fc2 = nn.Conv2d(
            in_channels=in_channels,
            out_channels=in_channels,
            kernel_size=(1, 1)
        )

    def forward(self, x):
        out = F.gelu(self.fc1(x))
        out = self.msca(out)
        out = self.fc2(out)
        return out

class FFN(nn.Module):

    def __init__(self, in_features, hidden_features, out_features, drop_prob=0.):
        super(FFN, self).__init__()

        self.fc1 = nn.Conv2d(
            in_channels=in_features,
            out_channels=hidden_features,
            kernel_size=(1, 1)
        )
        self.dw = bricks.DepthwiseConv(
            in_channels=hidden_features,
            kernel_size=(3, 3),
            bias=True
        )
        self.fc2 = nn.Conv2d(
            in_channels=hidden_features,
            out_channels=out_features,
            kernel_size=(1, 1)
        )
        self.dropout = nn.Dropout(drop_prob)

    def forward(self, x):
        out = self.fc1(x)
        out = F.gelu(self.dw(out))
        out = self.fc2(out)
        out = self.dropout(out)
        return out

class Block(nn.Module):

    def __init__(self, in_channels, expand_ratio, drop_prob=0., drop_path_prob=0.):
        super(Block, self).__init__()

        self.norm1 = nn.BatchNorm2d(num_features=in_channels)
        self.attention = Attention(in_channels=in_channels)
        self.drop_path = bricks.DropPath(drop_prob=drop_path_prob if drop_path_prob >= 0 else nn.Identity)
        self.norm2 = nn.BatchNorm2d(num_features=in_channels)
        self.ffn = FFN(
            in_features=in_channels,
            hidden_features=int(expand_ratio * in_channels),
            out_features=in_channels,
            drop_prob=drop_prob
        )

        layer_scale_init_value = 1e-2
        self.layer_scale1 = nn.Parameter(
            layer_scale_init_value * torch.ones(in_channels),
            requires_grad=True
        )
        self.layer_scale2 = nn.Parameter(
            layer_scale_init_value * torch.ones(in_channels),
            requires_grad=True
        )

    def forward(self, x):
        out = self.norm1(x)
        out = self.attention(out)
        out = x + self.drop_path(
            self.layer_scale1.unsqueeze(-1).unsqueeze(-1) * out
        )
        x = out

        out = self.norm2(out)
        out = self.ffn(out)
        out = x + self.drop_path(
            self.layer_scale2.unsqueeze(-1).unsqueeze(-1) * out
        )

        return out

class Stage(nn.Module):

    def __init__(
            self,
            stage_id,
            in_channels,
            out_channels,
            expand_ratio,
            blocks_num,
            drop_prob=0.,
            drop_path_prob=[0.]
    ):
        super(Stage, self).__init__()

        assert blocks_num == len(drop_path_prob)

        if stage_id == 0:
            self.down_sampling = StemConv(
                in_channels=in_channels,
                out_channels=out_channels
            )
        else:
            self.down_sampling = bricks.DownSampling(
                in_channels=in_channels,
                out_channels=out_channels,
                kernel_size=(3, 3),
                stride=(2, 2)
            )

        self.blocks = nn.Sequential(
            *[
                Block(
                    in_channels=out_channels,
                    expand_ratio=expand_ratio,
                    drop_prob=drop_prob,
                    drop_path_prob=drop_path_prob[i]
                ) for i in range(0, blocks_num)
            ]
        )

        self.norm = nn.LayerNorm(out_channels)

    def forward(self, x):
        out = self.down_sampling(x)
        out = self.blocks(out)
        # [batch_size, channels, height, width] -> [batch_size, channels, height * width]
        batch_size, channels, height, width = out.shape
        out = out.view(batch_size, channels, -1)
        # [batch_size, channels, height * width] -> [batch_size, height * width, channels]
        out = torch.transpose(out, -2, -1)
        out = self.norm(out)

        # [batch_size, height * width, channels] -> [batch_size, channels, height * width]
        out = torch.transpose(out, -2, -1)
        # [batch_size, channels, height * width] -> [batch_size, channels, height, width]
        out = out.view(batch_size, -1, height, width)

        return out

class MSCAN(nn.Module):

    def __init__(
            self,
            embed_dims=[3, 32, 64, 160, 256],
            expand_ratios=[8, 8, 4, 4],
            depths=[3, 3, 5, 2],
            drop_prob=0.1,
            drop_path_prob=0.1
    ):
        super(MSCAN, self).__init__()

        dpr = [x.item() for x in torch.linspace(0, drop_path_prob, sum(depths))]
        self.stages = nn.Sequential(
            *[
                Stage(
                    stage_id=stage_id,
                    in_channels=embed_dims[stage_id],
                    out_channels=embed_dims[stage_id + 1],
                    expand_ratio=expand_ratios[stage_id],
                    blocks_num=depths[stage_id],
                    drop_prob=drop_prob,
                    drop_path_prob=dpr[sum(depths[: stage_id]): sum(depths[: stage_id + 1])]
                ) for stage_id in range(0, len(depths))
            ]
        )

    def forward(self, x):
        out = x
        outputs = []

        for idx, stage in enumerate(self.stages):
            out = stage(out)
            if idx != 0:
                outputs.append(out)

        # outputs: [output_of_stage1, output_of_stage2, output_of_stage3]
        # output_of_stage1: [batch_size, embed_dims[2], height / 8, width / 8]
        # output_of_stage2: [batch_size, embed_dims[3], height / 16, width / 16]
        # output_of_stage3: [batch_size, embed_dims[4], height / 32, width / 32]
        return [x, *outputs]

class Hamburger(nn.Module):

    def __init__(
            self,
            hamburger_channels=256,
            nmf2d_config=json.dumps(
                {
                    "SPATIAL": True,
                    "MD_S": 1,
                    "MD_D": 512,
                    "MD_R": 64,
                    "TRAIN_STEPS": 6,
                    "EVAL_STEPS": 7,
                    "INV_T": 1,
                    "ETA": 0.9,
                    "RAND_INIT": True,
                    "return_bases": False,
                    "device": "cuda"
                }
            )
    ):
        super(Hamburger, self).__init__()
        self.ham_in = nn.Sequential(
            nn.Conv2d(
                in_channels=hamburger_channels,
                out_channels=hamburger_channels,
                kernel_size=(1, 1)
            )
        )

        self.ham = bricks.NMF2D(args=nmf2d_config)

        self.ham_out = nn.Sequential(
            nn.Conv2d(
                in_channels=hamburger_channels,
                out_channels=hamburger_channels,
                kernel_size=(1, 1),
                bias=False
            ),
            nn.GroupNorm(
                num_groups=32,
                num_channels=hamburger_channels
            )
        )

    def forward(self, x):
        out = self.ham_in(x)
        out = self.ham(out)
        out = self.ham_out(out)
        out = F.relu(x + out)
        return out

class LightHamHead(nn.Module):

    def __init__(
            self,
            in_channels_list=[64, 160, 256],
            hidden_channels=256,
            out_channels=256,
            classes_num=150,
            drop_prob=0.1,
            nmf2d_config=json.dumps(
                {
                    "SPATIAL": True,
                    "MD_S": 1,
                    "MD_D": 512,
                    "MD_R": 64,
                    "TRAIN_STEPS": 6,
                    "EVAL_STEPS": 7,
                    "INV_T": 1,
                    "ETA": 0.9,
                    "RAND_INIT": True,
                    "return_bases": False,
                    "device": "cuda"
                }
            )
    ):
        super(LightHamHead, self).__init__()

        self.cls_seg = nn.Sequential(
            nn.Dropout2d(drop_prob),
            nn.Conv2d(
                in_channels=out_channels,
                out_channels=classes_num,
                kernel_size=(1, 1)
            )
        )

        self.squeeze = nn.Sequential(
            nn.Conv2d(
                in_channels=sum(in_channels_list),
                out_channels=hidden_channels,
                kernel_size=(1, 1),
                bias=False
            ),
            nn.GroupNorm(
                num_groups=32,
                num_channels=hidden_channels,
            ),
            nn.ReLU()
        )

        self.hamburger = Hamburger(
            hamburger_channels=hidden_channels,
            nmf2d_config=nmf2d_config
        )

        self.align = nn.Sequential(
            nn.Conv2d(
                in_channels=hidden_channels,
                out_channels=out_channels,
                kernel_size=(1, 1),
                bias=False
            ),
            nn.GroupNorm(
                num_groups=32,
                num_channels=out_channels
            ),
            nn.ReLU()
        )

    # inputs: [x, x_1, x_2, x_3]
    # x: [batch_size, channels, height, width]
    def forward(self, inputs):
        assert len(inputs) >= 2
        o = inputs[0]
        batch_size, _, standard_height, standard_width = inputs[1].shape
        standard_shape = (standard_height, standard_width)
        inputs = [
            F.interpolate(
                input=x,
                size=standard_shape,
                mode="bilinear",
                align_corners=False
            )
            for x in inputs[1:]
        ]

        # x: [batch_size, channels_1 + channels_2 + channels_3, standard_height, standard_width]
        x = torch.cat(inputs, dim=1)

        # out: [batch_size, channels_1 + channels_2 + channels_3, standard_height, standard_width]
        out = self.squeeze(x)
        out = self.hamburger(out)
        out = self.align(out)

        # out: [batch_size, classes_num, standard_height, standard_width]
        out = self.cls_seg(out)

        _, _, original_height, original_width = o.shape
        # out: [batch_size, original_height * original_width, classes_num]
        out = F.interpolate(
            input=out,
            size=(original_height, original_width),
            mode="bilinear",
            align_corners=False
        )
        out = torch.transpose(out.view(batch_size, -1, original_height * original_width), -2, -1)

        return out

class SegNeXt(nn.Module):

    def __init__(
            self,
            embed_dims=[3, 32, 64, 160, 256],
            expand_rations=[8, 8, 4, 4],
            depths=[3, 3, 5, 2],
            drop_prob_of_encoder=0.1,
            drop_path_prob=0.1,
            hidden_channels=256,
            out_channels=256,
            classes_num=150,
            drop_prob_of_decoder=0.1,
            nmf2d_config=json.dumps(
                {
                    "SPATIAL": True,
                    "MD_S": 1,
                    "MD_D": 512,
                    "MD_R": 64,
                    "TRAIN_STEPS": 6,
                    "EVAL_STEPS": 7,
                    "INV_T": 1,
                    "ETA": 0.9,
                    "RAND_INIT": False,
                    "return_bases": False,
                    "device": "cuda"
                }
            )
    ):
        super(SegNeXt, self).__init__()

        self.encoder = MSCAN(
            embed_dims=embed_dims,
            expand_ratios=expand_rations,
            depths=depths,
            drop_prob=drop_prob_of_encoder,
            drop_path_prob=drop_path_prob
        )

        self.decoder = LightHamHead(
            in_channels_list=embed_dims[-3:],
            hidden_channels=hidden_channels,
            out_channels=out_channels,
            classes_num=classes_num,
            drop_prob=drop_prob_of_decoder,
            nmf2d_config=nmf2d_config
        )

    def forward(self, x):
        out = self.encoder(x)
        out = self.decoder(out)
        return out


#!/usr/bin/env python3
#encoding: utf-8

from requests import get
from cortexutils.analyzer import Analyzer
from DNS_records import RECORDS, CODE
from json import loads
from traceback import format_exc

class GoogleDNS_resolve(Analyzer):

	def __init__(self):
		Analyzer.__init__(self)
		self.url = "https://dns.google.com/resolve?"
		self.proxies = None
		self.answer = None

	def resolve(self, query):

		query = {
			"name" : query,
			"type" : "ANY"
		}

		try:
			data = loads(get(self.url, params=query, proxies=self.proxies).text)
		except Exception as e:
			self.report(format_exc())
		else:

			if data['Status'] == 0: # DNS response code
				if 'Answer' in data: # Maybe nothing is found

					for records in data['Answer']: # for each records found by Google
						try:
							records["type"] = RECORDS[records["type"]]  # replace IANA code by record name
						except KeyError:
							data["Error"] = "Invalid IANA code : {0}".format(int(records["type"]))	# Maybe using a special code
				else:
					data['Answer'] = []

			else: # If the DNS response match an error code
				try:
					# known DNS error code
					data["Error"] = "Error for {0} : {1}".format(data['Question'][0]['name'], CODE[int(data["Status"])])
				except KeyError:
					# DNS error code is unknow
					data["Error"] = "Unknow error : {0}".format(int(data["Status"]))

			self.answer = data
			self.answer['Question'][0]['type'] = RECORDS[data['Question'][0]['type']] # eplace IANA code by record name
			self.answer["Status"] = CODE[int(data["Status"])] # replace DNS response code by name

	def run(self):
		if self.data_type not in ["ip", "domain", "fqdn"]:
			self.error("Wrong data type")

		target = self.getData()

		self.proxies = {
			"https" : self.getParam("config.proxy_https"),
			"http" : self.getParam("config.proxy_http")
		}

		target = ".".join(target.split('.')[::-1]) + '.in-addr.arpa' if self.data_type == "ip" else target

		self.resolve(target)
		if self.answer != None:
			self.report(self.answer)
		else:
			self.error("Something went wrong")

	def summary(self, raw):
		count = self.build_taxonomy("info", "GoogleDNS", "RecordsCount", len(self.answer["Answer"]))
		return { "taxonomies" : [count]}

if __name__ == "__main__":
	GoogleDNS_resolve().run()


import numpy as np
import math
from dice_game import DiceGame
from qlplayer import QLPlayer, CircusSelector
from simpleplayer import DiceThresholdPlayer
import os

np.random.seed()
filename = 'trained_player.pkl'
simple_player = DiceThresholdPlayer(1)

if False and os.path.isfile(filename):
    trained_player = QLPlayer.from_file(filename)
    trained_player.state_converter = lambda x: (int(x[0]/100), x[1], x[2])
else:
    params = {}
    params['df'] = 0.9
    params['state_converter'] = lambda x: (int(x[0]/100), x[1], x[2])
    params['learn_select'] = CircusSelector(bias=200)
    trained_player = QLPlayer(params=params)

    for _ in range(10):
        for i in range(2000):
            DiceGame([trained_player, simple_player], 5000, verbose=False).play()
        trained_player.lr /= 1.414
        trained_player.learn_select.bias /= 1.414

    trained_player.is_learning = False
    trained_player.to_file(filename)

trained_player.is_learning = False
print('Done training')

# players = [trained_player, DiceThresholdPlayer(2), simple_player]
players = [trained_player, simple_player]
tags = ['QLPlayer', 'BasePlayer1', 'BasePlayer2']

scores = DiceGame(players, tags=tags, verbose=True).play()

wins = [0] * len(players)
for i in range(1000):
    game = DiceGame(players, 10000, verbose=False)
    scores = game.play()
    winning_player = scores.index(max(scores))
    wins[winning_player] += 1

print(wins)


class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        if not head or not head.next: return head
        res, tail = head,head
        while head:
            while tail and tail.next and head.val==tail.next.val:
                tail = tail.next
            head.next = tail.next
            tail,head = tail.next, head.next
        return res


input3 = open("input3.txt").readlines()

# São valores de 12 bits
counters = [0]*12

# Varre por elas e conta os bits 1 em cada casa
for i in range(len(input3)):
    for j in range(12):
        if( input3[i][j] == '1'):
            counters[j] += 1

gamma = 0
epsilon = 0
for k in range(12):
    # Maioria é 1
    if( counters[k] > len(input3)/2 ):
        gamma = (gamma << 1) | 1
        epsilon = (epsilon << 1) | 0
    # Maioria é 0
    else:
        gamma = (gamma << 1) | 0
        epsilon = (epsilon << 1) | 1

# Vamos fazer da forma mais simples. Primeiro fazemos 2 cópias da lista inteira
oxigen = input3.copy()
scrubber = input3.copy()

# Agora analizamos bit a bit. Primeiro para o oxigen.
for k in range(12):
    count0 = 0
    count1 = 0
    # Contagem de bits 1 e bits 0
    for ox in oxigen:
        if( ox[k] == '1' ):
            count1 += 1
        else:
            count0 += 1
    # print(f"{k},{count0},{count1}")
    # Remover direto da lista no loop não deu certo em Python, então vamos criar uma lista de itens a remove
    ox_remove = []
    # E usamos os critérios de maioria/minoria para colocar na lista de remoção
    for ox in oxigen:
        if( count0 > count1 ):
            if( ox[k] == '1' ):
                ox_remove.append(ox)
        elif( count0 < count1 ):
            if( ox[k] == '0' ):
                ox_remove.append(ox)
        else:
            if( ox[k] == '0' ):
                ox_remove.append(ox)
        # Critério de parada: só vai restar 1 elemento na lista
        if( len(oxigen) - len(ox_remove) == 1 ):
            break

    # Agora faz a remoção em si dos itens da lista
    for ox in ox_remove:
        oxigen.remove(ox)

    # Critério de parada: só tem 1 elemento restante
    if( len(oxigen) == 1 ):
        break

# Mesma análise para o scribber
for k in range(12):
    count0 = 0
    count1 = 0
    for sc in scrubber:
        if( sc[k] == '1' ):
            count1 += 1
        else:
            count0 += 1
    # print(f"{k},{count0},{count1}")
    sc_remove = []

    # Aqui só invertemos a lógica minoria/maioria
    for sc in scrubber:
        if( count0 < count1 ):
            if( sc[k] == '1' ):
                sc_remove.append(sc)
        elif( count0 > count1 ):
            if( sc[k] == '0' ):
                sc_remove.append(sc)
        else:
            if( sc[k] == '1' ):
                sc_remove.append(sc)
        if( len(scrubber) - len(sc_remove) == 1 ):
            break

    for sc in sc_remove:
        scrubber.remove(sc)
    if( len(scrubber) == 1 ):
        break

# Conferência: só pode ter 1 elemento em cada lista
print(oxigen)
print(scrubber)

# Parsing e multiplicação
oxigen = int(oxigen[0],2)
scrubber = int(scrubber[0],2)
print(f"{oxigen},{scrubber},{oxigen*scrubber}")


from heapq import merge
from csvreader import csv_reader
import firebase_admin

from firebase_admin import credentials
from firebase_admin import firestore

cred = credentials.Certificate('pythonScripts /team-7-site-59ff2-firebase-adminsdkServiceKey.json')
firebase_admin.initialize_app(cred,{'projectID': "team-7-site-59ff2",})
db = firestore.client()

customers_ref = db.collection('customers')
customer = customers_ref.stream()

cust_ref = customers_ref.document('id')

for doc in customer:
  print(f'{doc.id} => {doc.to_dict()}')

reader = csv_reader()
all_rows = reader.read_file("pythonScripts /customerDatabase.csv")

for row in all_rows:
  print(row)
'''
  new_customer_ref = customers_ref.document()
  new_customer_ref.set({
    'firstName' : row['\ufeffFirst Name'],
    'lastName'  : row['Last Name'],
    'numOfRecordVisits': row['Number of Recorded Visits '],
    'rewardPoints': row['Reward Points '],
    'preferredPaymentType': row['Preferred Payment Type '],
    'customerID' : row['Customer ID'],
    'email'     : row['Email '],
    'username'  : row['Username'],
  })

'''


import os
import pickle
import configparser
from typing import Any

import shellhacks_algorithm

config = configparser.ConfigParser()
config.read(os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    "config.cfg"
))

caching_directory = config["DEFAULT"]["caching_directory"]

absolute_caching_directory = os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    caching_directory
)

baselines = {
    "Baseball",
    "Basketball",
    "Boxing",
    "Football",
    "Golf Side View",
    "Golf Back View",
    "Running",
    "Push-ups",
    "Snatch",
    "Squat",
    "Clean and Jerk"
}

class VideoProcessor:

    """
    This function processes a video with caching
    """
    @staticmethod
    def process_video(filename: str, compression: int = 1) -> tuple[dict[tuple[tuple[str, str], tuple[str, str]], list[Any]], list[Any]]:
        if filename in baselines:
            print("OWEIFJIOWJFJWFIJ", filename)
            with open(
                os.path.join(
                    absolute_caching_directory,
                    filename + ".pickle"), 'rb') as f:

                obj = pickle.load(f)
                return obj, []

        old_filename = filename
        filename = filename.split('/')[-1]

        res = shellhacks_algorithm.process_video(old_filename, compression)

        with open(
            os.path.join(
                absolute_caching_directory,
                filename + ".pickle"), "wb") as f:
            pickle.dump(res[0], f)

        return res

def process_files(baseline: str, evaluee: str, compression: int = 4) -> tuple[dict[str, float], list]:
    a, _ = VideoProcessor.process_video(baseline, compression)
    b, ret = VideoProcessor.process_video(evaluee, compression)

    return shellhacks_algorithm.evaluate_sequence(a, b), ret


# Import libraries
import pandas as pd
import numpy as np
import re
import transformers
from transformers import BertModel,BertTokenizer,AdamW, get_linear_schedule_with_warmup, AutoModelForSequenceClassification, AutoTokenizer
import json
import torch
from torch import nn ,cuda
from torch.utils.data import DataLoader,Dataset,RandomSampler, SequentialSampler
from pytorch_lightning.loggers import WandbLogger
from pytorch_lightning import Trainer
import pytorch_lightning as pl
from pytorch_lightning.callbacks import ModelCheckpoint
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
from bs4 import BeautifulSoup
import gc
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc

RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(torch.cuda.is_available())

from collections import Counter
import operator
from operator import itemgetter

pd.set_option('display.max_columns', 500)

df_desc = pd.read_csv('C:/Users/daryl/Desktop/PLP Proj/steam_with_description.csv', encoding='ISO-8859-1')
df_desc.head()
df_desc['steamspy_tags']= df_desc['steamspy_tags'].str.split(";")
exploded = df_desc.explode('steamspy_tags')
exploded['steamspy_tags'] = exploded['steamspy_tags'].str.replace(r'[^A-Za-z,]', '', regex=True).replace('', np.nan, regex=False)

# count of unique tags
print(len(exploded['steamspy_tags'].unique()))
print(exploded['steamspy_tags'].value_counts()[0:20])

exploded = exploded[exploded['steamspy_tags'] != "D"]
exploded = exploded[exploded['steamspy_tags'] != "EarlyAccess"]
exploded = exploded[exploded['steamspy_tags'] != "FreetoPlay"]

top20 = dict(Counter(exploded['steamspy_tags']).most_common(20))
print(top20)

names = list(top20.keys())
values = list(top20.values())

plt.barh(range(len(top20)), values, tick_label=names)
ax = plt.gca()
ax.invert_yaxis()
plt.title("Top 20 label occurences")
plt.show()

top_20_tags = [x for x in names if x == x]

def filterval(ls, val):
    if val in ls:
        ls.remove(val)
    return ls
filterval(top_20_tags, "D")
print(top_20_tags)

tags = exploded.groupby(['name', 'detailed_description']).apply(lambda x:x['steamspy_tags'].values).reset_index(name='final_tags')
tags['final_tags'] = tags['final_tags']
print(tags)

def pre_process(text):
    text = BeautifulSoup(text).get_text()

    # fetch alphabetic characters
    text = re.sub("[^a-zA-Z]", " ", text)

    # convert text to lower case
    text = text.lower()

    # split text into tokens to remove whitespaces
    tokens = text.split()

    return " ".join(tokens)

tags['clean_desc'] = tags['detailed_description'].apply(pre_process)

x=[] # To store the filtered clean_body values
y=[] # to store the corresponding tags
z=[] # store names

for i in range(len(tags['final_tags'])):
    temp=[]
    for tag in tags['final_tags'][i]:
        if tag in top_20_tags:
            temp.append(tag)

    if(len(temp)>0):
        z.append(tags['name'][i])
        x.append(tags['clean_desc'][i])
        y.append(temp)

final_df = pd.DataFrame(
    {'name': z,
     'desc': x,
     'tags': y
    })

print(final_df)

from sklearn.preprocessing import MultiLabelBinarizer

mlb = MultiLabelBinarizer()

yt = mlb.fit_transform(y)
yt.shape

# Getting a sense of how the tags data looks like
print(yt[0])
print(mlb.inverse_transform(yt[0].reshape(1,-1)))
print(mlb.classes_)

#dealing with class imbalance
class_counts = np.sum(yt,axis=0)
print(class_counts)
pos_weights = [(class_counts.sum() - x)/ (x+1e-5) for i, x in enumerate(class_counts)]
print(pos_weights)
pos_weights = torch.as_tensor(pos_weights, dtype=torch.float)

# compute no. of words in each desc
desc = x
word_cnt = [len(quest.split()) for quest in desc]
# Plot the distribution
plt.figure(figsize=[12,8])
plt.hist(word_cnt, bins = 40)
plt.xlabel('Word Count/Description')
plt.ylabel('# of Occurences')
plt.title("Frequency of Word Counts/Description")
plt.show()

from sklearn.model_selection import train_test_split
# First Split for Train and Test
x_train,x_test,y_train,y_test = train_test_split(desc, yt, test_size=0.1, random_state=RANDOM_SEED,shuffle=True)
# Next split Train in to training and validation
x_tr,x_val,y_tr,y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=RANDOM_SEED,shuffle=True)

len(x_tr) ,len(x_val), len(x_test)

class QTagDataset(Dataset):
    def __init__(self, quest, tags, tokenizer, max_len):
        self.tokenizer = tokenizer
        self.text = quest
        self.labels = tags
        self.max_len = max_len

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

    def __getitem__(self, item_idx):
        text = self.text[item_idx]
        inputs = self.tokenizer.encode_plus(
            text,
            None,
            add_special_tokens=True,  # Add [CLS] [SEP]
            max_length=self.max_len,
            padding='max_length',
            return_token_type_ids=False,
            return_attention_mask=True,  # Differentiates padded vs normal token
            truncation=True,  # Truncate data beyond max length
            return_tensors='pt'  # PyTorch Tensor format
        )

        input_ids = inputs['input_ids'].flatten()
        attn_mask = inputs['attention_mask'].flatten()
        # token_type_ids = inputs["token_type_ids"]

        return {
            'input_ids': input_ids,
            'attention_mask': attn_mask,
            'label': torch.tensor(self.labels[item_idx], dtype=torch.float)

        }

class QTagDataModule(pl.LightningDataModule):

    def __init__(self, x_tr, y_tr, x_val, y_val, x_test, y_test, tokenizer, batch_size=32, max_token_len=300):
        super().__init__()
        self.tr_text = x_tr
        self.tr_label = y_tr
        self.val_text = x_val
        self.val_label = y_val
        self.test_text = x_test
        self.test_label = y_test
        self.tokenizer = tokenizer
        self.batch_size = batch_size
        self.max_token_len = max_token_len

    def setup(self):
        self.train_dataset = QTagDataset(quest=self.tr_text, tags=self.tr_label, tokenizer=self.tokenizer,
                                         max_len=self.max_token_len)
        self.val_dataset = QTagDataset(quest=self.val_text, tags=self.val_label, tokenizer=self.tokenizer,
                                       max_len=self.max_token_len)
        self.test_dataset = QTagDataset(quest=self.test_text, tags=self.test_label, tokenizer=self.tokenizer,
                                        max_len=self.max_token_len)

    def train_dataloader(self):
        return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True, num_workers=0)

    def val_dataloader(self):
        return DataLoader(self.val_dataset, batch_size=16)

    def test_dataloader(self):
        return DataLoader(self.test_dataset, batch_size=16)

# Initialize the Bert tokenizer
BERT_MODEL_NAME = 'bert-base-cased' # we will use the BERT base model(the smaller one)
Bert_tokenizer = BertTokenizer.from_pretrained('bert-base-cased')

max_word_cnt = 500
desc_cnt = 0

# For every sentence...
for des in desc:

    # Tokenize the text and add `[CLS]` and `[SEP]` tokens.
    input_ids = Bert_tokenizer.encode(des, add_special_tokens=True)

    # Update the maximum sentence length.
    if len(input_ids) > max_word_cnt:
        desc_cnt +=1

print(f'# Description having word count > {max_word_cnt}: is  {desc_cnt}')

# Initialize the parameters that will be use for training
N_EPOCHS = 20
BATCH_SIZE = 16
MAX_LEN = 500
LR = 7e-05

# Instantiate and set up the data_module
QTdata_module = QTagDataModule(x_tr,y_tr,x_val,y_val,x_test,y_test,Bert_tokenizer,BATCH_SIZE,MAX_LEN)
QTdata_module.setup()

# Initialize the Bert tokenizer
BERT_MODEL_NAME = 'bert-base-cased' # we will use the BERT base model(the smaller one)
Bert_tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
class QTagClassifier(pl.LightningModule):
    # Set up the classifier
    def __init__(self, n_classes=20, steps_per_epoch=None, n_epochs=35, lr=7e-5):
        super().__init__()

        self.bert = BertModel.from_pretrained(BERT_MODEL_NAME, return_dict=True)
        self.classifier = nn.Linear(self.bert.config.hidden_size, n_classes)  # outputs = number of labels
        self.steps_per_epoch = steps_per_epoch
        self.n_epochs = n_epochs
        self.lr = lr
        self.criterion = nn.BCEWithLogitsLoss(pos_weight = pos_weights)

    def forward(self, input_ids, attn_mask):
        output = self.bert(input_ids=input_ids, attention_mask=attn_mask)
        output = self.classifier(output.pooler_output)

        return output

    def training_step(self, batch, batch_idx):
        input_ids = batch['input_ids']
        attention_mask = batch['attention_mask']
        labels = batch['label']

        outputs = self(input_ids, attention_mask)
        loss = self.criterion(outputs, labels)
        self.log('train_loss', loss, prog_bar=True, logger=True)

        return {"loss": loss, "predictions": outputs, "labels": labels}

    def validation_step(self, batch, batch_idx):
        input_ids = batch['input_ids']
        attention_mask = batch['attention_mask']
        labels = batch['label']

        outputs = self(input_ids, attention_mask)
        loss = self.criterion(outputs, labels)
        self.log('val_loss', loss, prog_bar=True, logger=True)

        return loss

    def test_step(self, batch, batch_idx):
        input_ids = batch['input_ids']
        attention_mask = batch['attention_mask']
        labels = batch['label']

        outputs = self(input_ids, attention_mask)
        loss = self.criterion(outputs, labels)
        self.log('test_loss', loss, prog_bar=True, logger=True)

        return loss

    def configure_optimizers(self):
        optimizer = AdamW(self.parameters(), lr=self.lr)
        warmup_steps = self.steps_per_epoch // 3
        total_steps = self.steps_per_epoch * self.n_epochs - warmup_steps

        scheduler = get_linear_schedule_with_warmup(optimizer, warmup_steps, total_steps)

        return [optimizer], [scheduler]

# Initialize the parameters that will be use for training
N_EPOCHS = 20
BATCH_SIZE = 16
MAX_LEN = 500
LR = 7e-05

# Instantiate the classifier model
steps_per_epoch = len(x_tr)//BATCH_SIZE
model = QTagClassifier(n_classes=20, steps_per_epoch=steps_per_epoch,n_epochs=N_EPOCHS,lr=LR)

#if call from checkpoint
#model_path = "" #if calling from checkpoint, manually input model from lightning+logs folder
#model = QTagClassifier.load_from_checkpoint(model_path)

#Initialize Pytorch Lightning callback for Model checkpointing

# saves a file like: input/QTag-epoch=02-val_loss=0.32.ckpt
checkpoint_callback = ModelCheckpoint(
    monitor='val_loss',# monitored quantity
    filename='QTag-{epoch:02d}-{val_loss:.2f}',
    save_top_k=3, #  save the top 3 models
    mode='min', # mode of the monitored quantity  for optimization
)

# Instantiate the Model Trainer

#if resuming from checkpoint
#trainer = pl.Trainer(max_epochs = N_EPOCHS , gpus = 1, callbacks=[checkpoint_callback],resume_from_checkpoint = model_path, progress_bar_refresh_rate = 30)

#If training from scratch
trainer = pl.Trainer(max_epochs = N_EPOCHS , gpus = 1, callbacks=[checkpoint_callback], progress_bar_refresh_rate = 30)

# Train the Classifier Model
trainer.fit(model, QTdata_module)

# Retreiving checkpoint path for best model
model_path = checkpoint_callback.best_model_path

# Evaluate the model performance on the test dataset
trainer.test(model,datamodule=QTdata_module)

# Visualize the logs using tensorboard.
#run in terminal if not using colab/jupyter
#%load_ext tensorboard
#%tensorboard --logdir lightning_logs/

len(y_test), len(x_test)

# Size of Test set
print(f'Number of Games = {len(x_test)}')

from torch.utils.data import TensorDataset

# Tokenize all questions in x_test
input_ids = []
attention_masks = []

for quest in x_test:
    encoded_quest = Bert_tokenizer.encode_plus(
        quest,
        None,
        add_special_tokens=True,
        max_length=MAX_LEN,
        padding='max_length',
        return_token_type_ids=False,
        return_attention_mask=True,
        truncation=True,
        return_tensors='pt'
    )

    # Add the input_ids from encoded question to the list.
    input_ids.append(encoded_quest['input_ids'])
    # Add its attention mask
    attention_masks.append(encoded_quest['attention_mask'])

# Now convert the lists into tensors.
input_ids = torch.cat(input_ids, dim=0)
attention_masks = torch.cat(attention_masks, dim=0)
labels = torch.tensor(y_test)

# Set the batch size.
TEST_BATCH_SIZE = 64

# Create the DataLoader.
pred_data = TensorDataset(input_ids, attention_masks, labels)
pred_sampler = SequentialSampler(pred_data)
pred_dataloader = DataLoader(pred_data, sampler=pred_sampler, batch_size=TEST_BATCH_SIZE)

flat_pred_outs = 0
flat_true_labels = 0

# Put model in evaluation mode
model = model.to(device)  # moving model to cuda
model.eval()

# Tracking variables
pred_outs, true_labels = [], []
# i=0
# Predict
for batch in pred_dataloader:
    # Add batch to GPU
    batch = tuple(t.to(device) for t in batch)

    # Unpack the inputs from our dataloader
    b_input_ids, b_attn_mask, b_labels = batch

    with torch.no_grad():
        # Forward pass, calculate logit predictions
        pred_out = model(b_input_ids, b_attn_mask)
        pred_out = torch.sigmoid(pred_out)
        # Move predicted output and labels to CPU
        pred_out = pred_out.detach().cpu().numpy()
        label_ids = b_labels.to('cpu').numpy()
        # i+=1
        # Store predictions and true labels
        # print(i)
        # print(outputs)
        # print(logits)
        # print(label_ids)
    pred_outs.append(pred_out)
    true_labels.append(label_ids)

pred_outs[0][0]

# Combine the results across all batches.
flat_pred_outs = np.concatenate(pred_outs, axis=0)

# Combine the correct labels for each batch into a single list.
flat_true_labels = np.concatenate(true_labels, axis=0)

flat_pred_outs.shape, flat_true_labels.shape

# define candidate threshold values
threshold = np.arange(0.5, 0.99, 0.01)
threshold

# convert probabilities into 0 or 1 based on a threshold value
def classify(pred_prob, thresh):
    y_pred = []
    for tag_label_row in pred_prob:
        temp = []
        for tag_label in tag_label_row:
            if tag_label >= thresh:
                temp.append(1)  # Infer tag value as 1 (present)
            else:
                temp.append(0)  # Infer tag value as 0 (absent)
        y_pred.append(temp)

    return y_pred

from sklearn import metrics

scores = []  # Store the list of f1 scores for prediction on each threshold

# convert labels to 1D array
y_true = flat_true_labels.ravel()

for thresh in threshold:
    # classes for each threshold
    pred_bin_label = classify(flat_pred_outs, thresh)

    # convert to 1D array
    y_pred = np.array(pred_bin_label).ravel()

    scores.append(metrics.f1_score(y_true, y_pred))

# find the optimal threshold
opt_thresh = threshold[scores.index(max(scores))]
print(f'Optimal Threshold Value = {opt_thresh}')

# predictions for optimal threshold
y_pred_labels = classify(flat_pred_outs, opt_thresh)
y_pred = np.array(y_pred_labels).ravel()  # Flatten

print(metrics.classification_report(y_true, y_pred))

y_pred = mlb.inverse_transform(np.array(y_pred_labels))
y_act = mlb.inverse_transform(flat_true_labels)

df = pd.DataFrame({'Body': x_test, 'Actual Tags': y_act, 'Predicted Tags': y_pred})

df.sample(10)
df.to_csv('test_result.csv')

QTmodel = QTagClassifier.load_from_checkpoint(model_path)
def predict(question):
    text_enc = Bert_tokenizer.encode_plus(
            question,
            None,
            add_special_tokens=True,
            max_length= MAX_LEN,
            padding = 'max_length',
            return_token_type_ids= False,
            return_attention_mask= True,
            truncation=True,
            return_tensors = 'pt'
    )
    outputs = QTmodel(text_enc['input_ids'], text_enc['attention_mask'])
    pred_out = outputs[0].detach().numpy()
    #print(f'Outputs = {outputs}')
    #print(f'Type = {type(outputs)}')
    #print(f'Pred Outputs = {pred_out}')
    #print(f'Type = {type(pred_out)}')
    #preds = np.round(pred_out)
    preds = [(pred > opt_thresh) for pred in pred_out ]
    #pred_list = [ round(pred) for pred in pred_logits ]
    preds = np.asarray(preds)
    #print(f'Predictions = {preds}')
    #print(f'Type = {type(preds)}')
    #print(mlb.classes_)
    new_preds = preds.reshape(1,-1).astype(int)
    #print(new_preds)
    pred_tags = mlb.inverse_transform(new_preds)
    #print(mlb.inverse_transform(np.array(new_preds)))
    return pred_tags

#model inference
description = "Elden Ring is an action role-playing game played in a third-person perspective with gameplay focusing on combat and exploration; " \
              "it features elements similar to those found in other games developed by FromSoftware, such as the Souls series, Bloodborne, and " \
              "Sekiro: Shadows Die Twice. Director Hidetaka Miyazaki explained that players start with a linear opening but eventually progress to " \
              "freely explore the Lands Between, including its six main areas, as well as castles, fortresses, and catacombs scattered throughout the " \
              "open world map. These main areas are interconnected through a central hub that players can access later in the game's progression—similar to" \
              "Firelink Shrine from Dark Souls—and are explorable using the character's mount as the main mode of transport, " \
              "although a fast travel system is an available option. Throughout the game, players encounter non-player characters (NPCs) and " \
              "enemies alike, including the demigods who rule each main area and serve as the game's main bosses.["

tags = predict(description)
if not tags[0]:
    print('This Game can not be associated with any known tag - Please review to see if a new tag is required ')
else:
    print(f'Following Tags are associated : \n {tags}')


import numpy as np
import tensorflow as tf

class Recommendation:
    def __init__(self,filename):
        self.model = tf.keras.models.load_model(filename)

    def predict(self, array):
        # Manual One-hot Encoding Rules
        energetic = ["Extraverts - E", "Introverts - I"]
        energetic_to_num = dict((c, i) for i, c in enumerate(energetic))
        num_to_energetic = dict((i, c) for i, c in enumerate(energetic))

        sense = ["Sensors - S", "Intuitive - N"]
        sense_to_num = dict((c, i) for i, c in enumerate(sense))
        num_to_sense = dict((i, c) for i, c in enumerate(sense))

        decision = ["Thinkers - T", "Feelers - F"]
        decision_to_num = dict((c, i) for i, c in enumerate(decision))
        num_to_decision = dict((i, c) for i, c in enumerate(decision))

        plan = ["Judgers - J", "Perceivers - P"]
        plan_to_num = dict((c, i) for i, c in enumerate(plan))
        num_to_plan = dict((i, c) for i, c in enumerate(plan))

        behaviour = ["Analyst (Purple)", "Diplomat (Green)", "Sentinel (Blue)", "Explorer (Red)"]
        behaviour_to_num = dict((c, i) for i, c in enumerate(behaviour))
        num_to_behaviour = dict((i, c) for i, c in enumerate(behaviour))

        activities_type = ["Happy and carefree", "Calm and creative", "Healthy and satisfied", "Productive and progressing"]
        actType_to_num = dict((c, i) for i, c in enumerate(activities_type))
        num_to_actType = dict((i, c) for i, c in enumerate(activities_type))

        inout = ["Outdoors", "Indoors", "Both", "It doesn't matter"]
        inout_to_num = dict((c, i) for i, c in enumerate(inout))
        num_to_inout = dict((i, c) for i, c in enumerate(inout))

        cc = ["I feel better when I create", "I feel better when I consume", "Both", "I dislike both"]
        cc_to_num = dict((c, i) for i, c in enumerate(cc))
        num_to_cc = dict((i, c) for i, c in enumerate(cc))

        gender = ["Male", "Female"]
        gender_to_num = dict((c, i) for i, c in enumerate(gender))
        num_to_gender = dict((i, c) for i, c in enumerate(gender))

        depActivities = ["Meditation", "Religious Prayer", "Yoga", "Journaling", "Enjoy good food", "Resting - Sleep", "Creating Art", "Exercise", "Diet", "Spending time with loved ones", "Confess to family or loved ones (without hiding anything)", "Baking", "Reading", "Watch movies/ anime/ series", "Listen to music", "Enjoying the nature", "Attend counseling session", "Consult with doctor", "Gaming", "Shopping", "Travelling", "Playing musical instruments"]
        depActivities_to_num = dict((c, i) for i, c in enumerate(depActivities))
        num_to_depActivities = dict((i, c) for i, c in enumerate(depActivities))

        userInput = []
        userInput.append(energetic_to_num[array[0]])
        userInput.append(sense_to_num[array[1]])
        userInput.append(decision_to_num[array[2]])
        userInput.append(plan_to_num[array[3]])
        userInput.append(behaviour_to_num[array[4]])
        userInput.append(actType_to_num[array[5]])
        userInput.append(inout_to_num[array[6]])
        userInput.append(cc_to_num[array[7]])
        userInput.append(gender_to_num[array[8]])

        print(userInput)
        print(userInput)
        print(userInput)
        print(userInput)
        print(userInput)
        print(userInput)
        print(userInput)
        print(userInput)

        yhat = self.model.predict(np.asarray([userInput]))
        predict = np.argpartition(yhat[0], -4)[-4:]
        result = np.round(yhat, decimals=2, out=None)
        print('Predicted: %s' % np.round(yhat, decimals=2, out=None))

        suggestion = []
        for i, j in enumerate(predict):
            suggestion.append(depActivities[j])
            print("Suggestion Top " + str(i+1) + ": " + depActivities[j])

        return result, suggestion