# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.osv.expression import NEGATIVE_TERM_OPERATORS, TERM_OPERATORS_NEGATION, TRUE_LEAF, FALSE_LEAF
from odoo.tools.safe_eval import safe_eval

import copy

# 100.000.000 ids devraient suffire pour les produits. Les chiffres suivants serviront pour le fournisseur
DATASTORE_IND = 100000000

def _of_datastore_is_computed_field(model_obj, field_name):
    env = model_obj.env
    field = model_obj._fields[field_name]
    if not field.compute:
        return False
    if field.company_dependent:
        return False
    if not field._description_related:
        return True

    # Traitement particulier des champs related en fonction de la relation
    if model_obj._of_datastore_is_computed_field(field._description_related[0]):
        return True
    f = model_obj._fields[field._description_related[0]]
    for f_name in field._description_related[1:]:
        obj = env[f.comodel_name]
        if not hasattr(obj, '_of_datastore_is_computed_field'):
            # L'objet référencé n'est pas un objet centralisé
            return True
        if obj._of_datastore_is_computed_field(f_name):
            return True
        f = obj._fields[f_name]
    return False

class OfImportProductCategConfig(models.Model):
    _inherit = 'of.import.product.categ.config'

    is_datastore_matched = fields.Boolean('Is centralized')

class OfDatastoreCentralized(models.AbstractModel):
    _name = 'of.datastore.centralized'

    of_datastore_res_id = fields.Integer(string="ID on supplier database", index=True, copy=False)

    @classmethod
    def _browse(cls, ids, env, prefetch=None):
        # Il est important d'hériter de _browse() et non de browse()
        #  car c'est _browse() qui est appelé dans fields.Many2one.convert_to_record()
        result = super(OfDatastoreCentralized, cls)._browse(ids, env, prefetch=prefetch)
        for i in ids:
            if i < 0:
                # Appel de super() pour éviter un appel récursif infini
                record = super(OfDatastoreCentralized, cls)._browse((i, ), env, prefetch=prefetch)
                if not record._cache:
                    env['of.datastore.cache'].apply_values(record)
        return result

    @api.model
    def _get_datastore_unused_fields(self):
        u"""
        Retourne la liste des champs qu'on ne veut pas récupérer chez le fournisseur (par ex. les quantités en stock).
        """
        cr = self._cr

        # On ne veut aucun des champs ajoutés par les modules stock, mrp, purchase
        cr.execute(
            "SELECT f.name "
            "FROM ir_model_data AS d "
            "INNER JOIN ir_model_fields AS f "
            "  ON d.res_id=f.id "
            "WHERE d.model = 'ir.model.fields' "
            "  AND f.model = %s "
            "  AND d.module IN ('mrp','procurement','stock')",
            (self._name, ))
        res = [row[0] for row in cr.fetchall()]

        # Ajout de certains champs
        res += [
            'invoice_policy',
            'purchase_method',

            # Champs à valeur forcée manuellement pour l'import
            'of_import_categ_id',
            'of_import_cout',
            'of_import_price',
            'of_import_remise',

            # Champs de notes
            'description_sale',  # Description pour les devis
            'description_purchase',  # Description pour les fournisseurs
            'description_picking',  # Description pour le ramassage

            # Champs de structure de prix
            'of_purchase_transport',
            'of_sale_transport',
            'of_sale_coeff',
            'of_other_logistic_costs',
            'of_misc_taxes',
            'of_misc_costs',

            # Champs de localisation d'inventaire
            'property_stock_procurement',
            'property_stock_production',
            'property_stock_inventory',
            'of_product_posx',
            'of_product_posy',
            'of_product_posz',
        ]

        # On ne veut pas non-plus les champs one2many ou many2many
        # (seller_ids, packagind_ids, champs liés aux variantes....)
        # On conserve les lignes de kits
        for field_name, field in self._fields.iteritems():
            if field.type in ('one2many', 'many2many'):
                if field_name != 'kit_line_ids' and field_name not in res:
                    res.append(field_name)
        return res

    @api.model
    def _of_datastore_is_computed_field(self, field_name):
        if field_name == 'of_seller_name':
            # Le fournisseur est directement défini par la marque
            return True
        if field_name == 'of_theoretical_cost':
            # Cas particulier pour le coût théorique qu'on veut récupérer du TC
            return False
        return _of_datastore_is_computed_field(self, field_name)

    @api.model
    def _of_get_datastore_computed_fields(self):
        u"""
            Retourne la liste des champs qu'on ne veut pas récupérer chez le fournisseur
            mais qu'il faudra calculer en local (par ex. le champ price qui dépend de la liste de prix du context).
        """
        return [field for field in self._fields if self._of_datastore_is_computed_field(field)]

    @api.multi
    def _of_read_datastore(self, fields_to_read, create_mode=False):
        u"""
        Lit les donnees des produits dans leur base fournisseur.
        @param ids: id modifié des produits, en valeur négative
        @param create_mode: En mode create on ne renvoie pas les champs non trouvés (remplis avec False ou [])
                            En mode create on remplit seller_ids
        """
        supplier_obj = self.env['of.datastore.supplier']
        product_tmpl_obj = self.env['product.template']
        result = []

        # Certains champs sont nécessaires pour le calcul d'autres champs :
        # - brand_id : La marque, depuis laquelle on extrait les règles de lecture
        # - categ_id : La catégorie, qui peut correspondre à des règles de lecture plus spécifiques dans la marque
        # - product_tmpl_id : L'article de base, utile pour of_tmpl_datastore_res_id
        # - default_code : La référence de l'article, utile pour of_seller_product_code
        # - uom_id et uom_po_id : Les unités de mesure et de mesure d'achat de l'article,
        #   utiles pour calculer les prix d'achat/vente
        # - list_price : Le prix d'achat de l'article,
        #   à partir duquel sont réalisés les calculs pour le prix de vente et le coût
        # En mode création (create_mode == True), ces champs sont obligatoires donc déjà présents dans fields_to_read.
        # En lecture classique (create_mode == False), nous testons si au moins un champ de fields_to_read
        #   nécessite un accès distant (avec self._get_datastore_unused_fields()).
        #   Si c'est le cas, nous chargeons fields_to_read avec tous les champs de l'objet courant afin de peupler
        #   notre cache et ainsi d'éviter de multiplier les accès distants.
        unused_fields = self._get_datastore_unused_fields() + self._of_get_datastore_computed_fields()
        if not create_mode:
            # Pour la lecture classique, on veut stocker tous les champs en cache pour éviter de futurs accès distants
            for field in fields_to_read:
                if field not in unused_fields:
                    fields_to_read += [field for field in self._fields if field not in fields_to_read]
                    break

        if 'id' in fields_to_read:  # Le champ id sera de toute façon ajouté, le laisser génèrera des erreurs
            fields_to_read.remove('id')

        # Articles par fournisseur
        datastore_product_ids = {}

        # Produits par fournisseur
        for full_id in self._ids:
            supplier_id = -full_id / DATASTORE_IND
            datastore_product_ids.setdefault(supplier_id, []).append((-full_id) % DATASTORE_IND)

        # Champs a valeurs spécifiques
        fields_defaults = [
            ('of_datastore_supplier_id',        lambda: create_mode and supplier_id or supplier.sudo().name_get()[0]),
            ('of_datastore_res_id',             lambda: vals['id']),
            ('of_seller_pp_ht',                 lambda: vals['of_seller_pp_ht']),
            ('of_seller_product_category_name', lambda: vals['categ_id'][1]),
            ('of_tmpl_datastore_res_id',        lambda: vals['product_tmpl_id'][0]),
            ('description_norme',               lambda: product.description_norme or vals['description_norme']),
            ('of_template_image',               lambda: vals.get('of_template_image') or product.image),
            # Attention, l'ordre des deux lignes suivantes est important
            ('of_seller_product_code',          lambda: vals['default_code']),
            ('default_code',                    lambda: default_code_func[brand](vals['default_code'])),
        ]

        fields_defaults = [(k, v) for k, v in fields_defaults if k in fields_to_read]
        if create_mode:
            # Ajout des champs nécessaires à la creation du product_supplierinfo
            for field in ('of_seller_delay',):
                if field not in fields_to_read:
                    fields_to_read.append(field)

            # Création de la relation fournisseur
            fields_defaults.append(('seller_ids', lambda: [(5, ), (0, 0, {
                'name':    brand.partner_id.id,
                'min_qty': 1,
                'delay':   vals['of_seller_delay'],
            })]))

        datastore_fields = [field for field in fields_to_read if field not in unused_fields]

        m2o_fields = [
            field for field in datastore_fields
            if self._fields[field].type == 'many2one' and
            field != 'of_datastore_supplier_id'
        ]

        o2m_fields = ['kit_line_ids']

        for supplier_id, product_ids in datastore_product_ids.iteritems():
            supplier_value = supplier_id * DATASTORE_IND
            if not datastore_fields:
                if create_mode:
                    result += [{'id': product_id} for product_id in product_ids]
                else:
                    # Pas d'accès à la base centrale, on remplit l'id et on met tout le reste à False ou []
                    datastore_defaults = {
                        field: [] if self._fields[field].type in ('one2many', 'many2many') else False
                        for field in fields_to_read
                        if field != 'id'
                    }
                    result += [
                        dict(datastore_defaults, id=-(product_id + supplier_value))
                        for product_id in product_ids]
                continue
            supplier = supplier_obj.browse(supplier_id)
            client = supplier.of_datastore_connect()
            ds_product_obj = supplier_obj.of_datastore_get_model(client, self._name)

            datastore_product_data = supplier_obj.of_datastore_read(
                ds_product_obj, product_ids, datastore_fields, '_classic_read')

            if not create_mode:
                # Les champs manquants dans la table du fournisseur ne sont pas renvoyés, sans générer d'erreur
                # Il faut donc leur attribuer une valeur par défaut
                missing_fields = [field for field in fields_to_read if field not in datastore_product_data[0]]
                # Valeur remplie en 2 étapes
                # 1 : on met une valeur vide (False ou [] pour des one2many)
                datastore_defaults = {
                    field: [] if self._fields[field].type in ('one2many', 'many2many') else False
                    for field in missing_fields
                }
                # 2 : On renseigne les valeurs qui sont trouvées avec la fonction default_get
                datastore_defaults.update(product_tmpl_obj.default_get(missing_fields))

            # Traitement des données
            match_dicts = {}
            match_dicts['brand_id'] = {brand.datastore_brand_id: brand for brand in supplier.brand_ids}

            # Calcul de la fonction à appliquer sur la référence des articles de chaque marque
            if 'default_code' in fields_to_read:
                default_code_func = supplier.get_product_code_convert_func(client)

            datastore_read_m2o_fields = [field for field in m2o_fields if field in datastore_product_data[0]]
            field_res_ids = {field: set() for field in datastore_read_m2o_fields}
            for vals in datastore_product_data:
                # --- Calculs préalables ---
                brand = match_dicts['brand_id'][vals['brand_id'][0]]
                # Ajouter un search par article est couteux.
                # Tant pis pour les règles d'accès, on fait une recherche SQL
                if self._name == 'product.template':
                    self._cr.execute(
                        "SELECT id FROM product_template "
                        "WHERE brand_id = %s AND of_datastore_res_id = %s",
                        (brand.id, vals['id']))
                else:
                    self._cr.execute(
                        "SELECT t.id FROM product_product p "
                        "INNER JOIN product_template t ON t.id=p.product_tmpl_id "
                        "WHERE t.brand_id = %s AND p.of_datastore_res_id = %s",
                        (brand.id, vals['id']))
                rows = self._cr.fetchall()
                product = product_tmpl_obj.browse(rows and rows[0][0])
                categ_name = vals['categ_id'][1]
                obj_dict = {}

                # Calcul des valeurs spécifiques
                for field, val in fields_defaults:
                    vals[field] = val()
                if create_mode:
                    del vals['id']
                else:
                    vals['id'] = -(vals['id'] + supplier_value)
                    vals.update(datastore_defaults)

                # ---- Champs many2one ---
                for field in datastore_read_m2o_fields:
                    # Conversion du many2one pour la base courante
                    if vals[field]:
                        obj = self._fields[field].comodel_name
                        res = brand.datastore_match(
                            client, obj, vals[field][0], vals[field][1], product, match_dicts, create=create_mode)
                        if field in ('categ_id', 'uom_id', 'uom_po_id'):
                            obj_dict[field] = res
                        if res:
                            if res.id < 0:
                                # Valeur de la base centrale
                                # Normalement uniquement utilisé pour product_tmpl_id
                                vals[field] = (res.id, vals[field][1])
                            else:
                                vals[field] = res.id
                                field_res_ids[field].add(res.id)
                        else:
                            vals[field] = False

                # --- Champs x2many ---
                for field in o2m_fields:
                    if field not in datastore_fields:
                        continue
                    if not vals[field]:
                        continue
                    line_ids = [-(line_id + supplier_value) for line_id in vals[field]]
                    if create_mode:
                        # Preparation des lignes
                        obj = self._fields[field].comodel_name
                        obj_obj = self.env[obj]
                        vals[field] = [(5, )] + [(0, 0, line.copy_data()[0]) for line in obj_obj.browse(line_ids)]
                    else:
                        # Conversion en id datastore
                        # Parcours avec indice pour ne pas recreer la liste
                        vals[field] = line_ids

                # --- Champs spéciaux ---
                vals['of_datastore_has_link'] = bool(product)

                # Prix d'achat/vente
                # Note: On retire 'standard_price' de vals car dans certains cas on ne veut pas le mettre à jour
                vals.update(brand.compute_product_price(
                    vals['of_seller_pp_ht'], categ_name, obj_dict['uom_id'], obj_dict['uom_po_id'], product=product,
                    price=vals['of_seller_price'], remise=None, cost=vals.pop('standard_price', None),
                    based_on_price=vals['of_is_net_price']))
                # Calcul de la marge et de la remise
                if 'of_seller_remise' in fields_to_read:
                    vals['of_seller_remise'] =\
                        vals['of_seller_pp_ht'] and\
                        (vals['of_seller_pp_ht'] - vals['of_seller_price']) * 100 / vals['of_seller_pp_ht']
                if 'marge' in fields_to_read:
                    vals['marge'] =\
                        vals['list_price'] and\
                        (vals['list_price'] - vals.get('standard_price', 0)) * 100 / vals['list_price']

                # Suppression des valeurs non voulues
                # Suppression désactivée ... après tout, c'est calculé maintenant, autant le garder en cache
                # for field in added_fields:
                #     vals.pop(field, False)

            if not create_mode:
                # Conversion au format many2one (id,name)
                for field, res_ids in field_res_ids.iteritems():
                    if not res_ids:
                        continue

                    obj = self._fields[field].comodel_name
                    res_obj = self.env[obj].browse(res_ids)
                    res_names = {v[0]: v for v in res_obj.sudo().name_get()}
                    for vals in datastore_product_data:
                        # Test en deux temps car en python, False est une instance de int
                        if vals.get(field) and isinstance(vals[field], (int, long)):
                            vals[field] = res_names[vals[field]]

            result += datastore_product_data
        return result

    def _recompute_check(self, field):
        # Les champs calculés et stockés en base de données doivent toujours être recalculés
        # dans le cas des éléments centralisés.
        result = super(OfDatastoreCentralized, self)._recompute_check(field)
        res1 = self.filtered(lambda record: record.id < 0)
        if result and res1:
            res1 |= result
        # En cas d'ensemble vide, c'est result qui est renvoyé, qui vaut None
        return res1 or result

    @api.multi
    def read(self, fields=None, load='_classic_read'):
        new_ids = [i for i in self._ids if i > 0]

        # Produits sur la base courante
        res = super(OfDatastoreCentralized, self.browse(new_ids)).read(fields, load=load)

        if len(new_ids) != len(self._ids):
            cache_obj = self.env['of.datastore.cache']
            # Si fields est vide, on récupère tous les champs accessibles pour l'objet (copié depuis BaseModel.read())
            self.check_access_rights('read')
            fields = self.check_field_access_rights('read', fields)
            fields = set(fields)
            if 'id' in fields:
                fields.remove('id')
            obj_fields = [self._fields[field] for field in fields]
            use_name_get = (load == '_classic_read')
            # Gestion à part des modèles d'articles, sans quoi of_read_datastore sera appelé pour name_get()
            #   une fois par modèle.
            read_tmpl = self._fields.get('product_tmpl_id') in obj_fields
            tmpl_values = {}

            # Séparation des ids par base centrale
            datastore_product_ids = {}
            for full_id in self._ids:
                if full_id < 0:
                    datastore_product_ids.setdefault(-full_id / DATASTORE_IND, []).append(full_id)

            res = {vals['id']: vals for vals in res}
            for supplier_id, datastore_ids in datastore_product_ids.iteritems():
                with self.env['of.datastore.cache']._get_cache_token(supplier_id) as of_cache:
                    # Vérification des données dans notre cache
                    cached_products = of_cache.search([('model', '=', self._name), ('res_id', 'in', datastore_ids)])

                    # Les articles non en cache sont à lire
                    new_ids = set(datastore_ids) - set(cached_products.mapped('res_id'))

                    # Si au moins un objet est inexistant en cache, tous les champs sont à lire
                    new_fields = set(fields) if new_ids else set()

                    # Les articles dont au moins un champ n'est pas en cache sont aussi à lire, pour au moins ce champ
                    for cached_product in cached_products:
                        product_data = safe_eval(cached_product.vals)
                        missing_fields = fields - set(product_data.keys())
                        if missing_fields:
                            new_fields |= missing_fields
                            new_ids.add(cached_product.res_id)
                        if read_tmpl and 'product_tmpl_id' in product_data:
                            tmpl_values[product_data['product_tmpl_id'][0]] = product_data['product_tmpl_id']

                    if new_ids:
                        # Lecture des données sur la base centrale
                        data = self.browse(new_ids)._of_read_datastore(list(new_fields), create_mode=False)

                        # Stockage des données dans notre cache
                        of_cache.store_values(self._name, data)

                        if read_tmpl and 'product_tmpl_id' in new_fields:
                            for d in data:
                                tmpl_values[d['product_tmpl_id'][0]] = d['product_tmpl_id']

                for obj in self.browse(datastore_ids):
                    # Il faut charger les valeurs dans le cache manuellement car elles ne se chargent de façon
                    #   automatique que si le cache est vide, ce qui n'est plus le cas à ce stade.
                    cache_obj.apply_values(obj)
                    # Filtre des champs à récupérer et conversion au format read
                    vals = {
                        field.name: field.convert_to_read(obj[field.name], self, use_name_get)
                        for field in obj_fields
                    }
                    vals['id'] = obj.id
                    res[obj.id] = vals

                    if read_tmpl:
                        vals['product_tmpl_id'] = tmpl_values[obj.product_tmpl_id.id]

            # Remise des résultats dans le bon ordre
            res = [res[i] for i in self._ids]
        return res

    @api.model
    def of_datastore_update_domain(self, domain):
        u"""
        Vérifie si le domaine indique une recherche sur une base de données fournisseur.
        Si oui, retourne le domaine de recherche adapté pour la base de données fournisseur.
        @requires: Si args contient un tuple dont le premier argument est 'ds_supplier_search_id',
                   le second argument doit être '='
        @return: Id du fournisseur (of.datastore.supplier) ou False sinon, suivi du nouveau domaine de recherche
        """
        if 'of_datastore_product_search' not in domain:
            return False, domain

        domain = [copy.copy(arg) for arg in domain if arg != 'of_datastore_product_search']

        # Recherche des marques
        brand_domain = []
        for arg in domain:
            if not isinstance(arg, (list, tuple)):
                continue
            if arg[0] == 'brand_id':
                operator, right = arg[1], arg[2]
                # resolve string-based m2o criterion into IDs
                if isinstance(right, basestring) or\
                        right\
                        and isinstance(right, (tuple, list))\
                        and all(isinstance(item, basestring) for item in right):
                    brand_domain.append(('name', operator, right))
                else:
                    brand_domain.append(('id', operator, right))
        brands = self.env['of.product.brand'].search(brand_domain)
        ds_supplier = brands.mapped('datastore_supplier_id')

        if not ds_supplier:
            if brands:
                raise UserError(_('Selected brands are not centralized : %s') % ", ".join(brands.mapped('name')))
            return False, [FALSE_LEAF]

        if len(ds_supplier) > 1:
            raise UserError(_('You must select one or several brands using the same centralized database '
                              '(provided by the same supplier).'))
        brands = brands.filtered('datastore_supplier_id')

        # Recherche des produits non déjà enregistrés
        if self._context.get('datastore_not_stored'):
            orig_ids = self.sudo().with_context(active_test=False)\
                .search([('brand_id', 'in', brands._ids), ('of_datastore_res_id', '!=', False)])\
                .mapped('of_datastore_res_id')
            domain.append(('id', 'not in', orig_ids))

        parse_domain = self._of_datastore_update_domain_item

        # Conversion des champs
        for arg in domain:
            if not isinstance(arg, (list, tuple)):
                continue
            if arg[0].startswith('ds_'):
                arg[0] = arg[0][3:]
            elif arg[0] in ('categ_id', 'brand_id'):
                obj_name = self._fields[arg[0]].comodel_name
                new_arg = parse_domain(arg, self.env[obj_name])
                if new_arg:
                    arg[0], arg[1], arg[2] = new_arg
        return brands, domain

    @api.model
    def _of_datastore_update_domain_item(self, domain, obj):
        u""" Convertit un élément du domaine pour utilisation sur la base centrale
        @type domain: Tuple (left, operator, right)
        @var obj: Objet sur lequel doit s'appliquer le domaine
        @type obj: Browse record

        @todo: Permettre la conversion de la recherche sur catégories d'articles
        """
        left, operator, right = domain
        if obj._name == 'product.category':
            # Une categorie d'articles peut avoir une correspondance différente selon la marque ou l'article.
            # La conversion est compliquée
            if isinstance(right, basestring) or \
                    right and isinstance(right, (tuple, list)) and all(isinstance(item, basestring) for item in right):
                return False
            elif isinstance(right, (int, long)) and right < 0:
                return domain
            else:
                return TRUE_LEAF

        if operator in NEGATIVE_TERM_OPERATORS:
            operator = TERM_OPERATORS_NEGATION[operator]
            new_operator = 'not in'
        else:
            new_operator = 'in'

        if isinstance(right, basestring) or \
                right and isinstance(right, (tuple, list)) and all(isinstance(item, basestring) for item in right):
            obj_domain = [('name', operator, right)]
        else:
            obj_domain = [('id', operator, right)]
        obj = obj.search(obj_domain)

        result = False
        if obj._name == 'of.product.brand':
            result = (left, new_operator, obj.mapped('datastore_brand_id'))
        return result

    @api.model
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        brands, domain = self.of_datastore_update_domain(domain)
        # Recherche sur la base du fournisseur
        if brands:
            supplier = brands[0].datastore_supplier_id
            # Ex: si la base n'a qu'une base centralisée, elle peut appeler les articles de la base distante
            #     sans autre filtre de recherche.
            # Dans ce cas, on ne veut pas les autres marques du fournisseur
            domain = ['&', ('brand_id', 'in', brands.mapped('datastore_brand_id'))] + domain

            supplier_obj = self.env['of.datastore.supplier']

            # Exécution de la requête sur la base du fournisseur
            client = supplier.of_datastore_connect()
            if isinstance(client, basestring):
                # Échec de la connexion à la base fournisseur
                raise UserError(u'Erreur accès '+supplier.db_name)

            ds_product_obj = supplier_obj.of_datastore_get_model(client, self._name)
            res = supplier_obj.of_datastore_read_group(ds_product_obj, domain, fields, groupby, offset, limit, orderby, lazy)
            for row in res:
                for arg in row['__domain']:
                    if isinstance(arg, (list, tuple)):
                        arg[0] = 'ds_' + arg[0]
                row['__domain'] = [
                    'of_datastore_product_search',
                    ('brand_id', 'in', brands.ids)
                ] + row['__domain']

        else:
            # Éxecution de la requête sur la base courante
            res = super(OfDatastoreCentralized, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy)
        return res

    @api.model
    def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
        brands, args = self.of_datastore_update_domain(args)

        # Recherche sur la base du fournisseur
        if brands:
            supplier = brands[0].datastore_supplier_id
            # Ex: si la base n'a qu'une base centralisée, elle peut appeler les articles de la base distante
            #     sans autre filtre de recherche.
            # Dans ce cas, on ne veut pas les autres marques du fournisseur
            args = ['&', ('brand_id', 'in', brands.mapped('datastore_brand_id'))] + args

            supplier_obj = self.env['of.datastore.supplier']

            # Exécution de la requête sur la base du fournisseur
            client = supplier.of_datastore_connect()
            if isinstance(client, basestring):
                # Échec de la connexion à la base fournisseur
                raise UserError(u'Erreur accès '+supplier.db_name)

            ds_product_obj = supplier_obj.of_datastore_get_model(client, self._name)
            res = supplier_obj.of_datastore_search(ds_product_obj, args, offset, limit, order, count)

            if not count:
                supplier_value = supplier.id * DATASTORE_IND
                res = [-(product_id + supplier_value) for product_id in res]
        else:
            # Éxecution de la requête sur la base courante
            res = super(OfDatastoreCentralized, self)._search(
                args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
        return res

    @api.model
    def _of_datastore_name_search(self, res, brands, name, args, operator, limit):
        supplier = brands.mapped('datastore_supplier_id')
        if len(supplier) != 1:
            # Les marques doivent être centralisées, une seule base centrale à la fois
            return res

        if limit != 8 or len(res) == limit:
            # La recherche sur une base fournisseur ne se fait en automatique que pour les recherches
            #   dynamiques des champs many2one (limit=8)
            return res
        if len(res) == 7:
            # Le 8e produit ne sert qu'à savoir si on affiche "Plus de résultats"
            return res + [(False, '')]

        # Recherche des produits dans la base centrale
        client = supplier.of_datastore_connect()
        if isinstance(client, basestring):
            # Échec de la connexion à la base fournisseur
            return res

        brands = brands.filtered('datastore_supplier_id')

        # Recherche des produits non déjà enregistrés
        orig_ids = self.with_context(active_test=False).search(
            [('brand_id', 'in', brands._ids),
             ('of_datastore_res_id', '!=', False)]).mapped('of_datastore_res_id')

        # Mise a jour des paramètres de recherche
        new_args = [('brand_id', 'in', brands.mapped('datastore_brand_id')),
                    ('id', 'not in', orig_ids)] + list(args or [])

        ds_product_obj = supplier.of_datastore_get_model(client, self._name)
        res2 = supplier.of_datastore_name_search(ds_product_obj, name, new_args, operator, limit-len(res))
        supplier_ind = DATASTORE_IND * supplier['id']

        default_code_func = supplier.get_product_code_convert_func(client)
        if len(brands) == 1:
            f = default_code_func[brands]
            res += [[-(pid + supplier_ind), '[' + f(pname[1:]) if pname.startswith('[') else pname]
                    for pid, pname in res2]
        else:
            brand_match = {brand.datastore_brand_id: brand for brand in supplier.brand_ids}
            ds_products_brand = supplier.of_datastore_read(ds_product_obj, zip(*res2)[0], ['brand_id'])
            # {clef=identifiant article sur base centrale : valeur=marque sur base courante}
            ds_products_brand = {data['id']: brand_match[data['brand_id'][0]] for data in ds_products_brand}
            default_code_func = supplier.get_product_code_convert_func(client)
            res += [
                [
                    -(pid + supplier_ind),
                    '[' + default_code_func[ds_products_brand[pid]](pname[1:]) if pname.startswith('[') else pname
                ]
                for pid, pname in res2
            ]
        return res

class Property(models.Model):
    _inherit = 'ir.property'

    @api.model
    def get_multi(self, name, model, ids):
        """ Surcharge pour récupérer une valeur company_dependent d'un article centralisé.
        """
        result = {}
        model_obj = self.env[model]
        if ids and hasattr(model_obj, '_of_read_datastore'):
            ds_ids = [i for i in ids if i < 0]
            if ds_ids:
                ids = [i for i in ids if i >= 0]
                result = {d['id']: d[name] for d in model_obj.browse(ds_ids)._of_read_datastore([name])}
        result.update(super(Property, self).get_multi(name, model, ids))
        return result


from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
from database import SessionLocal, engine
import models, schemas, crud

# データベース作成
models.Base.metadata.create_all(bind=engine)

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

'''
Organization
Employee
Theme
KwCategory
KeyWord
Meeting
ICS
Schedule
Entry

'''

@app.get("/")
async def index():
    return {"message": "Success"}

@app.get("/organizations", response_model=List[schemas.Organization])
async def read_users(
    limit: int = 100,
    db: Session = Depends(get_db),
    q_name: str = None
    ):
    users = crud.get_organizations(db=db, limit=limit, q_name=q_name)
    return users

# @app.get("/rooms", response_model=List[schemas.Room])
# async def read_rooms(skip: int =0, limit: int = 100, db: Session = Depends(get_db)):
#     rooms = crud.get_rooms(db=db, skip=skip, limit=limit)
#     return rooms

# @app.get("/bookings", response_model=List[schemas.Booking])
# async def read_bookings(skip: int =0, limit: int = 100, db: Session = Depends(get_db)):
#     bookings = crud.get_bookings(db=db, skip=skip, limit=limit)
#     return bookings

@app.post("/organizations", response_model=schemas.Organization)
async def create_organization(data: schemas.OrganizationCreate, db: Session = Depends(get_db)):
    organization = crud.create_organization(db=db, data=data)
    return organization

@app.post("/employees", response_model=schemas.Employee)
async def create_employee(data: schemas.EmployeeCreate, db: Session = Depends(get_db)):
    employee = crud.create_employee(db=db, data=data)
    return employee

@app.post("/themes", response_model=schemas.Theme)
async def create_theme(data: schemas.ThemeCreate, db: Session = Depends(get_db)):
    theme = crud.create_theme(db=db, data=data)
    return theme

@app.post("/kwcategories", response_model=schemas.KwCategory)
async def create_kwcategory(data: schemas.KwCategoryCreate, db: Session = Depends(get_db)):
    kwcategory = crud.create_kwcategory(db=db, data=data)
    return kwcategory

@app.post("/keywords", response_model=schemas.KeyWord)
async def create_keyword(data: schemas.KeyWordCreate, db: Session = Depends(get_db)):
    keyword = crud.create_keyword(db=db, data=data)
    return keyword

@app.post("/meetings", response_model=schemas.Meeting)
async def create_meeting(data: schemas.MeetingCreate, db: Session = Depends(get_db)):
    meeting = crud.create_meeting(db=db, data=data)
    return meeting

@app.post("/icss", response_model=schemas.ICS)
async def create_ics(data: schemas.ICSCreate, db: Session = Depends(get_db)):
    ics = crud.create_ics(db=db, data=data)
    return ics

@app.post("/schedules", response_model=schemas.Schedule)
async def create_schedule(data: schemas.ScheduleCreate, db: Session = Depends(get_db)):
    schedule = crud.create_schedule(db=db, data=data)
    return schedule

@app.post("/entries", response_model=schemas.Entry)
async def create_entry(data: schemas.EntryCreate, db: Session = Depends(get_db)):
    entry = crud.create_entry(db=db, data=data)
    return entry

# @app.post("/users", response_model=schemas.User)
# async def create_users(user: schemas.UserCreate, db: Session = Depends(get_db)):
#     user = crud.create_user(db=db, user=user)
#     return user

# @app.post("/rooms", response_model=schemas.Room)
# async def create_rooms(room: schemas.RoomCreate, db: Session = Depends(get_db)):
#     room = crud.create_room(db=db, room=room)
#     return room

# @app.post("/bookings", response_model=schemas.Booking)
# async def create_bookings(booking: schemas.BookingCreate, db: Session = Depends(get_db)):
#     booking = crud.create_booking(db=db, booking=booking)
#     return booking


from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Online whiteboard')

        self.setGeometry(100, 100, 800, 900)

        self.image = QImage(self.size(), QImage.Format_ARGB32)

        self.color = Qt.white

        self.image.fill(Qt.white)

        self.drawing = False
        self.brushSize = 2
        self.brushColor = Qt.black

        self.lastPoint = QPoint()

        main_menu = self.menuBar()

        file_menu = main_menu.addMenu("File")

        b_size = main_menu.addMenu("Brush Size")

        b_color = main_menu.addMenu("Brush Color")

        b_erase = main_menu.addMenu("Eraser")

        b_back = main_menu.addMenu("Fill color")

        save_action = QAction("Save", self)
        save_action.setShortcut("Ctrl + S")
        file_menu.addAction(save_action)
        save_action.triggered.connect(self.save)

        clear_action = QAction("Clear", self)
        clear_action.setShortcut("Ctrl + C")
        file_menu.addAction(clear_action)
        clear_action.triggered.connect(self.clear)

        erase_action = QAction("Eraser", self)
        b_erase.addAction(erase_action)
        erase_action.triggered.connect(self.erase)

        fill_action = QAction("Blue", self)
        self.color = Qt.blue
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_blue)

        fill_action = QAction("Red", self)
        self.color = Qt.red
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_red)

        fill_action = QAction("Green", self)
        self.color = Qt.green
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_green)

        fill_action = QAction("Black", self)
        self.color = Qt.black
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_black)

        fill_action = QAction("Yellow", self)
        self.color = Qt.yellow
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_yellow)

        fill_action = QAction("White", self)
        self.color = Qt.white
        b_back.addAction(fill_action)
        fill_action.triggered.connect(self.fill_white)

        pix_4 = QAction("4px", self)
        b_size.addAction(pix_4)
        pix_4.triggered.connect(self.pixel_4)

        pix_7 = QAction("7px", self)
        b_size.addAction(pix_7)
        pix_7.triggered.connect(self.pixel_7)

        pix_9 = QAction("9px", self)
        b_size.addAction(pix_9)
        pix_9.triggered.connect(self.pixel_9)

        pix_12 = QAction("12px", self)
        b_size.addAction(pix_12)
        pix_12.triggered.connect(self.pixel_12)

        pix_15 = QAction("15px", self)
        b_size.addAction(pix_15)
        pix_15.triggered.connect(self.pixel_15)

        black = QAction("Black", self)
        b_color.addAction(black)
        black.triggered.connect(self.black_color)

        green = QAction("Green", self)
        b_color.addAction(green)
        green.triggered.connect(self.green_color)

        yellow = QAction("Yellow", self)
        b_color.addAction(yellow)
        yellow.triggered.connect(self.yellow_color)

        red = QAction("Red", self)
        b_color.addAction(red)
        red.triggered.connect(self.red_color)

        blue = QAction("Blue", self)
        b_color.addAction(blue)
        blue.triggered.connect(self.blue_color)

        blue = QAction("White", self)
        b_color.addAction(blue)
        blue.triggered.connect(self.white_color)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawing = True
            self.lastPoint = event.pos()

    def mouseMoveEvent(self, event):
        if (event.buttons() & Qt.LeftButton) & self.drawing:
            painter = QPainter(self.image)

            painter.setPen(QPen(self.brushColor, self.brushSize,
                                Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))

            painter.drawLine(self.lastPoint, event.pos())
            self.lastPoint = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawing = False

    def paintEvent(self, event):
        canvas_painter = QPainter(self)
        canvas_painter.drawImage(self.rect(), self.image, self.image.rect())

    def save(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Image", "",
                                                "PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")

        if file_path == "":
            return
        self.image.save(file_path)

    def clear(self):
        self.color = Qt.white
        self.image.fill(Qt.white)
        self.update()

    def pixel_4(self):
        self.brushSize = 4

    def pixel_7(self):
        self.brushSize = 7

    def pixel_9(self):
        self.brushSize = 9

    def pixel_12(self):
        self.brushSize = 12

    def pixel_15(self):
        self.brushSize = 15

    def black_color(self):
        self.brushColor = Qt.black

    def green_color(self):
        self.brushColor = Qt.green

    def yellow_color(self):
        self.brushColor = Qt.yellow

    def red_color(self):
        self.brushColor = Qt.red

    def blue_color(self):
        self.brushColor = Qt.blue

    def white_color(self):
        self.brushColor = Qt.white

    def erase(self):
        self.brushColor = self.color

    def fill_blue(self):
        self.color = Qt.blue
        self.image.fill(Qt.blue)

    def fill_black(self):
        self.color = Qt.black
        self.image.fill(Qt.black)

    def fill_yellow(self):
        self.color = Qt.yellow
        self.image.fill(Qt.yellow)

    def fill_green(self):
        self.color = Qt.green
        self.image.fill(Qt.green)

    def fill_red(self):
        self.color = Qt.red
        self.image.fill(Qt.red)

    def fill_white(self):
        self.color = Qt.white
        self.image.fill(Qt.white)

App = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec())


import cv2
import sys
import numpy as np

class Context:

    def __init__(self):
        self.sliders = {}
        self.toggles = {}
        self._redraw = False
        self.cur_buf_id = 0;
        self.buffers = []
        self.buffers_by_name = {}
        self._once = []
        self._store = {}
        cv2.namedWindow('image')

    def once(self, key):
        if key in self._once:
            return False
        self._once.append(key)
        return True

    def store(self, key, data):
        self._store[key] = data

    def load(self, key):
        return self._store[key]

    def redraw(self, *_):
        self._redraw = True

    def add_buffer(self, name, shape=[], src=None):
        if name not in self.buffers_by_name:
            img = src if src is not None else np.zeros(shape, np.uint8)
            self.buffers.append(img)
            self.cur_buf_id = len(self.buffers)-1
            self.buffers_by_name[name] = (self.buffers[-1], self.cur_buf_id)

    def b(self, name):
        return self.buffers_by_name[name][0]

    def __setitem__(self, key, value):
        if key in self.buffers_by_name:
            id = self.buffers_by_name[key][1]
            self.buffers_by_name[key] = (value, id)
            self.buffers[id] = value
        else:
            self.add_buffer(key, src=value)

    def __getitem__(self, key):
        if key in self.buffers_by_name:
            return self.buffers_by_name[key][0]
        if key in self._store:
            return self._store['key']
        return None

    def get_toggle(self, key, max_, callback, init=0):
        key = ord(key)
        if key not in self.toggles:
            self.toggles[key] = {'state': init, 'has_changed': True, 'callback': callback}

        ko = self.toggles[key]
        ko['callback'] = callback
        has_changed = ko['has_changed']
        ko['has_changed'] = False
        if ko['state'] > max_:
            ko['state'] = 0

        return (ko['state'], has_changed)

    def got_key(self, key):
        (_, _) = self.get_toggle('b', 1, None, init=0)
        (_, _) = self.get_toggle('v', 1, None, init=0)

        if key in self.toggles:
            ko = self.toggles[key]
            ko['state'] += 1
            ko['has_changed'] = True
            if ko['callback'] is not None:
                ko['callback'](None)
            #print "Key:", chr(key), "=", ko['state']
            sys.stdout.flush()

        (_, ffd ) = self.get_toggle('b', 1, None, init=0)
        (_, back) = self.get_toggle('v', 1, None, init=0)
        if back:
            self.cur_buf_id -= 1
            self._redraw = True
        if ffd:
            self.cur_buf_id += 1
            self._redraw = True
        self.cur_buf_id = self.cur_buf_id % len(self.buffers)

    def get_slider(self, name, callback=None, init=0, max_=255):
        created = False
        if name not in self.sliders:
            def none():
                pass
            if callback == None:
                callback = none
            self.sliders[name] = {'old_value': init}
            cv2.createTrackbar(name,'image',init,max_,callback)
            created = True

        val = cv2.getTrackbarPos(name,'image')
        old_val = self.sliders[name]['old_value']
        self.sliders[name]['old_value'] = val
        return (val, val != old_val or created)

    def eventloop(self):

        while(1):
            k = cv2.waitKey(100) & 0xFF

            if k != 255:
                self.got_key(k)
            if k == 27 or k == ord('q'):
                break

            if self._redraw:
                self._redraw = False
                #print "imshow"
                #sys.stdout.flush()
                cv2.imshow('image', self.buffers[self.cur_buf_id])

        cv2.destroyAllWindows()

    def save_all_buffers(self):
        for (i, (k, b)) in enumerate(self.buffers_by_name.items()):
            fn = "debug/%02d_%s.png" % (i, k)
            cv2.imwrite(fn, b[0])


import numpy as np
import cv2

from .kalman import Kalman

#https://github.com/uoip/monoVO-python

def get_R(alpha):
    M = np.array([[np.cos(np.pi*alpha/180), np.sin(np.pi*alpha/180)],
         [-np.sin(np.pi*alpha/180), np.cos(np.pi*alpha/180)]
        ])
    return M

def show_direction(image, t, M):
    line_thickness = 1
    cx, cy = t
    triangle = np.array([[-9, 9], [9, 9], [0, -11]]).T

    triangle_rot = M@triangle
    triangle = triangle_rot.T
    triangle[:,0] += cx
    triangle[:,1] += cy
    points = [[0,1], [0,2], [1,2]]
    for point in points:
        cv2.line(image, (int(triangle[point[0]][0]),int(triangle[point[0]][1])),
                        (int(triangle[point[1]][0]),int(triangle[point[1]][1])),
                 (0, 0, 255),
                 thickness=line_thickness
                )

dt = 0.1
# Q
GPS     = 11.7*8.8*dt**2  # assume 8.8m/s2 as maximum acceleration, forcing the vehicle
Course  = 1.7*dt # assume 0.2rad/s as maximum turn rate for the vehicle
Velocity= 8.8*dt # assume 8.8m/s2 as maximum acceleration, forcing the vehicle
q = np.diag([GPS**2, GPS**2, Course**2, Velocity**2])
# H
h = np.array([[1.0, 0.0, 0.0, 0.0],
				[0.0, 1.0, 0.0, 0.0]])
# R
varGPS = 0.5 # Standard Deviation of GPS Measurement
r = np.diag([varGPS**2.0, varGPS**2.0])
# F
f = np.eye(4)

def mapping(q_in):
	kalman = Kalman(f = f, h = h, q = q, r = r)
	kalman.set_state()
	traj = np.zeros((400,400,3), dtype=np.uint8)
	while True:
		#raw_frame, frame, coords, frame_id
		_, _, coords, frame_id = q_in.get()
		alpha = coords[3]
		Rt = get_R(alpha)
		x, y, z = coords[0], coords[1], coords[2]
		# Kalman
		# kalman.predict()
		# kalman.update(np.array([[float(coords[0])],
		# 				  [float(coords[2])]]))
		# coords = np.array([[float(kalman.state[0])],
		# 					coords[1],
		# 					[float(kalman.state[1])]])
		# x, y, z = coords[0], coords[1], coords[2]
		draw_x, draw_y = int(x), int(y)

		z_color = int(z*255/300)
		#cv2.circle(traj, (draw_x,draw_y), 1, (z_color,255-z_color,255), 2)
		cv2.circle(traj, (draw_x,draw_y), 1, (frame_id/1000,255-frame_id/1000,255), 2)
		cv2.rectangle(traj, (10, 20), (600, 60), (0,0,0), -1)
		text = "Coordinates: x={:.2f}m y={:.2f}m z={:.2f}m".format(x,y,z)
		cv2.putText(traj, text, (20,40), cv2.FONT_HERSHEY_PLAIN, 1, (255,255,255), 1, 8)
		show_direction(traj, (draw_x, draw_y), Rt)
		cv2.imshow('Trajectory', traj)
		cv2.waitKey(1)

if __name__ == '__main__':
	mapping()


from google.cloud import texttospeech
from pydub import AudioSegment
from pydub.playback import play

google_credentials_file = "PATH_TO_YOUR_GOOGLE_CREDENTIALS_JSON"

# Set the environment variable for Google Text-to-Speech API
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = google_credentials_file

# Initialize Google Text-to-Speech API client
tts_client = texttospeech.TextToSpeechClient()
# Function to synthesize text to speech using Google Text-to-Speech API
def synthesize_text(text, language_code="zh-CN"):
    input_text = texttospeech.SynthesisInput(text=text)
    voice = texttospeech.VoiceSelectionParams(
        language_code=language_code, ssml_gender=texttospeech.SsmlVoiceGender.NEUTRAL
    )
    audio_config = texttospeech.AudioConfig(
        audio_encoding=texttospeech.AudioEncoding.LINEAR16
    )

    response = tts_client.synthesize_speech(
        input=input_text, voice=voice, audio_config=audio_config
    )

    return response.audio_content

# Function to play the synthesized audio
def play_audio(audio_data):
    audio = AudioSegment.from_file(io.BytesIO(audio_data), format="wav")
    play(audio)


# -*- coding: utf-8 -*-
import re
import json
import time
import scrapy
import requests
import itertools
from lxml import etree
from hashlib import md5

from overseaSpider.items import ShopItem, SkuAttributesItem, SkuItem
from overseaSpider.util.scriptdetection import detection_main
from overseaSpider.util.utils import isLinux

website = 'samys'

class SamysSpider(scrapy.Spider):
    name = website
    # allowed_domains = ['samys.com']
    start_urls = ['https://www.samys.com/']

    @classmethod
    def update_settings(cls, settings):
        custom_debug_settings = getattr(cls, 'custom_debug_settings' if getattr(cls, 'is_debug',
                                                                                False) else 'custom_settings', None)
        system = isLinux()
        if not system:
            # 如果不是服务器, 则修改相关配置
            custom_debug_settings["HTTPCACHE_ENABLED"] = False
            custom_debug_settings["MONGODB_SERVER"] = "127.0.0.1"
        settings.setdict(custom_debug_settings or {}, priority='spider')

    def __init__(self, **kwargs):
        super(SamysSpider, self).__init__(**kwargs)
        self.counts = 0
        setattr(self, 'author', "无穹")

    is_debug = True
    custom_debug_settings = {
        'MONGODB_COLLECTION': website,
        'CONCURRENT_REQUESTS': 4,
        'DOWNLOAD_DELAY': 1,
        'LOG_LEVEL': 'DEBUG',
        'COOKIES_ENABLED': True,
        # 'HTTPCACHE_EXPIRATION_SECS': 14 * 24 * 60 * 60, # 秒
        'DOWNLOADER_MIDDLEWARES': {
            # 'overseaSpider.middlewares.PhantomjsUpdateCookieMiddleware': 543,
            # 'overseaSpider.middlewares.OverseaspiderProxyMiddleware': 400,
            'overseaSpider.middlewares.OverseaspiderUserAgentMiddleware': 100,
        },
        'ITEM_PIPELINES': {
            'overseaSpider.pipelines.OverseaspiderPipeline': 300,
        },
    }

    def filter_html_label(self, text):  # 洗description标签函数
        label_pattern = [r'(<!--[\s\S]*?-->)', r'<script>.*?</script>', r'<style>.*?</style>', r'<[^>]+>']
        for pattern in label_pattern:
            labels = re.findall(pattern, text, re.S)
            for label in labels:
                text = text.replace(label, '')
        text = text.replace('\n', '').replace('\r', '').replace('\t', '').replace('  ', '').strip()
        return text

    def filter_text(self, input_text):
        filter_list = [u'\x85', u'\xa0', u'\u1680', u'\u180e', u'\u2000-', u'\u200a',
                       u'\u2028', u'\u2029', u'\u202f', u'\u205f', u'\u3000', u'\xA0', u'\u180E',
                       u'\u200A', u'\u202F', u'\u205F']
        for index in filter_list:
            input_text = input_text.replace(index, "").strip()
        return input_text

    def parse(self, response):
        """获取全部分类"""
        category_url = ['https://www.samys.com/c/Photography/1/113.html',
                        'https://www.samys.com/c/Video/1/235.html',
                        'https://www.samys.com/c/Studio--Lighting/1/360.html',
                        'https://www.samys.com/c/Electronics/1/421.html',
                        'https://www.samys.com/c/Smartphone/1/830.html',
                        'https://www.samys.com/c/Pro-Cinema--Audio/2/794.html']
        for i in category_url:
            yield scrapy.Request(
                url=i,
                callback=self.parse_list,
                meta={"flag": 0}
            )

    def parse_list(self, response):
        """商品列表页"""
        detail_url = response.xpath("//div[@itemprop='name']/a/@href").getall()
        if detail_url:
            for i in detail_url:
                yield scrapy.Request(
                    url='https://www.samys.com'+i,
                    callback=self.parse_detail
                )
            if response.meta.get("flag") == 0:
                next_url = response.url + '?start=37'
                yield scrapy.Request(
                    url=next_url,
                    callback=self.parse_list,
                    meta={"flag": 1, "start": 37, "url": response.url}
                )
            else:
                start = response.meta.get("start") + 36
                next_url = response.url + '?start=' + str(start)
                yield scrapy.Request(
                    url=next_url,
                    callback=self.parse_list,
                    meta={"flag": 1, "start": start, "url": response.meta.get("url")}
                )
        else:
            category_url = response.xpath("//div[@class='category-container']/div/a/@href").getall()
            for i in category_url:
                yield scrapy.Request(
                    url='https://www.samys.com' + i,
                    callback=self.parse_list,
                    meta={"flag": 0}
                )

    def parse_detail(self, response):
        """详情页"""
        items = ShopItem()
        items["url"] = response.url
        items["name"] = response.xpath('//meta[@property="og:title"]/@content').get()
        cat_temp = response.xpath("//ul[@class='breadcrumbs floatContainer']//a//text()").getall()
        items["detail_cat"] = '/'.join(cat_temp)
        items["cat"] = cat_temp[-1]
        des_temp=response.xpath('//span[@itemprop="description"]//text()').getall()
        items["description"] = self.filter_text(self.filter_html_label(''.join(des_temp)))
        items["source"] = 'samys.com'
        items["brand"] = response.xpath('//meta[@itemprop="brand"]/@content').get()
        image_temp=response.xpath("//ul[@class='slider-detail']/li/a/img/@src").getall()[:1]+response.xpath("//ul[@class='slider-detail']/li/a/img/@data-post-load-image").getall()
        if not image_temp:
            image_temp = response.xpath("//div[@class='swiper-slide false']/img/@src").getall()
        image=[]
        for i in image_temp:
            image.append('https://www.samys.com'+i)
        items["images"] = image
        items["current_price"] = response.xpath("//meta[@itemprop='price']/@content").get()
        items["original_price"] = items["current_price"]
        items["measurements"] = ["Weight: None", "Height: None", "Length: None", "Depth: None"]

        items["sku_list"] =[]
        status_list = list()
        status_list.append(items["url"])
        status_list.append(items["original_price"])
        status_list.append(items["current_price"])
        status_list = [i for i in status_list if i]
        status = "-".join(status_list)
        items["id"] = md5(status.encode("utf8")).hexdigest()

        items["lastCrawlTime"] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        items["created"] = int(time.time())
        items["updated"] = int(time.time())
        items['is_deleted'] = 0
        # detection_main(items=items, website=website, num=20, skulist=True, skulist_attributes=True)
        # print(items)
        yield items


from os.path import join
from typing import Optional

from dagger.dag_creator.airflow.operator_creator import OperatorCreator
from dagger.dag_creator.airflow.operators.redshift_sql_operator import (
    RedshiftSQLOperator,
)

class RedshiftLoadCreator(OperatorCreator):
    ref_name = "redshift_load"

    def __init__(self, task, dag):
        super().__init__(task, dag)

        self._input_path = join(self._task.inputs[0].rendered_name, "")
        self._input_s3_bucket = self._task.inputs[0].bucket
        self._input_s3_prefix = self._task.inputs[0].path

        self._output_schema = self._task.outputs[0].schema
        self._output_table = self._task.outputs[0].table
        self._output_schema_quoted = f'"{self._output_schema}"'
        self._output_table_quoted = f'"{self._output_table}"'

        self._tmp_table = (
            f"{self._task.tmp_table_prefix}_{self._output_table}"
            if self._task.tmp_table_prefix
            else None
        )
        self._tmp_table_quoted = f'"{self._tmp_table}"' if self._tmp_table else None

        self._copy_ddl_from = self._task.copy_ddl_from
        self._alter_columns = self._task.alter_columns

        self._sort_keys = self._task.sort_keys

    @staticmethod
    def _read_sql(directory, file_path):
        full_path = join(directory, file_path)

        with open(full_path, "r") as f:
            sql_string = f.read()

        return sql_string

    def _get_create_table_cmd(self) -> Optional[str]:
        if self._tmp_table and self._task.create_table_ddl:
            ddl = self._read_sql(
                self._task.pipeline.directory, self._task.create_table_ddl
            )
            return ddl.format(
                schema_name=self._output_schema_quoted,
                table_name=self._tmp_table_quoted,
            )
        if self._tmp_table and self._copy_ddl_from:
            return (
                f"CREATE TABLE {self._output_schema_quoted}.{self._tmp_table_quoted}"
                f"(LIKE {self._copy_ddl_from})"
            )
        elif self._tmp_table:
            return (
                f"CREATE TABLE {self._output_schema_quoted}.{self._tmp_table_quoted}"
                f"(LIKE {self._output_schema_quoted}.{self._output_table_quoted})"
            )
        elif self._task.create_table_ddl:
            ddl = self._read_sql(
                self._task.pipeline.directory, self._task.create_table_ddl
            )
            return ddl.format(
                schema_name=self._output_schema_quoted,
                table_name=self._output_table_quoted,
            )
        elif self._copy_ddl_from:
            return (
                f"CREATE TABLE IF NOT EXISTS {self._output_schema_quoted}.{self._output_table}"
                f"(LIKE {self._copy_ddl_from})"
            )

        return None

    def _get_sort_key_cmd(self) -> Optional[str]:
        sort_key_cmd = None
        if self._sort_keys:
            sort_key_cmd = (
                f"ALTER TABLE {self._output_schema_quoted}.{self._tmp_table_quoted} "
                f"ALTER COMPOUND SORTKEY({self._sort_keys})"
            )
        return sort_key_cmd

    def _get_delete_cmd(self) -> Optional[str]:
        if self._task.incremental:
            return (
                f"DELETE FROM {self._output_schema_quoted}.{self._output_table_quoted}"
                f"WHERE {self._task.delete_condition}"
            )

        if not self._task.incremental and self._tmp_table is None:
            return f"TRUNCATE TABLE {self._output_schema_quoted}.{self._output_table_quoted}"

        return None

    def _get_load_cmd(self) -> Optional[str]:
        table_name = self._tmp_table_quoted or self._output_table_quoted
        columns = "({})".format(self._task.columns) if self._task.columns else ""
        extra_parameters = "\n".join(
            [
                "{} {}".format(key, value)
                for key, value in self._task.extra_parameters.items()
            ]
        )

        return (
            f"copy {self._output_schema_quoted}.{table_name}{columns}\n"
            f"from '{self._input_path}'\n"
            f"iam_role '{self._task.iam_role}'\n"
            f"{extra_parameters}"
        )

    def _get_replace_table_cmd(self) -> Optional[str]:
        if self._tmp_table is None:
            return None

        return (
            f"BEGIN TRANSACTION;\n"
            f"DROP TABLE IF EXISTS {self._output_schema_quoted}.{self._output_table_quoted};\n"
            f"ALTER TABLE {self._output_schema_quoted}.{self._tmp_table_quoted} "
            f"RENAME TO {self._output_table_quoted};\n"
            f"END"
        )

    def _get_alter_columns_cmd(self) -> Optional[str]:
        if self._alter_columns is None:
            return None

        alter_column_commands = []
        alter_columns = self._alter_columns.split(",")
        for alter_column in alter_columns:
            [column_name, column_type] = alter_column.split(":")
            alter_column_commands.append(
                f"ALTER TABLE {self._output_schema_quoted}.{self._tmp_table_quoted} "
                f"ALTER COLUMN {column_name} TYPE {column_type}"
            )

        return ";\n".join(alter_column_commands)

    def _get_drop_tmp_table_cmd(self) -> Optional[str]:
        if self._tmp_table is None:
            return None

        return f"DROP TABLE IF EXISTS {self._output_schema_quoted}.{self._tmp_table_quoted}"

    def _get_cmd(self) -> str:
        raw_load_cmd = [
            self._get_drop_tmp_table_cmd(),
            self._get_create_table_cmd(),
            self._get_alter_columns_cmd(),
            self._get_sort_key_cmd(),
            self._get_delete_cmd(),
            self._get_load_cmd(),
            self._get_replace_table_cmd(),
        ]

        load_cmd = [cmd for cmd in raw_load_cmd if cmd]

        return ";\n".join(load_cmd)

    def _create_operator(self, **kwargs):
        load_cmd = self._get_cmd()

        redshift_op = RedshiftSQLOperator(
            dag=self._dag,
            task_id=self._task.name,
            sql=load_cmd,
            redshift_conn_id=self._task.postgres_conn_id,
            autocommit=True,
            **kwargs,
        )
        return redshift_op


def qaq():
    string = list(input())
    n = len(string)
    count = 0

    for i in range(0, n):
        for j in range(i+1, n):
            for k in range(j+1, n):
                if(string[i] == "Q" and string[j] == "A" and string[k] == "Q"):
                    count += 1

    print(count)

if __name__ == "__main__":
    qaq()


import pandas as pd
import numpy as np
from typing import List
from loguru import logger
from meche_copilot.utils.num_tokens_from_string import num_tokens_from_string

def combine_dataframe_chunks(dfs: List[pd.DataFrame]) -> pd.DataFrame:
    if all(df.shape[1] == dfs[0].shape[1] for df in dfs):
        return pd.concat(dfs, axis=0, ignore_index=True)
    elif all(df.shape[0] == dfs[0].shape[0] for df in dfs):
        return pd.concat(dfs, axis=1)
    else:
        raise ValueError("Chunks do not have consistent shape for concatenation.")

def chunk_dataframe(df: pd.DataFrame, axis=1, num_chunks=None, pct_list=None, max_tokens=None, **kwargs) -> List[pd.DataFrame]:
    """Chunk a dataframe into a list of dataframes using number of chunks xor pct of data in each chunk xor max_tokens in each chunk"""

    if axis not in [0, 1]:
        raise ValueError("axis should be either 0 (rows) or 1 (columns).")

    if sum([num_chunks is not None, pct_list is not None, max_tokens is not None]) != 1:
        raise ValueError(f"Exactly one of num_chunks, pct_list, or max_tokes must be specified. Got {num_chunks}, {pct_list}, {max_tokens}")

    # if using percents, they should not add up to greater than 100
    if pct_list:
        if sum(pct_list) > 100:
            raise ValueError("Sum of pct_list should be 100% or less.")
        num_chunks = len(pct_list) + 1
        pct_list.append(100 - sum(pct_list))

    # if using num_chunks (or pct_list), shouldnt be greater than items in axis
    if num_chunks:
      if axis == 0 and num_chunks > df.shape[0]:
          raise ValueError("Number of chunks should not be greater than number of rows.")
      if axis == 1 and num_chunks > df.shape[1]:
          raise ValueError("Number of chunks should not be greater than number of columns.")

    chunks = []
    if num_chunks and not pct_list: # split into num_chunks along axis
        logger.debug(f"Splitting df into {num_chunks} chunks along axis {axis}.")
        split_func = np.array_split
        chunks = split_func(df, num_chunks, axis=axis)
    elif pct_list: # split into fractions along axis
        logger.debug(f"Splitting df into {len(pct_list)} chunks along axis {axis} with pct_list {pct_list}.")
        fractions = [pct / 100 for pct in pct_list]
        if axis == 0: # split rows into fractions
            start_idx = 0
            for frac in fractions:
                end_idx = start_idx + int(frac * df.shape[0])
                chunks.append(df.iloc[start_idx:end_idx])
                start_idx = end_idx
        else: # split columns into fractions
            start_idx = 0
            for frac in fractions:
                end_idx = start_idx + int(frac * df.shape[1])
                chunks.append(df.iloc[:, start_idx:end_idx])
                start_idx = end_idx
    else: # split using max_tokens
        logger.debug(f"Splitting df along axis {axis} with max_tokens {max_tokens} per chunk.")
        encoding_name = kwargs.get("encoding_name", "gpt-4")
        start_idx = 0
        prev_tokens = None # To keep track of the previous token size
        while start_idx < df.shape[0] if axis == 0 else start_idx < df.shape[1]:
            for i in range(start_idx, df.shape[0] if axis == 0 else df.shape[1]): # iterate over rows/cols until max_tokens is reached, then append that chunk
                csv_string = df.iloc[start_idx:i+1].to_csv() if axis == 0 else df.iloc[:, start_idx:i+1].to_csv()
                tokens = num_tokens_from_string(csv_string, encoding_name)
                if tokens > max_tokens:
                    # Print the previous token size, not the updated token size
                    logger.debug(f"Adding chunk with shape {df.iloc[start_idx:i].shape if axis == 0 else df.iloc[:, start_idx:i].shape} and prev num tokens {prev_tokens}.")
                    chunks.append(df.iloc[start_idx:i] if axis == 0 else df.iloc[:, start_idx:i])
                    start_idx = i  + 1 # update start_idx
                    break
                prev_tokens = tokens # Save the previous token size
            else: # if loop completes without breaking (i.e., all remaining data fits within max_tokens)
                chunks.append(df.iloc[start_idx:] if axis == 0 else df.iloc[:, start_idx:])
                break
        logger.debug(f"Split df into {len(chunks)} chunks")

    return chunks


import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
import time

MIN_MATCH_COUNT = 10

img_sample = cv.cvtColor(cv.imread("./img/dataset/9.png",cv.IMREAD_COLOR),cv.COLOR_BGR2GRAY)

img_q = cv.cvtColor(cv.imread("./img/query/3.png",cv.IMREAD_COLOR),cv.COLOR_BGR2GRAY)

sift = cv.SIFT_create()

keypoints_1, descriptors_1 = sift.detectAndCompute(img_q, None)
keypoints_2, descriptors_2 = sift.detectAndCompute(img_sample, None)

outImage_1 = cv.drawKeypoints(img_q, keypoints_1,None)
outImage_2 = cv.drawKeypoints(img_sample, keypoints_2,None)

print(len(keypoints_1))
print(len(keypoints_2))

#cv.imwrite('image.jpg', outImage_1)
#cv.waitKey(0)

# BFMatcher
def BFMatcher(descript_1,descript_2):
    bf = cv.BFMatcher()
    matches = bf.knnMatch(descript_1,descript_2,k=2)
    return matches

# FLANNMatcher
def FLANNMatcher(descript_1,descript_2):
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
    search_params = dict(checks = 50)
    flann = cv.FlannBasedMatcher(index_params, search_params)
    matches = flann.knnMatch(descript_1,descript_2,k=2)
    return matches

start = time.time()
matches = BFMatcher(descriptors_1,descriptors_2)
#matches = FLANNMatcher(descriptors_1,descriptors_2)
end = time.time()
print("time cost: ",end-start)

# ratio test
good = []
for m,n in matches:
    if m.distance < 0.75*n.distance:
        good.append(m)
# cv.drawMatchesKnn expects list of lists as matches.
print("match pairs: ", len(good))
# img4 = cv.drawMatchesKnn(img_q,keypoints_1,img_sample,keypoints_2,good,None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# plt.imshow(img4),plt.show()

if len(good)>MIN_MATCH_COUNT:
    src_pts = np.float32([ keypoints_1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ keypoints_2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)
    matchesMask = mask.ravel().tolist()
    h,w = img_q.shape
    pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
    dst = cv.perspectiveTransform(pts,M)
    #box_color = (0,0,255)
    sample_draw = cv.merge((img_sample.copy(),img_sample.copy(),img_sample.copy()))
    img_sample_detected = cv.polylines(sample_draw,[np.int32(dst)],True,(255,0,0),5, cv.LINE_AA)
else:
    print( "Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT) )
    matchesMask = None
query_draw = cv.merge((img_q.copy(),img_q.copy(),img_q.copy()))
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
                   singlePointColor = None,
                   matchesMask = matchesMask, # draw only inliers
                   flags = 2)
img3 = cv.drawMatches(query_draw,keypoints_1,img_sample_detected,keypoints_2,good,None,**draw_params)
plt.imshow(img3, 'gray'),plt.show()


from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
import time
from constants import URL

def get_product_properties(browser):

    properties = {}
    search_result = browser.find_element(By.CLASS_NAME, "sooqrSearchResults")

    title = search_result.find_element(By.CLASS_NAME, "productlist-title")
    title.find_element(By.TAG_NAME, "a").click()

    time.sleep(0.1)

    content_place_holder = browser.find_element(By.ID, "pdetailTableSpecs")
    table_body = content_place_holder.find_element(By.TAG_NAME, "tbody")

    searched_keys = ["Zusammenstellung", "Nadelstärke"]
    for table_row in table_body.find_elements(By.TAG_NAME, "tr"):
        key, value = table_row.find_elements(By.TAG_NAME, "td")
        if key.text in searched_keys:
            properties[key.text] = value.text

    try:
        properties["Lieferbar"] = True if browser.find_element(By.CLASS_NAME, "stock-green").text == "Lieferbar" else False
    except:
        properties["Lieferbar"] = False

    properties["Preis"] = browser.find_element(By.CLASS_NAME, "product-price-amount").text

    return properties

def select_correct_brand(browser, marke):
    """checks if any search results were found and if they are from the correct marke

    Args:
        browser ([webdriver]): Firefox browser
        marke ([str]):

    Returns:
        [str]: returns error message if the correct prodcut can not be found
    """

    # try to locate marken_filter
    try:
        marken_search_filter = browser.find_element(By.ID, "sooqr44898be26662b0dfSearchFilter191640")
        marken_search_filter_field = marken_search_filter.find_elements(By.TAG_NAME, "input")
    except NoSuchElementException:
        return "No search result found"

    # try to click marke
    for marken_input in marken_search_filter_field:
        test_marke = marken_input.get_attribute("value")
        if test_marke == marke:
            marken_input.click()
            break
    else:
        return "No such brand for search term"
    return ""

def search_product(browser, marke, bezeichnung):
    """webscrapes all needed information for product

    Args:
        browser ([webdriver]): Firefox browser
        marke ([str]):
        bezeichnung ([str]):

    Returns:
        [dict]: dictionary of properties of searched products
    """

    # nativating to url (home) of site everytime before searching for product becuase occationally elements could not
    # be found (although visible) after continuing from previous product site
    browser.get(URL)

    search_field = browser.find_element(By.ID, "searchSooqrTop")
    search_field.clear()
    search_field.send_keys(bezeichnung)

    product_properties = {"marke": marke, "bezeichnung": bezeichnung}

    # checking for errors like not finding any element when searching for bezeichnung or none of the correct marke
    occured_errors = select_correct_brand(browser, marke)
    if occured_errors != "":
        product_properties["error"] = occured_errors
        return product_properties

    product_properties.update(get_product_properties(browser))

    return product_properties


import time
import datetime
from timeit import default_timer as timer
import settings
from pymongo import MongoClient
from faker import Faker
from bson.decimal128 import Decimal128
import random

fake = Faker()

####
# Start script
####
startTs = time.gmtime()
start = timer()
print("================================")
print("   Generating Transactions Data ")
print("================================")
print("\nStarting " + time.strftime("%Y-%m-%d %H:%M:%S", startTs) + "\n")

####
# Main start function
####
def main():
    mongo_client = MongoClient(MDB_CONNECTION)
    db = mongo_client[MDB_DATABASE]
    my_collection = db[MDB_COLLECTION]

    print('Begin generating txns documents.')
    print('Number of documents to generate: ' + str(NUM_DOCS))

    for index in range(int(NUM_DOCS)):
        fake_timestamp = fake.date_between(start_date='-1y', end_date='today')
        txn_types = ['deposit', 'withdrawal']
        txns = random.choice(txn_types)

        my_txn_document = {
            "customerId": fake.ssn(),
            "name": fake.name(),
            "address": fake.street_address(),
            "city": fake.city(),
            "state": fake.state(),
            "postalCode": fake.postcode(),
            "email": fake.email(),
            "lastLocation": {
                "type": "Point",
                "coordinates": [
                    Decimal128(fake.longitude()),
                    Decimal128(fake.latitude())
                ]
            },
            "txnType": txns,
            "txnAmount": random.randint(0, 10000),
            "txnDate": datetime.datetime(fake_timestamp.year, fake_timestamp.month, fake_timestamp.day)
        }

        # Print example doc on first doc creation
        if index == 1:
            print('Example Document')
            print(my_txn_document)

        # Indicate how many docs inserted
        if index % 100 == 0:
            print('Docs inserted: ' + str(index))

        my_collection.insert_one(my_txn_document)

####
# Constants loaded from .env file
####
MDB_CONNECTION = settings.MDB_CONNECTION
MDB_DATABASE = settings.MDB_DATABASE
MDB_COLLECTION = settings.MDB_COLLECTION
NUM_DOCS = settings.NUM_DOCS

####
# Main
####
if __name__ == '__main__':
    main()

####
# Indicate end of script
####
end = timer()
endTs = time.gmtime()
total_time = end - start

if total_time < 1:
    docs_inserted_time = int(NUM_DOCS) / 1
else:
    docs_inserted_time = int(NUM_DOCS) / total_time

print("\nEnding " + time.strftime("%Y-%m-%d %H:%M:%S", endTs))
print('===============================')
print('Total Time Elapsed (in seconds): ' + str(total_time))
print('===============================')
print('Number of Docs inserted per second: ' + str(docs_inserted_time))
print('===============================')