from datetime import date, datetime

from dateutil.relativedelta import relativedelta

from odoo import fields, models, api
from odoo.addons import decimal_precision as dp
from ..utils import serial_utils

class StockProductionLot(models.Model):
    _inherit = 'stock.production.lot'
    _sql_constraints = [
        ('name_uniq', 'UNIQUE(name)', 'el lote que intenta crear, ya existe en el sistema')
    ]

    unpelled_state = fields.Selection([
        ('draft', 'En Espera para ingresar'),
        ('waiting', 'En Espera para Iniciar'),
        ('drying', 'Secando'),
        ('stopped', 'Detenido'),
        ('done', 'Terminado')
    ],
        'Estado',

    )

    can_add_serial = fields.Boolean(
        'Puede Agregar Series',
        compute='_compute_can_add_serial'
    )

    producer_ids = fields.Many2many(
        'res.partner',
        compute='_compute_producer_ids'
    )

    product_variety = fields.Char(
        'Variedad',
        compute='_compute_product_variety'
    )

    producer_id = fields.Many2one(
        'res.partner',
        string='Productor'
    )

    reception_guide_number = fields.Integer(
        'Guía',
        related='stock_picking_id.guide_number',
        store=True
    )

    reception_state = fields.Selection(
        string='Estado de la reecepción',
        related='stock_picking_id.state'
    )

    product_canning = fields.Char(
        'Envase',
        compute='_compute_reception_data'
    )

    is_prd_lot = fields.Boolean('Es Lote de salida de Proceso')

    is_standard_weight = fields.Boolean(
        'Series Peso Estandar',
        related='product_id.is_standard_weight'
    )

    standard_weight = fields.Float(
        'Peso Estandar',
        related='product_id.weight',
        digits=dp.get_precision('Product Unit of Measure')
    )

    qty_standard_serial = fields.Integer('Cantidad de Series por Pallet')

    stock_production_lot_serial_ids = fields.One2many(
        'stock.production.lot.serial',
        'stock_production_lot_id',
        string="Detalle"
    )

    serial_without_pallet_ids = fields.One2many(
        'stock.production.lot.serial',
        'stock_production_lot_id',
        domain=[('pallet_id', '=', None)],
        string='Series sin Pallet'
    )

    stock_production_lot_available_serial_ids = fields.One2many(
        'stock.production.lot.serial',
        'stock_production_lot_id',
        domain=[('reserved_to_stock_picking_id', '=', None), ('consumed', '=', False)],
        string='Series Disponibles'
    )

    total_serial = fields.Float(
        'Total',
        compute='_compute_total_serial',
        digits=dp.get_precision('Product Unit of Measure')
    )

    count_serial = fields.Integer(
        'Total Series',
        compute='_compute_count_serial'
    )

    available_total_serial = fields.Float(
        'Total Disponible',
        compute='_compute_available_total_serial',
        search='_search_available_total_serial',
        digits=dp.get_precision('Product Unit of Measure')
    )

    qty_to_reserve = fields.Float(
        'Cantidad a Reservar',
        digits=dp.get_precision('Product Unit of Measure')
    )

    context_picking_id = fields.Integer(
        'picking del contexto',
        compute='_compute_context_picking_id'
    )

    pallet_ids = fields.One2many(
        'manufacturing.pallet',
        'lot_id'
    )

    picking_type_id = fields.Many2one(
        'stock.picking.type',
        string='Bodega',
        related='stock_picking_id.picking_type_id',
        store=True

    )

    reception_net_weight = fields.Float(
        'kg. Neto',
        related='stock_picking_id.net_weight',
        digits=dp.get_precision('Product Unit of Measure'),
        store=True
    )

    reception_date = fields.Datetime(
        'Fecha Recepción',
        related='stock_picking_id.truck_in_date',
        store=True
    )

    reception_elapsed_time = fields.Char(
        'Hr Camión en Planta',
        related='stock_picking_id.elapsed_time'
    )

    oven_init_active_time = fields.Integer(
        'Inicio Tiempo Activo',
        related='oven_use_ids.init_active_time'
    )

    finish_active_time = fields.Integer(
        'Fin Tiempo Activo',
        related='oven_use_ids.finish_active_time'
    )

    oven_use_ids = fields.One2many(
        'oven.use',
        'used_lot_id',
        'Uso de Hornos',
        sort='active_seconds desc'
    )

    drier_counter = fields.Integer(
        'Hr en Secador',
        related='oven_use_ids.active_seconds'
    )

    stock_picking_id = fields.Many2one(
        'stock.picking',
        'Recepción'
    )

    all_pallet_ids = fields.One2many(
        'manufacturing.pallet',
        'lot_id',
        string='pallets'
    )

    label_durability_id = fields.Many2one(
        'label.durability',
        'Durabilidad Etiqueta'
    )

    is_dried_lot = fields.Boolean(
        'Es lote de Secado'
    )

    product_variety = fields.Char(
        'Variedad del Producto',
        related='product_id.variety',
        store=True
    )

    product_caliber = fields.Char(
        'Calibre del Producto',
        related='product_id.caliber',
        store=True
    )

    harvest = fields.Integer(string='Cosecha', compute='_compute_lot_harvest', store=True)

    dried_report_product_name = fields.Char(compute='_compute_lot_oven_use')

    location_id = fields.Many2one('stock.location', compute='_compute_lot_location')

    serial_not_consumed = fields.Integer('Envases disponible', compute='_compute_serial_not_consumed')

    available_kg = fields.Float('Kilos Disponibles')

    available_weight = fields.Float('Datos Disponibles')

    show_guide_number = fields.Char('Guia', compute='_compute_guide_number')

    reception_weight = fields.Float('Kilos Recepcionados', compute='_compute_reception_weight')

    sale_order_id = fields.Many2one('sale.order', 'Pedido')

    workcenter_id = fields.Many2one('mrp.workcenter', 'Enviado a proceso de')

    delivered_date = fields.Date('Fecha de Envio')

    physical_location = fields.Text('Ubicacion Fisica')

    observations = fields.Text('Observaciones')

    start_date = fields.Datetime('Fecha Inicio')

    measure = fields.Char('Medida', compute='compute_measure')

    produced_qty = fields.Integer('Cantidad Producida', compute='compute_produced_qty')

    produced_weight = fields.Float('Kilos Producidos', compute='compute_produced_weight')

    production_state = fields.Char('Estado de Producccion', compute='compute_production_state')

    dispatch_state = fields.Char('Estado de Despacho', compute='compute_dispatch_state')

    client_id = fields.Many2one('res.partner', related='sale_order_id.partner_id')

    destiny_country_id = fields.Many2one('res.country', compute='compute_destiny_country')

    dispatch_date = fields.Date('Fecha de Despacho')

    show_date = fields.Datetime('Fecha de Creacion', compute='compute_show_date')

    packaging_date = fields.Date(string='Fecha de Envasado')

    change_packaging = fields.Boolean(string='¿Desea cambiar fecha de envasado?')

    best_before_date = fields.Date(string='Consumir preferentemente antes de')

    change_best = fields.Boolean(string='¿Desea cambiar fecha de consumir preferentemente antes de?')

    is_unpelled_locked = fields.Boolean(string='Bloqueado')

    unpelled_dried_id = fields.Many2one('unpelled.dried', 'Proceso de Secado')

    qty_serial_without_lot = fields.Integer(string='Cantidad de Series Temporales')

    temporary_serial_ids = fields.One2many('custom.temporary.serial', 'lot_id',
                                           string='Series temporales sin Paletizar')

    do_print_selection_serial = fields.Boolean('Imprimir Series Seleccionadas')

    last_serial_number = fields.Char('Ultima serie temporal creado')

    is_finished = fields.Boolean('Finalizado')

    ventilation_date = fields.Date(string='Fecha de Ventilacion')

    store_place = fields.Char(string='Lugar de Almacenamiento')

    is_drying = fields.Boolean('Esta secando?', compute='compute_is_drying')

    correlative_serial = fields.Char('Correlativo de Serie')

    serial_to_select_ids = fields.Many2many('stock.production.lot.serial', string='Series',
                                            compute='compute_serial_to_select_ids')

    counter_pallet = fields.Integer('Contador de pallet', default=0)

    origin_process = fields.Char('Proceso de origen')

    def set_counter_in_pallet(self, harvest):
        lot_ids = self.env['stock.production.lot'].search([('product_id.is_standard_weight', '=', True)]).filtered(
            lambda x: x.harvest == harvest)
        for lot in lot_ids:
            if len(lot.all_pallet_ids) > 0:
                for pallet in lot.all_pallet_ids:
                    pallet.write({
                        'counter_pallet_lot': lot.counter_pallet + 1
                    })
                    lot.write({
                        'counter_pallet': lot.counter_pallet + 1
                    })

    def compute_serial_to_select_ids(self):
        for item in self:
            item.serial_to_select_ids = item.stock_production_lot_serial_ids.filtered(
                lambda x: not x.reserved_to_stock_picking_id)

    @api.multi
    def compute_is_drying(self):
        for item in self:
            dried_unpelled_id = self.env['unpelled.dried'].sudo().search([('out_lot_id', '=', item.id)])
            if dried_unpelled_id:
                if dried_unpelled_id.state == 'progress':
                    item.is_drying = True
                else:
                    item.is_drying = False
            else:
                item.is_drying = False

    @api.multi
    def compute_production(self):
        for item in self:
            if item.is_prd_lot:
                final_lot_id = self.env['mrp.workorder'].search([('final_lot_id.id', '=', item.id)])
                if final_lot_id:
                    item.production_id = final_lot_id.production_id
                    item.production_state = final_lot_id.production_id.state
                else:
                    if len('temporary_serial_ids') > 0:
                        item.production_id = item.temporary_serial_ids.mapped('production_id')
                        item.production_state = item.temporary_serial_ids.mapped('production_id').state
                    elif len('stock_production_lot_serial_ids') > 0:
                        item.production_id = item.stock_production_lot_serial_ids.mapped('production_id')
                        item.production_id = item.stock_production_lot_serial_ids.mapped('production_id').state
            else:
                item.production_id = None
                item.production_state = ''

    @api.multi
    def print_all_temporary_serial(self):
        for item in self:
            serials = self.temporary_serial_ids.filtered(lambda x: not x.printed)
            serials.write({
                'printed': True
            })
            if not serials:
                raise models.ValidationError('Ya se imprimieron todas las series')
            if len(serials) > 400:
                raise models.UserError(
                    'La cantidad de series que esta intentado imprimir superando el maximo permitido \n Maximo Permitido : 400')
            return self.env.ref(
                'dimabe_manufacturing.action_print_temporary_serial'
            ).report_action(serials)

    @api.multi
    def print_selection_serial(self):
        for item in self:
            serials = item.temporary_serial_ids.filtered(lambda x: x.to_print)
            serials.write({
                'to_print': False,
                'printed': True
            })
            item.write({
                'do_print_selection_serial': False
            })
            return self.env.ref(
                'dimabe_manufacturing.action_print_temporary_serial'
            ).report_action(serials)

    @api.model
    def selected_serials_to_print(self, selected_ids):
        serials = self.env['custom.temporary.serial'].search([('id', '=', selected_ids)])
        serials.write({
            'to_print': True
        })
        serials.mapped('lot_id').write({
            'do_print_selection_serial': True
        })
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
        }

    @api.onchange('qty_serial_without_lot')
    def onchange_qty_serial_without_lot(self):
        for item in self:
            if item.qty_serial_without_lot > 400:
                raise models.UserError('No se pueden generar mas de 400 series al vez')

    @api.multi
    def generate_temporary_serial(self):
        if self.qty_serial_without_lot > 400:
            raise models.UserError('No se pueden generar mas de 400 series al vez')
        counter = self.get_last_serial()
        serials = []
        for serial in range(self.qty_serial_without_lot):
            zeros = serial_utils.get_zeros(counter)
            production_id = self.env['mrp.workorder'].search([('final_lot_id.id', '=', self.id)]).production_id
            if not production_id:
                production_id = self.env['stock.move.line'].sudo().search(
                    [('lot_id.id', '=', self.id), ('move_id.production_id', '!=', False)]).move_id.production_id
            canning_id = self.get_possible_canning_id(production_id.id)[0]
            gross_weight = self.standard_weight + canning_id.weight
            serials.append({
                'product_id': self.product_id.id,
                'producer_id': self.producer_id.id,
                'lot_id': self.id,
                'name': f'{self.name}{zeros}{counter}',
                'best_before_date': fields.Date.today() + relativedelta(
                    months=self.label_durability_id.month_qty) if not self.best_before_date else self.best_before_date,
                'harvest': self.harvest,
                'label_durability_id': self.label_durability_id.id,
                'net_weight': self.standard_weight,
                'production_id': production_id.id,
                'packaging_date': fields.Date.today() if not self.packaging_date else self.packaging_date,
                'gross_weight': gross_weight
            })
            counter += 1
        self.env['custom.temporary.serial'].create(serials)
        self.write({
            'last_serial_number': self.temporary_serial_ids[-1].name,
            'qty_serial_without_lot': 0
        })

    @api.multi
    def get_last_serial(self):
        last_serial = 0
        if len(self.temporary_serial_ids) > 0:
            qty_number = -4 if len(self.temporary_serial_ids) > 999 else -3
            last_serial = self.last_serial_number[qty_number:]
        if len(self.temporary_serial_ids) == 0:
            if len(self.stock_production_lot_serial_ids) > 0:
                qty_number = -4 if len(self.stock_production_lot_serial_ids) > 999 else -3
                last_serial = self.last_serial_number[qty_number:]

        return int(last_serial) + 1

    @api.multi
    def generate_new_pallet(self):
        for item in self:
            if len(item.temporary_serial_ids) < item.qty_standard_serial:
                raise models.ValidationError(
                    f"Series insuficientes para completar el pallet \n "
                    f"Cantidad de Series Disponible {len(item.temporary_serial_ids)}")
            item.write({
                'counter_pallet': item.counter_pallet + 1
            })
            pallet_id = self.env['manufacturing.pallet'].create({
                'producer_id': item.producer_id.id,
                'lot_id': item.id,
                'sale_order_id': item.sale_order_id.id if item.sale_order_id else None,
                'counter_pallet_lot': item.counter_pallet
            })
            temporary_ids = self.env['custom.temporary.serial'].search([('lot_id', '=', item.id)],
                                                                       limit=item.qty_standard_serial)
            temporary_ids.create_serial(pallet_id.id)
            pallet_id.write({
                'state': 'close'
            })
            workorder = self.env['mrp.workorder'].sudo().search([('final_lot_id', '=', item.id)], limit=1)
            workorder.write({
                'pt_out_weight': sum(serial.display_weight for serial in item.stock_production_lot_serial_ids)
            })
            self.update_kg(self.id)

    def do_change_date_best(self):
        for item in self:
            wiz = self.env['change.date.lot'].create({
                'lot_id': item.id,
                'best_before_date_new': item.best_before_date,
                'best_before_date_old': item.stock_production_lot_serial_ids.mapped('best_before_date')[0]
            })
            view = self.env.ref('dimabe_manufacturing.change_date_best_lot_form_view')
            return {
                'name': '¿Esta seguro de cambiar las fecha de envasado?',
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'change.date.lot',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context
            }

    def get_possible_canning_id(self, production_id):
        production_id = self.env['mrp.production'].search([('id', '=', production_id)])
        return production_id.bom_id.bom_line_ids.filtered(
            lambda a: 'envases' in str.lower(a.product_id.categ_id.name) or
                      'embalaje' in str.lower(a.product_id.categ_id.name)
                      or (
                              a.product_id.categ_id.parent_id and (
                              'envases' in str.lower(a.product_id.categ_id.parent_id.name) or
                              'embalaje' in str.lower(a.product_id.categ_id.parent_id.name))
                      )
        ).mapped('product_id')

    def do_change_date_packing(self):
        for item in self:
            wiz = self.env['change.date.lot'].create({
                'lot_id': item.id,
                'packaging_date_new': item.packaging_date,
                'packaging_date_old': item.stock_production_lot_serial_ids.mapped('packaging_date')[0]
            })
            view = self.env.ref('dimabe_manufacturing.change_date_lot_form_view')
            return {
                'name': '¿Esta seguro de cambiar las fecha de envasado?',
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'change.date.lot',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context
            }

    @api.multi
    def unlink_serial_without_pallet(self):
        for item in self:
            if item.serial_without_pallet_ids.filtered(lambda a: a.consumed):
                raise models.ValidationError('Existe una o mas series consumidas')
            else:
                item.serial_without_pallet_ids.sudo().unlink()

    @api.multi
    def unlink_selection(self):
        for item in self:
            if not item.serial_without_pallet_ids.filtered(lambda a: a.to_unlink):
                raise models.UserError("No ha seleccionado nada")
            else:
                if item.serial_without_pallet_ids.filtered(lambda a: a.consumed):
                    raise models.ValidationError('Existe una o mas series consumidas')
                else:
                    item.serial_without_pallet_ids.filtered(lambda a: a.to_unlink).sudo().unlink()

    @api.multi
    def _compute_reception_elapsed_time(self):
        for item in self:
            item.reception_elapsed_time = item.stock_picking_id.elapsed_time

    @api.multi
    def compute_show_date(self):
        for item in self:
            if item.is_dried_lot:
                dried = self.env['dried.unpelled.history'].search([('out_lot_id', '=', item.id)])
                item.show_date = dried.finish_date
            else:
                item.show_date = item.create_date

    @api.multi
    def show_pallets(self):
        return {
            'name': "Series de Salida",
            'view_type': 'form',
            'view_mode': 'tree,graph,form,pivot',
            'res_model': 'manufacturing.pallet',
            'view_id': False,
            'type': 'ir.actions.act_window',
            'views': [
                [self.env.ref('dimabe_manufacturing.manufacturing_pallet_tree_view').id, 'tree']],
            'context': self.env.context,
            'domain': [('id', 'in', self.pallet_ids.mapped("id"))]
        }

    @api.multi
    def show_lot(self):
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'stock.production.lot',
            'res_id': self.id,
            'view_type': 'form',
            'view_mode': 'form',
            'views': [(self.env.ref('stock.view_production_lot_form_simple').id, 'form')],
            'target': 'current',
            'context': self.env.context
        }

    @api.multi
    def compute_destiny_country(self):
        for item in self:
            if item.stock_production_lot_serial_ids.mapped('production_id'):
                item.destiny_country_id = item.stock_production_lot_serial_ids.mapped('production_id')[
                    0].stock_picking_id.arrival_port.country_id

    @api.multi
    def compute_measure(self):
        for item in self:
            item.measure = f'{item.product_id.weight} Kilos'

    @api.multi
    def compute_produced_qty(self):
        for item in self:
            item.produced_qty = len(item.stock_production_lot_serial_ids)

    @api.multi
    def compute_produced_weight(self):
        for item in self:
            item.produced_weight = sum(item.stock_production_lot_serial_ids.mapped('display_weight'))

    @api.multi
    def compute_production_state(self):
        for item in self:
            if item.stock_production_lot_serial_ids.mapped('production_id'):
                state = item.stock_production_lot_serial_ids.mapped('production_id')[0].state
                if state == 'done':
                    item.production_state = "Finalizado"
                else:
                    item.production_state = "En proceso"
            else:
                item.production_state = "Borrador"

    @api.multi
    def compute_dispatch_state(self):
        for item in self:
            if item.stock_production_lot_serial_ids.mapped('reserved_to_stock_picking_id'):
                state = item.stock_production_lot_serial_ids.mapped('reserved_to_stock_picking_id').mapped('state')[0]
                if state == 'done':
                    item.dispatch_state = "Finalizado"
                else:
                    item.dispatch_state = "En proceso"
            else:
                item.dispatch_state = "Borrador"

    @api.multi
    def _compute_reception_weight(self):
        for item in self:
            if not item.stock_picking_id:
                weight = self.env['stock.picking'].search([('name', '=', item.name)])
                item.reception_weight = weight.production_net_weight
            if item.stock_picking_id:
                item.reception_weight = item.stock_picking_id.production_net_weight
            if item.is_dried_lot:
                dried_ids = self.env['dried.unpelled.history'].search([('out_lot_id', '=', item.id)])
                item.reception_weight = sum(dried_ids.mapped('total_out_weight'))

    @api.multi
    def check_duplicate(self):
        for item in self:
            if len(item.stock_production_lot_serial_ids) > 999:
                not_duplicates = []
                duplicates = []
                for serial in item.stock_production_lot_serial_ids.mapped('serial_number'):
                    if serial not in not_duplicates:
                        not_duplicates.append(serial)
                    else:
                        duplicates.append(serial)
                serie = len(not_duplicates)

                if len(duplicates) > 1:
                    item.stock_production_lot_serial_ids[999].update({
                        'serial_number': item.name + '1000'
                    })
                    for duplicate in duplicates:
                        serial = self.env['stock.production.lot.serial'].search([('serial_number', '=', duplicate)])
                        serie += 1
                        if len(serial) > 1:
                            serial[1].write({
                                'serial_number': item.name + '{}'.format(serie)
                            })
                        else:
                            serial.write({
                                'serial_number': item.name + '{}'.format(serie)
                            })

    @api.multi
    def _compute_guide_number(self):
        for item in self:
            if item.stock_picking_id:
                item.show_guide_number = str(item.stock_picking_id.guide_number)
            else:
                reception = self.env['stock.picking'].search([('name', '=', item.name)])
                item.show_guide_number = str(reception.guide_number)
            if item.is_dried_lot:
                dried_ids = self.env['dried.unpelled.history'].search([('out_lot_id', '=', item.id)])
                guide_numbers = []
                for dried_id in dried_ids:
                    if dried_id.lot_guide_numbers:
                        guide_numbers.append(str(dried_id.lot_guide_numbers))
                item.show_guide_number = '|'.join(guide_numbers)

    @api.depends('stock_production_lot_serial_ids', 'available_kg')
    @api.multi
    def _compute_lot_harvest(self):
        for item in self:
            if item.stock_production_lot_serial_ids:
                item.harvest = max(item.stock_production_lot_serial_ids.mapped('serial_harvest'))

    @api.multi
    def _compute_lot_location(self):
        for item in self:
            stock_quant = item.get_stock_quant()
            if len(stock_quant) > 0:
                item.location_id = stock_quant.location_id
            if item.stock_picking_id:
                item.location_id = item.stock_picking_id.location_dest_id
                return
            if item.is_dried_lot:
                location_id_dried = self.env['dried.unpelled.history'].search(
                    [('out_lot_id', '=', item.id)]).dest_location_id
                if not location_id_dried:
                    location_id_dried = self.env['unpelled.dried'].sudo().search(
                        [('out_lot_id', '=', item.id)]
                    ).dest_location_id
                item.location_id = location_id_dried
                return
            if item.is_prd_lot:
                if item.stock_production_lot_serial_ids.mapped('production_id').state == 'done':
                    item.location_id = item.stock_production_lot_serial_ids.mapped('production_id').location_dest_id
                    return
            else:
                location_id = self.env['stock.picking'].search([('name', '=', item.name)])
                item.location_id = location_id.location_dest_id
                return

    @api.multi
    def _compute_serial_not_consumed(self):
        for item in self:
            item.serial_not_consumed = len(item.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed))

    @api.multi
    def _compute_can_add_serial(self):
        for item in self:
            if item.is_prd_lot:
                item.can_add_serial = True

    @api.multi
    def _compute_serial_without_pallet_ids(self):
        for item in self:
            if item.product_id.is_standard_weight:
                item.serial_without_pallet_ids = item.stock_production_lot_serial_ids.filtered(
                    lambda a: not a.pallet_id
                )

    @api.onchange('label_durability_id')
    def onchange_label_durability_id(self):
        if self.stock_production_lot_serial_ids:
            for serial_id in self.stock_production_lot_serial_ids:
                serial_id.write({
                    'label_durability_id': self.label_durability_id.id
                })

    @api.multi
    def _compute_count_serial(self):
        for item in self:
            item.count_serial = len(item.stock_production_lot_serial_ids)

    @api.multi
    def print_all_serial(self):
        return self.env.ref('dimabe_manufacturing.action_print_all_serial') \
            .report_action(self.stock_production_lot_serial_ids)

    @api.multi
    def _compute_product_variety(self):
        for item in self:
            item.product_variety = item.product_id.get_variety()

    @api.multi
    def _compute_pallet_ids(self):
        for item in self:
            if item.product_id.is_standard_weight:
                item.pallet_ids = item.stock_production_lot_available_serial_ids.mapped('pallet_id')

    @api.multi
    def _compute_context_picking_id(self):
        for item in self:
            if 'stock_picking_id' in self.env.context:
                item.context_picking_id = self.env.context['stock_picking_id']

    @api.multi
    def _compute_stock_production_lot_available_serial_ids(self):
        for item in self:
            item.stock_production_lot_available_serial_ids = item.stock_production_lot_serial_ids.filtered(
                lambda a: not a.reserved_to_stock_picking_id
            )

    @api.multi
    def _compute_available_total_serial(self):
        for item in self:
            item.available_total_serial = sum(item.stock_production_lot_serial_ids.filtered(
                lambda a: not a.reserved_to_stock_picking_id and not a.consumed
            ).mapped('display_weight'))

    @api.multi
    def _compute_producer_ids(self):
        for item in self:
            if item.is_prd_lot:
                if 'default_producer_ids' in self.env.context.keys():
                    item.producer_ids = self.env.context['default_producer_ids']
                else:
                    item.producer_ids = self.env['res.partner'].search([])
            elif item.is_dried_lot:
                dried_data = self.env['unpelled.dried'].search([
                    ('out_lot_id', '=', item.id)
                ])
                if not dried_data:
                    dried_data = self.env['dried.unpelled.history'].search([
                        ('out_lot_id', '=', item.id)
                    ])
                if dried_data:
                    item.producer_ids = self.env['res.partner'].search([
                        '|',
                        ('id', '=', dried_data.in_lot_ids.mapped('stock_picking_id.partner_id.id')),
                        ('always_to_print', '=', True)
                    ])

            else:
                item.producer_ids = self.env['res.partner'].search([
                    '|',
                    ('supplier', '=', True),
                    ('always_to_print', '=', True)

                ])
            if item.producer_ids:
                item.producer_ids = item.producer_ids.filtered(
                    lambda a: a.company_type == 'company' or a.always_to_print
                )

    @api.multi
    def _search_available_total_serial(self, operator, value):
        stock_production_lot_ids = self.env['stock.production.lot.serial'].search([
            ('consumed', '=', False),
            ('reserved_to_stock_picking_id', '=', False)
        ]).mapped('stock_production_lot_id')

        if operator == '>':
            stock_production_lot_ids = stock_production_lot_ids.filtered(
                lambda a: sum(a.stock_production_lot_serial_ids.mapped('display_weight')) > value
            )
        elif operator == '=':
            stock_production_lot_ids = stock_production_lot_ids.filtered(
                lambda a: sum(a.stock_production_lot_serial_ids.mapped('display_weight')) == value
            )
        elif operator == '<':
            stock_production_lot_ids = stock_production_lot_ids.filtered(
                lambda a: sum(a.stock_production_lot_serial_ids.mapped('display_weight')) < value
            )
        elif operator == '>=':
            stock_production_lot_ids = stock_production_lot_ids.filtered(
                lambda a: sum(a.stock_production_lot_serial_ids.mapped('display_weight')) >= value
            )
        elif operator == '<=':
            stock_production_lot_ids = stock_production_lot_ids.filtered(
                lambda a: sum(a.stock_production_lot_serial_ids.mapped('display_weight')) <= value
            )

        return [('id', 'in', stock_production_lot_ids.mapped('id'))]

    @api.multi
    def _compute_reception_data(self):
        for item in self:
            if item.stock_picking_id:
                item.product_canning = item.stock_picking_id.get_canning_move().name

    @api.multi
    def _compute_total_serial(self):
        for item in self:
            item.total_serial = sum(item.stock_production_lot_serial_ids.mapped('display_weight'))

    @api.multi
    def add_to_packing_list(self):
        picking_id = int(self.env.context['stock_picking_id'])
        self.pallet_ids.filtered(lambda a: not a.reserved_to_stock_picking_id).write({
            'add_picking': True
        })
        self.stock_production_lot_serial_ids.filtered(
            lambda a: not a.reserved_to_stock_picking_id and not a.consumed).write({
            'to_add': True
        })
        picking = self.env['stock.picking'].search([('id', '=', picking_id)])
        dispatch_line = picking.dispatch_line_ids.filtered(lambda x: x.product_id.id == self.product_id.id)
        if len(dispatch_line) > 1:
            view = self.env.ref('dimabe_manufacturing.view_confirm_order_reserved')
            wiz = self.env['confirm.order.reserved'].create({
                'sale_ids': [(4, s.id) for s in dispatch_line.mapped('sale_id')],
                'picking_principal_id': picking.id,
                'custom_dispatch_line_ids': [(4, c.id) for c in dispatch_line],
                'lot_id': self.id
            })
            return {
                'name': 'Seleccione el pedido al cual quiere reservar',
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'confirm.order.reserved',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context
            }
        else:
            self.add_selection(stock_picking_id=picking_id)
        self.clean_add_pallet()
        self.clean_add_serial()

    @api.multi
    def unreserved(self):
        for item in self:
            stock_picking = None
            if 'stock_picking_id' in self.env.context:
                stock_picking_id = self.env.context['stock_picking_id']
                stock_picking = self.env['stock.picking'].search([('id', '=', stock_picking_id)])
            if stock_picking:
                self.update_stock_quant(stock_picking.location_id.id)
                self.pallet_ids.filtered(lambda a: a.reserved_to_stock_picking_id.id == stock_picking_id).write({
                    'reserved_to_stock_picking_id': None
                })
                self.stock_production_lot_serial_ids.filtered(
                    lambda a: a.reserved_to_stock_picking_id.id == stock_picking_id).write({
                    'reserved_to_stock_picking_id': None
                })
                stock_picking.move_line_ids_without_package.filtered(lambda a: a.lot_id.id == self.id).unlink()

    @api.multi
    def write(self, values):
        for item in self:
            final_lot_id = self.env['mrp.workorder'].search([('final_lot_id', '=', item.id)])
            if final_lot_id:
                values['sale_order_id'] = final_lot_id.sale_order_id.id
            res = super(StockProductionLot, self).write(values)
            if 'available_kg' in values.keys():
                if self.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed):
                    self.get_and_update(self.product_id.id)
                else:
                    self.env['stock.quant'].sudo().search(
                        [('lot_id', '=', self.id), ('location_id.usage', '=', 'internal')]).unlink()
            # if not item.producer_id and item.stock_production_lot_serial_ids:
            #     if item.stock_production_lot_serial_ids.mapped('producer_id'):
            #         item.write({
            #             'producer_id': item.stock_production_lot_serial_ids.mapped('producer_id')[0].id,
            #         })
            if not item.product_id.is_standard_weight:
                for serial in item.stock_production_lot_serial_ids:
                    if not serial.serial_number:
                        if len(item.stock_production_lot_serial_ids) > 1:
                            item.stock_production_lot_serial_ids[0].write({
                                'serial_number': item.name + '001'
                            })
                            counter = int(item.stock_production_lot_serial_ids.filtered(lambda a: a.serial_number)[
                                              -1].serial_number) + 1
                        else:
                            counter = 1
                        tmp = '00{}'.format(counter)
                        serial.serial_number = item.name + tmp[-3:]
                        item.write({
                            'available_kg': sum(
                                item.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed).mapped(
                                    'real_weight'))
                        })
            # else:
            #     if len(item.stock_production_lot_serial_ids) > 999:
            #         item.check_duplicate()
            return res

    def get_stock_quant(self, location_id=None):
        if location_id:
            quant_ids = self.quant_ids.filtered(lambda x: x.location_id.id == location_id)
        else:
            quant_ids = self.quant_ids.filtered(lambda x: x.location_id.usage == 'internal')
        return quant_ids
        # if self.location_id:
        #     stock_quant = self.quant_ids.filtered(
        #         lambda a: a.location_id.id == location_id)
        #     return None if not stock_quant else stock_quant
        # else:
        #     return self.quant_ids.filtered(
        #         lambda a: a.location_id.usage == 'internal'
        #     )

    @api.multi
    def delete_all_serial(self):
        for item in self:
            for serial in item.serial_without_pallet_ids:
                serial.unlink()

    def show_available_serial(self):
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'stock.production.lot',
            'res_id': self.id,
            'view_type': 'form',
            'view_mode': 'form',
            'views': [(self.env.ref('dimabe_manufacturing.available_lot_form_view').id, 'form')],
            'target': 'new',
            'nodestroy': True,
            'context': self.env.context
        }

    @api.multi
    def add_selection(self, stock_picking_id=None):
        if 'dispatch_id' in self.env.context.keys():
            picking_id = int(self.env.context['dispatch_id'])
        else:
            picking_id = stock_picking_id
        if not self.stock_production_lot_serial_ids.filtered(lambda a: a.to_add) and not self.pallet_ids.filtered(
                lambda a: a.add_picking):
            raise models.ValidationError('No se seleccionado nada')
        if isinstance(picking_id, dict):
            picking = self.env['stock.picking'].search([('id', '=', picking_id['dispatch_id'])])
        else:
            picking = self.env['stock.picking'].search([('id', '=', picking_id)])
        if self.pallet_ids.filtered(lambda a: a.add_picking):
            self.add_selection_pallet(picking.id, picking.location_id.id)
        if self.stock_production_lot_serial_ids.filtered(lambda a: a.to_add):
            self.add_selection_serial(picking.id, picking.location_id.id)
        dispatch_line = picking.dispatch_line_ids.filtered(lambda x: x.product_id.id == self.product_id.id)
        if len(dispatch_line) > 1:
            view = self.env.ref('dimabe_manufacturing.view_confirm_order_reserved')
            wiz = self.env['confirm.order.reserved'].create({
                'sale_ids': [(4, s.id) for s in dispatch_line.mapped('sale_id')],
                'picking_principal_id': picking.id,
                'custom_dispatch_line_ids': [(4, c.id) for c in dispatch_line],
                'lot_id': self.id
            })
            return {
                'name': 'Seleccione el pedido al cual quiere reservar',
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'confirm.order.reserved',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context
            }
        line = picking.move_line_ids_without_package.filtered(
            lambda a: a.lot_id.id == self.id and a.product_id.id == self.product_id.id)

        if line:
            test = self.get_reserved_quantity_by_picking(picking.id)
            line.sudo().write({
                'product_uom_qty': self.get_reserved_quantity_by_picking(picking.id)
            })
        else:
            line_create = self.env['stock.move.line'].create({
                'move_id': picking.move_ids_without_package.filtered(
                    lambda m: m.product_id.id == self.product_id.id).id,
                'picking_id': picking.id,
                'product_id': self.product_id.id,
                'product_uom_id': self.product_id.uom_id.id,
                'product_uom_qty': self.get_reserved_quantity_by_picking(picking.id),
                'location_id': picking.location_id.id,
                'location_dest_id': picking.partner_id.property_stock_customer.id,
                'date': date.today(),
                'lot_id': self.id
            })
        self.clean_add_pallet()
        self.clean_add_serial()
        picking.clean_reserved()
        if len(dispatch_line) == 1:
            dispatch_line.write({
                'real_dispatch_qty': self.get_reserved_quantity_by_picking(picking.id),
                'move_line_ids': [(4, line_create.id)] if not line else [(4, line.id)]
            })
        picking.write({
            'lot_search_id': None
        })
        self.write({
            'correlative_serial': None
        })

    def add_selection_serial(self, picking_id, location_id):
        pallets = self.stock_production_lot_serial_ids.filtered(
            lambda
                a: a.to_add and not a.reserved_to_stock_picking_id and not a.reserved_to_production_id and not a.consumed).mapped(
            'pallet_id')
        for pallet in pallets:
            pallet.write({
                'reserved_to_stock_picking_id': picking_id
            })
        self.stock_production_lot_serial_ids.filtered(
            lambda a: a.to_add and not a.reserved_to_stock_picking_id and not a.consumed).write({
            'reserved_to_stock_picking_id': picking_id
        })
        self.update_stock_quant(location_id)
        self.clean_add_serial()

    def add_selection_pallet(self, picking_id, location_id):
        self.pallet_ids.filtered(lambda p: p.add_picking).write({
            'reserved_to_stock_picking_id': picking_id
        })
        self.pallet_ids.filtered(lambda p: p.add_picking).mapped('lot_serial_ids').filtered(
            lambda s: not s.reserved_to_stock_picking_id and not s.consumed).write({
            'reserved_to_stock_picking_id': picking_id
        })
        self.update_stock_quant(location_id)

    def get_available_quantity(self):
        return sum(self.stock_production_lot_serial_ids.filtered(
            lambda r: r.reserved_to_stock_picking_id and not r.consumed).mapped('display_weight'))

    def get_reserved_quantity(self):
        return sum(self.stock_production_lot_serial_ids.filtered(lambda
                                                                     x: x.reserved_to_stock_picking_id and x.reserved_to_stock_picking_id.state != 'done' and not x.consumed).mapped(
            'display_weight'))

    def get_reserved_quantity_by_picking(self, picking_id):
        return round(sum(self.stock_production_lot_serial_ids.filtered(
            lambda a: a.reserved_to_stock_picking_id.id == picking_id).mapped(
            'display_weight')))

    def clean_add_pallet(self):
        self.pallet_ids.filtered(lambda a: a.add_picking).write({
            'add_picking': False
        })

    def clean_add_serial(self):
        self.stock_production_lot_serial_ids.filtered(lambda a: a.to_add).write({
            'to_add': False
        })

    def update_stock_quant(self, location_id):
        lot = self.env['stock.production.lot'].search([('name', '=', self.name)])
        if lot.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed):

            quant = self.env['stock.quant'].sudo().search(
                [('lot_id', '=', lot.id), ('location_id.usage', '=', 'internal'), ('location_id', '=', location_id)])

            if quant:
                quant.write({
                    'reserved_quantity': sum(lot.stock_production_lot_serial_ids.filtered(lambda
                                                                                x: x.reserved_to_stock_picking_id and x.reserved_to_stock_picking_id.state != 'done' and not x.consumed).mapped(
                        'display_weight')),
                    'quantity': sum(lot.stock_production_lot_serial_ids.filtered(
                        lambda x: not x.reserved_to_stock_picking_id and not x.consumed).mapped('display_weight')),
                    'location_id': location_id
                })
            else:
                self.env['stock.quant'].sudo().create({
                    'lot_id': lot.id,
                    'product_id': lot.product_id.id,
                    'reserved_quantity': sum(lot.stock_production_lot_serial_ids.filtered(lambda
                                                                                x: x.reserved_to_stock_picking_id and x.reserved_to_stock_picking_id.state != 'done' and not x.consumed).mapped(
                        'display_weight')),
                    'quantity': sum(lot.stock_production_lot_serial_ids.filtered(
                        lambda x: not x.reserved_to_stock_picking_id and not x.consumed).mapped('display_weight')),
                    'location_id': location_id
                })
        else:
            quant = self.env['stock.quant'].sudo().search(
                [('lot_id', '=', lot.id), ('location_id.usage', '=', 'internal'),
                 ('location_id', '=', location_id)])
            quant.write({
                'reserved_quantity': 0,
                'quantity': 0,
                'location_id': location_id
            })

    def update_stock_quant_production(self, location_id):
        lot = self.env['stock.production.lot'].search([('name', '=', self.name)])
        if lot:
            if lot.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed):

                quant = self.env['stock.quant'].sudo().search(
                    [('lot_id', '=', lot.id), ('location_id.usage', '=', 'internal'),
                     ('location_id', '=', location_id)])

                if quant:
                    quant.sudo().write({
                        'reserved_quantity': sum(
                            lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                                'display_weight')),
                        'quantity': sum(lot.stock_production_lot_serial_ids.filtered(
                            lambda x: not x.consumed).mapped('display_weight')),
                        'location_id': location_id
                    })
                else:
                    self.env['stock.quant'].sudo().create({
                        'lot_id': lot.id,
                        'product_id': lot.product_id.id,
                        'reserved_quantity': sum(
                            lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                                'display_weight')),
                        'quantity': sum(lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                            'display_weight')),
                        'location_id': location_id,
                        'in_date': datetime.now()
                    })
            else:
                quant = self.env['stock.quant'].sudo().search(
                    [('lot_id', '=', lot.id), ('location_id.usage', '=', 'internal'),
                     ('location_id', '=', location_id)])
                quant.write({
                    'reserved_quantity': 0,
                    'quantity': sum(lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                        'display_weight'))
                })

    def update_kg(self, lot_id):
        lot = self.env['stock.production.lot'].search([('id', '=', lot_id)])
        total = sum(lot.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed).mapped('display_weight'))
        lot.sudo().write({
            'available_kg': total,
            'available_weight': total
        })

    def verify_without_lot(self):
        for item in self:
            quant = self.env['stock.quant'].sudo().search(
                [('product_id.id', '=', item.product_id.id), ('lot_id', '=', None)])
            quant.sudo().unlink()

    def change_date_packing(self):
        for item in self:
            item.change_packaging = True

    def change_date_best(self):
        for item in self:
            item.change_best = True

    def get_and_update(self, product_id, to_fix=False):
        lots = self.env['stock.production.lot'].search([('product_id', '=', product_id), ('available_kg', '>', 0)])
        for lot in lots:
            quant = self.env['stock.quant'].search([('lot_id', '=', lot.id), ('location_id.usage', '=', 'internal')])
            if quant:
                try:
                    if quant.quantity != lot.available_kg or quant.quantity != sum(
                            lot.stock_production_lot_serial_ids.mapped('display_weight')):
                        quant.write({
                            'reserved_quantity': lot.available_kg,
                            'quantity': lot.available_kg
                        })
                except:
                    query = 'DELETE FROM stock_quant where id = {}'.format(quant[0].id)
                    cr = self._cr
                    cr.execute(query)
            else:
                self.env['stock.quant'].sudo().create({
                    'lot_id': lot.id,
                    'product_id': lot.product_id.id,
                    'reserved_quantity': sum(
                        lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                            'display_weight')),
                    'quantity': sum(lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped(
                        'display_weight')),
                    'location_id': lot.stock_production_lot_serial_ids.mapped('production_id')[
                        0].location_dest_id.id if lot.stock_production_lot_serial_ids.mapped('production_id') else 12,
                    'in_date': datetime.now()
                })
        quant = self.env['stock.quant'].search(
            [('quantity', '<', 0), ('product_id', '=', product_id), ('location_id.usage', '=', 'internal')])
        quant.sudo().unlink()

    def check_all_existence(self):
        lots = self.env['stock.production.lot'].search([('available_kg', '!=', 0), ('harvest', '=', 2021)])
        for lot in lots:
            quant = self.env['stock.quant'].search([('lot_id.id', '=', lot.id), ('location_id.usage', '=', 'internal')])
            if quant:
                if len(quant) == 1:
                    if quant.quantity != sum(
                            lot.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed).mapped(
                                'display_weight')) or quant.quantity < 0:
                        if quant.location_id.usage == 'internal':
                            lot.update_stock_quant_production(quant.location_id.id)
                else:
                    for qu in quant:
                        if qu.quantity != sum(
                                lot.stock_production_lot_serial_ids.filtered(lambda a: not a.consumed).mapped(
                                    'display_weight')) or qu.quantity < 0:
                            if qu.location_id.usage == 'internal':
                                lot.update_stock_quant_production(qu.location_id.id)
            else:
                location_id = lot.location_id.id
                if location_id:
                    lot.update_stock_quant_production(location_id)

    def check_duplicate_quant(self, product_id=None, lot_id=None):
        if lot_id:
            lots = self.env['stock.production.lot'].search(['id', '=', lot_id])
        elif product_id:
            lots = self.env['stock.production.lot'].search(
                [('available_kg', '=', 0), ('harvest', '=', 2021), ('product_id', '=', product_id)])
        else:
            lots = self.env['stock.production.lot'].search([('available_kg', '=', 0), ('harvest', '=', 2021)])
        for lot in lots:
            quant = self.env['stock.quant'].search([('location_id.usage', '=', 'internal'), ('lot_id', '=', lot.id)])
            if len(quant) > 1:
                quant[1:].sudo().unlink()
            if lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed):
                total_not_consumed = sum(
                    lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped('display_weight'))
            else:
                total_not_consumed = 0
            if total_not_consumed == 0:
                quant.sudo().unlink()
            quant.sudo().write({
                'quantity': sum(
                    lot.stock_production_lot_serial_ids.filtered(lambda x: not x.consumed).mapped('display_weight'))
            })

    def check_no_stock_quant(self, product_id=None, lot_id=None):
        if lot_id:
            lots = self.env['stock.production.lot'].search(['id', '=', lot_id])
        elif product_id:
            lots = self.env['stock.production.lot'].search(
                [('available_kg', '=', 0), ('harvest', '=', 2021), ('product_id', '=', product_id)])
        else:
            lots = self.env['stock.production.lot'].search([('available_kg', '=', 0), ('harvest', '=', 2021)])
        for lot in lots:
            quant = self.env['stock.quant'].search([('lot_id.id', '=', lot.id), ('location_id.usage', '=', 'internal')])
            if quant:
                quant.sudo().unlink()

    @api.multi
    def select_serial(self):
        for item in self:
            view = self.env.ref('dimabe_manufacturing.available_lot_form_view_serial_selected')
            if item.correlative_serial or item.correlative_serial != '':
                serials = []
                if ',' in item.correlative_serial:
                    serials = item.correlative_serial.split(',')
                if '-' in item.correlative_serial:
                    list_serial = [int(serial) for serial in item.correlative_serial.split('-')]
                    for serial in list(range(min(list_serial), max(list_serial) + 1)):
                        zeros = '00' if serial <= 9 else '' if serial > 999 else '0'
                        serials.append(f'{zeros}{serial}')
                else:
                    serials.append(item.correlative_serial)
                for serial in serials:
                    serial_number = f'{item.name}{serial}'
                    serial_id = self.env['stock.production.lot.serial'].sudo().search(
                        [('serial_number', '=', serial_number)])
                    if not serial_id:
                        raise models.ValidationError(f'El correlativo {serial} no esta presente en el lote')
                    if serial_id.consumed:
                        raise models.ValidationError(
                            f'El correlativo {serial} ya fue utilizada en el despacho {serial_id.reserved_to_stock_picking_id.name}')
                    if serial_id.reserved_to_stock_picking_id:
                        raise models.ValidationError(
                            f'El correlativo {serial} ya se encuentra reservado en el despacho {serial_id.reserved_to_stock_picking_id.name}'
                        )
                    if serial_id.to_add:
                        raise models.ValidationError(f'El correlativo {serial} ya fue seleccionado')
                    serial_id.write({
                        'to_add': True
                    })
                item.write({
                    'correlative_serial': None,
                })
                return {
                    'type': 'ir.actions.act_window',
                    'view_mode': 'form',
                    'res_model': self._inherit,
                    'target': 'new',
                    'views': [(view.id, 'form')],
                    'res_id': self.id
                }


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Sep  4 16:44:31 2019

@author: wolkerst
"""

import matplotlib.pyplot as plt
import numpy as np
import sys
import warnings
import pickle
import os

if not sys.warnoptions:
    warnings.simplefilter("ignore")
import math
from ssm.core import pchain
from ssm.pmodules import *
from ssm.core.util_pmodules import Aggregate, SimpleInjector
from ssm.pmodules import *
from ssm.pmodules import Reader
from CHECLabPy.plotting.camera import CameraImage
import os
from os import path
import subprocess
import glob

# =============================================================================
# # Load fit parameters
# =============================================================================

def get_fit_parameters(fit_path):
    """ Loads and returns the fit parameter of
        f(x) = ax^2 + bx + d
    """
    property_path = os.path.join(fit_path + "/", "fit_parameters")
    with open(property_path, "rb") as handle:
        props = pickle.load(handle)
    # print(props)
    a = props["all_parameters"][:, 0]
    b = props["all_parameters"][:, 1]
    d = props["all_parameters"][:, 2]

    return (a, b, d)

# =============================================================================
# # Load calibration data - only the data.data and time is necessary
# =============================================================================

def get_your_data(runfile):
    data_proc = pchain.ProcessingChain()
    path = "/data/CHECS-data/astri_onsky/slowsignal/" + str(runfile) + ".hdf5"
    reader = Reader(path)
    data_proc.add(reader)
    frame_cleaner = PFCleaner()
    data_proc.add(frame_cleaner)
    aggr = Aggregate(["raw_resp"])
    data_proc.add(aggr)
    data_proc.run()
    data = aggr.aggr["raw_resp"][0]
    dt = data.time - data.time[0]
    return (data, dt)

def clean_data(data):
    """ Remove nan pixels """
    good_pix = np.ones(data.data.shape, dtype="bool")
    good_pix[np.nan_to_num(data.data) == 0] = False
    return good_pix

def get_smooth_int(
    data,
    dt,
    good_pixel,
    minutes_of_stable_int=None,
    interval_stable=None,
    plot_stable_interval=None,
):
    """
    Input: data (as data.data), dt(timespan), good_pixel
    Optional: minutes_of_stable_int
              interval_stable
              plot_stable_interval
    """
    # Get stable interval from minimum of std, from calibrated data in 4 min measurement intervals
    min_of_run = math.floor(dt[-1] / 60)
    if interval_stable == None:
        if minutes_of_stable_int == None:
            if min_of_run < 1:
                # In case the measurement is very short
                min_of_run = 1
                minutes_of_stable_int = 0.2
            else:
                minutes_of_stable_int = 4
        number_of_intervals = int(min_of_run / minutes_of_stable_int)

        # Splits calibrated data into 4 min intervals
        cal_interval_ = np.array_split(data, number_of_intervals, axis=0)
        cal_interval_std = [
            np.std(cal_interval_[:][i], axis=0) for i in range(number_of_intervals)
        ]
        cal_std = [
            np.nanmean(cal_interval_std[i][good_pixel[0, :] == 1], axis=0)
            for i in range(number_of_intervals)
        ]
        # Dont look at this code ..
        bin_of_min_std = np.where(cal_std == min(cal_std))[0][0]
        min_cal_std = np.array_split(dt, number_of_intervals)[bin_of_min_std]
        int_start = list(dt).index(min_cal_std[0])
        int_stop = list(dt).index(min_cal_std[-1])
    else:
        if minutes_of_stable_int == None:
            if min_of_run < 1:
                # In case the measurement is very short
                min_of_run = 1
                minutes_of_stable_int = 0.2
            else:
                minutes_of_stable_int = 4
        number_of_intervals = int(min_of_run / minutes_of_stable_int)
        min_cal_std = np.array_split(dt, number_of_intervals)[interval_stable]
        int_start = list(dt).index(min_cal_std[0])
        int_stop = list(dt).index(min_cal_std[-1])

    if plot_stable_interval != None:
        # Plot for some pixel to see what is chosen
        plt.figure()
        plt.axvline(dt[int_start], color="k")
        plt.axvline(dt[int_stop], color="k")
        plt.xlabel("Time / s")
        plt.ylabel("Amplitude / (mV)")
        for i in [1, 23, 600, 900, 1200]:
            plt.plot(dt, data[:, i])

    return (int_start, int_stop)

def invf(y, a, b, c):
    return np.nan_to_num(-(b - np.sqrt(b * b - 4 * a * (c - y))) / (2 * a))

def calibrate_data(
    runfile_cal,
    path_,
    save_file=None,
    minutes_of_stable_int=None,
    interval_stable=None,
    plot_stable_interval=None,
):

    """
    Input:      runfile_cal ... data for calibration
                path_ ... place of data
    Optional:   save_file ... no saving by default
                if True ... calibration saved to:
                path_+'/'+ runfile_cal+"calibration_properties"
                minutes_of_stable_int ... default: 4 mins for longer runs and 20s for short runs
                interval_stable ... if you want to control which interval is chosen
                plot_stable_interval ... default None, if any it will plot int for some pixels

    Output:     ff_c ... ff-coeficient
                int_begin,int_end ... interval of least RMS
                int_time_averaged ... time avergaed value of interval
                int_space_averaged ... camera averaged

    """

    data, dt = get_your_data(runfile_cal)
    good_pixel = clean_data(data.data)  # same shape as data.data

    # Get time interval with stable background:
    intensity = invf(data.data, a, b, d)
    intensity[good_pixel == 0] = -1  # Set bad pix to dummy intensity unequal zero

    int_begin, int_end = get_smooth_int(
        data.data,
        dt,
        good_pixel,
        minutes_of_stable_int,
        interval_stable,
        plot_stable_interval,
    )
    int_time_averaged = np.mean(
        intensity[[int_begin, int_end], :], axis=0
    )  # Mean of each pixel individually over stable int time
    int_space_averaged = np.mean(
        intensity[:, good_pixel[0][:]], axis=1
    )  # mean of all pixels at each time frame
    int_spacetime_averaged = np.mean(int_space_averaged)

    # flat fieding Coefficient:
    ff_c = int_spacetime_averaged / int_time_averaged
    ff_c[good_pixel[0, :] == 0] = 0

    calibrated_data = ff_c * intensity
    # Get the offet of Calibrated data via the mean in a good interval
    # I_offset_of_calibration = I_cal - offset_interval
    offset_calibrated_data = calibrated_data - np.mean(
        calibrated_data[int_begin:int_end], axis=0
    )

    if save_file == True:
        # Save new calibration files
        property_path = os.path.join(
            path_ + "/" + runfile_cal, "calibration_properties"
        )
        calibration_properties = {
            "Interval_offset": (int_begin, int_end),
            "New_good_pixel": good_pixel,
            "Time_averaged_int": int_time_averaged,
            "Space_averaged": int_space_averaged,
            "ff_coefficients_c": ff_c,
            "Offset_calibrated_data": offset_calibrated_data,
        }
        with open(property_path, "wb") as handle:
            pickle.dump(calibration_properties, handle)
    return (ff_c, int_begin, int_end, int_time_averaged, int_space_averaged)

def flat_fielding(
    path_,
    runfile_ff,
    runfile_cal=None,
    calibrate=None,
    save_calibration_file=None,
    save_ff_data=None,
):

    """
    Input necessary: path where calibrated data is stored
                     runfile_ff ... data to flat field
    Optional input:  runfile_cal ... data used for calibration, default "Run13638_ss"
                     calibrate ... any input will lead to recalculation of calibration properties
                     save_calibration_file ... any input save the calibration properties to "path_+calibration_properties"
                     Existing files may be overwritten
                     save_ff_data ... will save the flat_fielding output with file it was calibrated
                     e.g. "path_+runfile_cal+Calibration_with+runfile_cal"

    This function returns:
        - the flat field coefficient ff_c
        - the calibrated and ff-data F_i
        - the calibrated data I_cal

    """

    if runfile_cal == None:
        runfile_cal = "Run13638_ss"
    if calibrate != None:
        ff_c, int_begin, int_end, int_time_averaged, int_space_averaged = calibrate_data(
            runfile_cal, path_, save_calibration_file
        )
    if calibrate == None:
        property_path = os.path.join(
            path_ + "/" + runfile_cal, "calibration_properties"
        )
        with open(property_path, "rb") as handle:
            calibration_data = pickle.load(handle)
        int_begin, int_end = calibration_data["Interval_offset"]
        good_pixel = calibration_data["New_good_pixel"]
        ff_c = calibration_data["ff_coefficients_c"]

    data, dt = get_your_data(runfile_ff)
    good_pixel = clean_data(data.data)  # same shape as data.data
    # Get time interval with stable background:
    I_cal = invf(data.data, a, b, d)
    I_cal[good_pixel == 0] = 0
    # Calibrated and Flat fielded data:
    F_i = ff_c * I_cal

    if save_ff_data != None:
        # Save calibations
        property_path = os.path.join(
            path_ + "/" + runfile_ff, "Calibration_with_" + str(runfile_cal)
        )
        CAL = {
            "Interval_offset": (int_begin, int_end),
            "New_good_pixel": good_pixel,
            "Time_averaged_int": int_time_averaged,
            "Space_averaged": int_space_averaged,
            "Calibrated_data": F_i,
            "ff_coefficients_c": ff_c,
        }
        with open(property_path, "wb") as handle:
            pickle.dump(CAL, handle)

    return (F_i, ff_c, I_cal)

def compare_ffc(path_, runfile_cal_a, runfile_cal_b):
    """

    Plots the histogram of two ff-coeffs e.g.
    runfile_cal_a = 'Run13637_ss'
    runfile_cal_b = 'Run13638_ss'

    """
    property_path = os.path.join(path_ + "/" + runfile_cal_a, "calibration_properties")
    with open(property_path, "rb") as handle:
        readfile1 = pickle.load(handle)
    coeff_1 = readfile1["ff_coefficients_c"]
    property_path = os.path.join(path_ + "/" + runfile_cal_b, "calibration_properties")
    with open(property_path, "rb") as handle:
        readfile1 = pickle.load(handle)
    coeff_2 = readfile1["ff_coefficients_c"]

    compare_c = np.nan_to_num((coeff_1 - coeff_2) / coeff_2)
    plt.figure(figsize=(8, 6))
    plt.hist(compare_c, bins=100, range=(-0.1, 0.1))
    plt.xticks(fontsize=13)
    plt.yticks(fontsize=13)
    plt.legend(
        [
            r"$\frac{c_{"
            + str(runfile_cal_a)
            + "} - c_{"
            + str(runfile_cal_b)
            + "}}{c_{"
            + str(runfile_cal_b)
            + "}}$ "
        ],
        fontsize=19,
        loc=1,
    )
    plt.axvline(0, c="k")

def plot_calibration_properties(
    path_,
    runfile_cal,
    save=None,
    camera_min=None,
    camera_max=None,
    cal_min=None,
    cal_max=None,
):

    # import data:
    data, dt = get_your_data(runfile_cal)

    # Import calibration properties
    property_path = os.path.join(path_ + "/" + runfile_cal, "calibration_properties")
    with open(property_path, "rb") as handle:
        readout = pickle.load(handle)
    int_begin, int_end = readout["Interval_offset"]
    int_time_averaged = readout["Time_averaged_int"]
    int_space_averaged = readout["Space_averaged"]
    calibrated_data = readout["Calibrated_data"]
    ff_c = readout["ff_coefficients_c"]
    offset_calibrated_data = readout["Offset_calibrated_data"]

    if camera_min == None:
        zmin_intspace = min(int_space_averaged) - 0.05 * min(int_space_averaged)
    else:
        zmin_intspace = camera_min
    if camera_max == None:
        zmax_intspace = max(int_space_averaged) + 0.05 * max(int_space_averaged)
    else:
        zmax_intspace = camera_max

    if cal_min == None:
        zmin_calbdat = min(int_space_averaged) - 0.01 * min(int_space_averaged)
    else:
        zmin_calbdat = cal_min
    if cal_max == None:
        zmax_calbdat = max(int_space_averaged) + 0.01 * max(int_space_averaged)
    else:
        zmax_calbdat = cal_max

    # Visualize for some pixels:
    plt.figure()
    plt.axvline(dt[int_begin], color="k")
    plt.axvline(dt[int_end], color="k")
    plt.xlabel("Time / s")
    plt.ylabel("Amplitude / (mV)")
    plt.title("Interval of offset")
    for i in [1, 23, 600, 900, 1200]:
        plt.plot(dt, calibrated_data[:, i])
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal, runfile_cal + "_std_interval"
            )
        )

    plt.figure()
    plt.plot(data.time - data.time[0], int_space_averaged)
    plt.xlabel("Time since run start (s)")
    plt.ylabel("Average amplitude (mV)")
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal,
                runfile_cal + "_space_averaged_over_time",
            )
        )

    camera = CameraImage(data.xpix, data.ypix, data.size)
    camera.image = int_time_averaged
    camera.set_limits_minmax(zmin=zmin_intspace, zmax=zmax_intspace)
    camera.add_colorbar("Amplitdue (mV)")
    camera.ax.set_title("Time averaged data")
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal, runfile_cal + "_camera_time_averaged"
            )
        )

    camera = CameraImage(data.xpix, data.ypix, data.size)
    camera.image = calibrated_data[0, :]
    camera.add_colorbar("Amplitdue (mV)")
    zmin_calbdat = min(int_space_averaged) - 0.01 * min(int_space_averaged)
    # zmin_calbdat = 380
    zmax_calbdat = max(int_space_averaged) + 0.01 * max(int_space_averaged)
    camera.set_limits_minmax(zmin=zmin_calbdat, zmax=zmax_calbdat)
    camera.ax.set_title("Calibrated Data")
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal, runfile_cal + "_calibrated_data"
            )
        )

    camera = CameraImage(data.xpix, data.ypix, data.size)
    camera.image = ff_c
    camera.add_colorbar("Amplitdue (mV)")
    camera.ax.set_title("Flat field coefficents $ff_{c}$")
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal, runfile_cal + "_flat_field_coeffs_c"
            )
        )

    camera = CameraImage(data.xpix, data.ypix, data.size)
    camera.image = offset_calibrated_data[0, :]
    camera.add_colorbar("Amplitdue (mV)")
    camera.ax.set_title("Offset of calibrated data")
    zmin_offset = None
    zmax_offset = None
    # np.where(offset_calibrated_data > 50)
    # zmin_offset = 0
    # zmax_offset = 60
    camera.set_limits_minmax(zmin=zmin_offset, zmax=zmax_offset)
    if save != None:
        plt.savefig(
            os.path.join(
                current_path + "/" + runfile_cal,
                runfile_cal + "_offset_calibrated_data",
            )
        )

def get_time(frame):
    #
    m, s = divmod(frame, 60)
    h, m = divmod(m, 60)

    return f"{int(h):02d}:{int(m):02d}:{int(s):02d}"

############## Try out functions ###################################################

# Suspicious patients
# bad_pixel = np.array([25, 58, 101, 226, 256, 259, 304, 448, 449,570, 653, 670, 776, 1049, 1094,
#                      1158, 1177, 1352, 1367, 1381,1427, 1434,1439,1503, 1562, 1680, 1765,
#                      1812, 1813, 1814, 1815, 1829, 1852, 1869, 1945, 1957, 2009, 2043])

# current_path = '/home/wolkerst/projects/cta/SSM-analysis' ## Change to where it will finally be
current_path = os.getcwd()
a, b, d = get_fit_parameters(current_path)

# Define which run to calibrate with which
# Default calibration run
runfile_cal = "Run13638_ss"
# Data for flat fielding
runfile_ff = "Run13312"

flat_fielding(os.getcwd(), "Run13312")

def get_time(frame):

    m, s = divmod(frame, 60)
    h, m = divmod(m, 60)

    return f"{int(h):02d}:{int(m):02d}:{int(s):02d}"


import json
import math
import numpy as np

import networkx as nx
import matplotlib.pyplot as plt

def draw_networkx(graph: nx.Graph, iterations=15, seed=42, node_size=10, with_labels=False, arrows=False, width=0.15):
    """Draw networkx graph with spring layout.

    Args:
        graph (nx.Graph): the graph to draw.
        iterations (int, optional): max number of iterations to spring. Defaults to 15.
        seed (int, optional): random seed. Defaults to 42.
        node_size (int, optional): node size of draw. Defaults to 10.
        with_labels (bool, optional): whether draw labels. Defaults to False.
        arrows (bool, optional): whether draw arrows. Defaults to False.
        width (float, optional): edge width of draw. Defaults to 0.15.
    """

    pos = nx.spring_layout(graph, iterations=iterations, seed=seed)
    fig, ax = plt.subplots(figsize=(15, 9))
    ax.axis('off')
    nx.draw_networkx(graph, pos=pos, ax=ax, arrows=arrows,
                     node_size=node_size, with_labels=with_labels, width=width)

def draw_evaluation(record_path: str, type: str = 'elapsed', datasets=['MathOverflow', 'StackOverflow', 'WikiTalk', 'AskUbuntu'], algor=['SBG', 'CE-SBG', 'O_SBG'], mark=['ro-', 'bs-', 'gd-'], hatch =['.', 'x', '|'], save_path=None, figsize=(10, 8), suptitle=None, ncols=2):
    with open(record_path, 'r') as f:
        record = json.load(f)

    vary = record['vary']
    vary_list = record[vary]

    ncols = 1 if len(datasets) == 1 else ncols
    nrows = math.ceil(len(datasets) / ncols)
    fig, axs = plt.subplots(nrows, ncols, figsize=figsize)
    if suptitle:
        fig.suptitle(suptitle)
    for i, dataset in enumerate(datasets):
        if len(datasets) == 1:
            ax = axs
        elif len(datasets) == 2:
            ax = axs[i]
        else:
            ax = axs[int(i/ncols), i%ncols]
        if type == 'elapsed':
            draw_efficiency_evaluation(ax, vary_list, record, dataset, algor, mark)
            ylabel = 'Time (s)'
        elif type == 'spread':
            draw_quality_evaluation(ax, vary_list, record, dataset, algor, hatch)
            ylabel = 'Influenced number'
        elif type == 'prob_edge':
            draw_prob_edge(ax, vary_list, record, dataset, algor, hatch)
            ylabel = 'Number of Probing edges'

        # ax.set_title(dataset, y=1.04, fontsize='x-large')
        ax.set_title(dataset, y=1.08, fontsize='x-large')
        ax.set_ylabel(ylabel, fontsize='large')
        ax.set_xlabel(vary, fontsize='large')
        ax.legend(bbox_to_anchor=(0, 0.98, 1, 0.10), loc='lower left', ncol=3, mode='expand', edgecolor='1.0')
        ax.tick_params(direction='in', bottom=True, top=True, left=True, right=True, pad=8, labelsize='large')

    fig.tight_layout()
    if save_path:
        plt.savefig(save_path)
    plt.show()

def draw_efficiency_evaluation(ax, vary_list, record_data, dataset, algor=['SBG', 'CE-SBG', 'O_SBG'], mark=['r+-', 'bs-', 'gd-']):
    for alg, m in zip(algor, mark):
        value = np.array(record_data[f'{dataset}_{alg}_elapsed'])

        value = np.where(value > 3600 * 24, 3600 * 24, value)
        valid_value = np.ma.masked_where(value >= 3600 * 24, value)
        invalid_value = np.ma.masked_where(value < 3600 * 24, value)

        ax.plot(vary_list, valid_value, m, label=alg, markersize=9, clip_on=False)
        # plot invalid value with *
        ax.plot(vary_list, invalid_value, f'{m[0]}*-', markersize=9, clip_on=False)

    ax.set_yscale('log', base=10, subs=[10])
    ax.set_yticks([10 ** i for i in range(-1, 6)])
    ax.set_ylim((0.1, 10 ** 5))
    ax.set_xticks(vary_list)
    ax.set_xlim((vary_list[0], vary_list[-1]))

def draw_quality_evaluation(ax, vary_list, record_data, dataset, algor=['SBG', 'CE-SBG', 'O_SBG'], hatch =['.', 'x', '|'], color=['#069AF3', '#F97306', '#15B01A']):
    x = np.arange(len(vary_list))
    width = 0.2
    for alg, h, c, label, offset in zip(algor, hatch, color, ['SBG', 'CE-SBG', 'O-SBG'], [-width, 0, width]):
        value = np.array(record_data[f'{dataset}_{alg}_spread'])
        value = np.where(value < 0, 1, value)

        ax.bar(x + offset, value, width, hatch=h, label=label)

    ax.set_yticks(np.arange(0, 30 * 11, 30))
    ax.set_ylim((0, 300))
    ax.set_xticks(x, vary_list)

def draw_prob_edge(ax, vary_list, record_data, dataset, algor=['SBG', 'CE-SBG', 'O_SBG'], hatch =['.', 'x', '|'], color=['#069AF3', '#F97306', '#15B01A']):
    x = np.arange(len(vary_list))
    width = 0.2
    for alg, h, c, label, offset in zip(algor, hatch, color, ['SBG', 'CE-SBG', 'O-SBG'], [-width, 0, width]):
        value = np.array(record_data[f'{dataset}_{alg}_edges'])
        ax.bar(x + offset, value, width, hatch=h, label=label)

    ax.set_yscale('log', base=10, subs=[10])
    ax.set_yticks([10 ** i for i in range(0, 7)])
    ax.set_ylim((1, 10 ** 6))
    ax.set_xticks(x, vary_list)


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

import os, sys
import numpy as np
import scipy.ndimage as ndimage
from scipy.ndimage import morphology
from scipy.ndimage import measurements
import pandas as pd
from copy import copy

sys.path.insert(0, '/home/fajtai/py/')

def labelData(data,struct = None):
    """
    :param data: input matrix
    :type data: np.ndarray
    :return: (labeledData, numOfLabels)

    """
    if isinstance(struct,type(None)):
        struct = morphology.generate_binary_structure(data.ndim,1)

    labeledData, numOfLabels = measurements.label(data,structure=struct)
    return labeledData, numOfLabels

def get_label_with_overlap(labeled_data, mask, only_max_hit = False, hit_percentage_filter = 0):
    """
    This function is stands for filtering labeled data with a binary mask. Only labels overlapped with the given binary mask will be kept.\n
    :param labeled_data: labeled image data
    :param mask: the binary mask, which is filled with 1 in the target region
    :param only_max_hit: if this switch true, the function returns only one label, with the highest mask hit count
    :param hit_percentage_filter: leaves out the labels with hit count less than X percentage (default 50)
    :return: binary mask of the filtered labeled data
    """
    overlap = ((mask*labeled_data)>0).astype("int")
    hits = pd.DataFrame(columns = ["label_value","hit_count"])
    indices=np.argwhere(overlap>0)
    for i in indices:
        label_val = labeled_data[tuple(i)]
        if not any(hits["label_value"]==label_val):
            hits= hits.append({"label_value":label_val, "hit_count":1},ignore_index=True)
        else:
            c = hits.loc[hits["label_value"]==label_val,"hit_count"]
            hits.loc[hits["label_value"] == label_val, "hit_count"] = c+1

    if only_max_hit:
        hits = hits[hits["hit_count"]==np.max(hits["hit_count"])]

    overlap_filtered_labels = np.zeros_like(labeled_data)

    # print(hits)
    for _,row in hits.iterrows():
        label_indices = labeled_data==row["label_value"]
        if hit_percentage_filter>0:
            if np.sum(label_indices)*hit_percentage_filter/100<= row["hit_count"]:
                overlap_filtered_labels[label_indices]=1
        else:
            overlap_filtered_labels[label_indices]=1

    return overlap_filtered_labels

def get_label_stats(sample,binary_masks,is_labeled=False,ignore_zero = True):
    if not isinstance(binary_masks, np.ndarray) or not isinstance(sample,np.ndarray):
        raise TypeError

    if binary_masks.shape != sample.shape:
        raise IndexError("Sample and mas must have the same size!")

    if not is_labeled:
        labeled_data, num_of_labels = labelData(binary_masks)
        if ignore_zero:
            labels = np.arange(1, num_of_labels+1, dtype="int")
        else:
            labels = np.arange(0, num_of_labels+1, dtype="int")
    else:
        labeled_data = binary_masks
        labels = np.unique(labeled_data)
        if ignore_zero:
            labels = labels[labels!=0]

    df_list = []

    for L in labels:
        ind = labeled_data==L

        df_list.append({"label":L,
                        "count":np.sum(ind),
                        "mean":np.mean(sample[ind]),
                        "min":np.min(sample[ind]),
                        "max":np.max(sample[ind]),
                        "sd":np.std(sample[ind])})
    df = pd.DataFrame(df_list)

    return labeled_data, df

def labeling_data_with_counts(data,remove_background = True):
    labeledData, _ = ndm.label(data)
    unique,counts = np.unique(labeledData,return_counts=True)
    counts = zip(unique,counts)
    counts = sorted(counts, key=lambda x: x[1], reverse=True)

    if remove_background:
        del(counts[0])

    return labeledData, counts

def labelData_highpass(data,threshold):

    filtered_data = np.zeros_like(data)

    filtered_data[data>threshold]=1

    outData = labelData(filtered_data)
    return outData

def shift_label_indices(orig_label_map, delta):
    if not isinstance(orig_label_map,np.ndarray):
        return orig_label_map

    if delta==0:
        return orig_label_map

    shifted = orig_label_map+delta
    shifted[orig_label_map==0]=0

    return shifted

def merge_label_maps(fix_label_map,shifting_label_map, method = 0, return_start_indices = False):
    """

    :param fix_label_map: fix label map
    :param shifting_label_map: label map, which's indices will be shifted
    :param method: 0 - overlapping label indices will be relabeled\n
    1 - fix label map values overrides shifting label map values\n
    2 - shifting label map values overrides fix label map values
    :param return_start_indices:
    :return: merged label map
    """
    if not isinstance(fix_label_map,np.ndarray):
        return

    if not isinstance(shifting_label_map,np.ndarray):
        return

    if fix_label_map.shape != shifting_label_map.shape:
        return

    F = copy(fix_label_map)
    S = copy(shifting_label_map)

    overlap = np.logical_and(fix_label_map > 0, S > 0).astype("int")

    delta = np.max(fix_label_map)
    overlap_delta = 0

    shifted = shift_label_indices(S,delta)
    merged = None

    if method == 0:
        labeled_overlap,_ = labelData(overlap)
        overlap_delta = np.max(shifted)

        shifted[overlap==1]=0
        F[overlap==1]=0

        shifted_labeled_overlap = shift_label_indices(labeled_overlap,overlap_delta)

        merged = F + shifted + shifted_labeled_overlap

    if method == 1:
        shifted[overlap==1]=0
        merged = F + shifted

    if method == 2:
        F[overlap==1]=0
        merged = F + shifted

    if return_start_indices:
        return merged, [delta+1,overlap_delta+1]

    else:
        return merged

def main():
    # A = np.array([1,0,3,0,2,2])
    # B = np.array([2,0,1,1,1,0])
    #
    # print(merge_label_maps(A,B,method=0))
    # print(merge_label_maps(A,B,method=1))
    # print(merge_label_maps(A,B,method=2))
    pass

if __name__ == '__main__':
    main()


from django.contrib import admin
from django.urls import path, include
from .views import index

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index, name = 'index'),
    path('analiz/', include('analiz.urls')),
    path('dersler/', include('dersler.urls')),
    path('users/', include('users.urls')),
    path('deprem/', include('deprem.urls')),
]


""" Displayes
"""
from zope.component import getAdapter
from eea.reports.relations.interfaces import IGroupRelations
from Products.Five import BrowserView

class ReportContainerView(BrowserView):
    """ Default report view
    """
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def is_replaced_by(self):
        """ Is this report replaced by?
        """
        relations = getAdapter(self.context, IGroupRelations)
        return relations.forward()

    def does_replace(self):
        """ Does this report replace other reports?
        """
        relations = getAdapter(self.context, IGroupRelations)
        return relations.backward()

    @property
    def serial_title(self):
        """ Compute serial title for display
        """
        field = self.context.getField('serial_title')
        if field.is_empty(self.context):
            return ''

        value = field.getAccessor(self.context)()

        alt = value[3] if len(value) > 3 else ''
        if alt:
            return alt

        vocab = field.Vocabulary(self.context)
        rtype = vocab.getValue(value[0]) if len(value) > 0  else ''
        number = value[1] if len(value) > 1 else 0
        year = value[2] if len(value) > 2 else -1

        text = rtype

        if text is None:
            text = value[0]

        if number:
            text += " No %s" % number

        if year != -1:
            text += "/%s" % year

        return text

    @property
    def publishers(self):
        """ Compute publishers to be displayed
        """
        field = self.context.getField('publishers')
        value = field.getAccessor(self.context)()
        vocab = field.Vocabulary(self.context)

        for publisher in value:
            yield vocab.getValue(publisher, publisher)

    @property
    def report(self):
        """ Report file
        """
        field = self.context.getField('file')
        return field.getAccessor(self.context)()

    @property
    def filename(self):
        """ Report filename
        """
        size = self.report.get_size()
        if not size:
            return ''

        field = self.context.getField('file')
        filename = field.getFilename(self.context)
        return filename if filename else self.context.pretty_title_or_id()

    @property
    def size(self):
        """ Report file size
        """
        return self.report.getObjSize(self.report)

    @property
    def items(self):
        """ Files children
        """
        return self.context.getFolderContents({
            'portal_type': ['Document', 'File', 'Link', 'Folder']
        })

    @property
    def has_children(self):
        """ Report has children?
        """
        return self.size or self.items

class ReportContainerDownload(BrowserView):
    """ Report download
    """
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def __call__(self):
        """ Download
        """
        field = self.context.getField('file')
        return field.download(self.context, self.request, self.request.response)


from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length=200, verbose_name="Имя")
    email = models.EmailField(max_length=200)
    message = models.TextField(max_length=1000, verbose_name="Сообщение")

    class Meta:
        verbose_name = "Обратная связь"
        verbose_name_plural = "Обратная связь"

    def __str__(self):
        return self.email


# Program that create a classifier chain and perform multilabel classification.

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.utils import check_X_y, check_array
from sklearn.utils.multiclass import unique_labels

class ClassifierChain:
    def __init__(self, base_classifier):
        self.base_classifier = base_classifier
        self.classifiers = []

    def fit(self, X, y):
        X, y = check_X_y(X, y, accept_sparse=True)
        self.classes_ = unique_labels(y)

        for i in range(y.shape[1]):
            classifier = self.base_classifier
            classifier.fit(X, y[:, i])
            self.classifiers.append(classifier)

            # Augment feature matrix
            X = np.concatenate((X, y[:, :i]), axis=1)

    def predict(self, X):
        X = check_array(X, accept_sparse=True)

        Y_pred = np.zeros((X.shape[0], len(self.classes_)), dtype=int)
        for i, classifier in enumerate(self.classifiers):
            Y_pred[:, i] = classifier.predict(X)
            X = np.concatenate((X, Y_pred[:, :i+1]), axis=1)

        return Y_pred

    def predict_proba(self, X):
        X = check_array(X, accept_sparse=True)

        Y_pred_proba = np.zeros((X.shape[0], len(self.classes_)), dtype=float)
        for i, classifier in enumerate(self.classifiers):
            Y_pred_proba[:, i] = classifier.predict_proba(X)[:, 1]
            X = np.concatenate((X, Y_pred_proba[:, :i+1]), axis=1)

        return Y_pred_proba

# Load the emotions dataset from CSV
emotions_data = pd.read_csv('emotions.csv')

# Extract features (X) and labels (y)
X = emotions_data.iloc[:, :-6].values
y = emotions_data.iloc[:, -6:].values

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Define the base classifier
base_classifier = RandomForestClassifier()

# Build the classifier chain
classifier_chain = ClassifierChain(base_classifier)

# Train the classifier chain
classifier_chain.fit(X_train, y_train)

# Make predictions on the test set
y_pred = classifier_chain.predict(X_test)

# Calculate accuracy score
accuracy = accuracy_score(y_test, y_pred)

print("Accuracy:", accuracy)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Custorder',
            fields=[
                ('orderId', models.AutoField(serialize=False, primary_key=True, db_column='orderId')),
                ('orderNum', models.CharField(max_length=50, db_column='orderNum')),
                ('orderDate', models.DateTimeField(db_column='orderDate')),
                ('orderForDate', models.DateTimeField(db_column='orderForDate')),
                ('orderLogEmployee', models.SmallIntegerField(db_column='orderLogEmployee')),
                ('orderDiscount', models.FloatField(db_column='orderDiscount')),
            ],
            options={
                'db_table': 'custorder',
                'managed': False,
            },
        ),
        migrations.CreateModel(
            name='Item',
            fields=[
                ('itemId', models.AutoField(serialize=False, primary_key=True, db_column='itemId')),
                ('itemName', models.CharField(unique=True, max_length=50, db_column='itemName')),
                ('itemPrice', models.FloatField(null=True, db_column='itemPrice', blank=True)),
            ],
            options={
                'db_table': 'item',
                'managed': False,
            },
        ),
        migrations.CreateModel(
            name='ItemQtyUnit',
            fields=[
                ('itemQtyUnitId', models.SmallIntegerField(serialize=False, primary_key=True, db_column='itemQtyUnitId')),
            ],
            options={
                'db_table': 'itemqtyunit',
                'managed': False,
            },
        ),
        migrations.CreateModel(
            name='OrderItems',
            fields=[
                ('orderItemsId', models.AutoField(serialize=False, primary_key=True, db_column='orderItemsId')),
                ('qty', models.SmallIntegerField()),
                ('orderItemPrice', models.FloatField(db_column='orderItemPrice')),
            ],
            options={
                'db_table': 'orderitems',
                'managed': False,
            },
        ),
        migrations.CreateModel(
            name='QtyUnit',
            fields=[
                ('qtyUnitId', models.SmallIntegerField(serialize=False, primary_key=True, db_column='qtyUnitId')),
                ('qtyUnitName', models.CharField(unique=True, max_length=50, db_column='qtyUnitName')),
            ],
            options={
                'db_table': 'qtyunit',
                'managed': False,
            },
        ),
    ]


from datetime import datetime

from O365 import Account
from O365 import FileSystemTokenBackend

from ..data import Monitoring

def get_mail(
    monitoring: Monitoring,
    from_time: datetime,
):
    """
    メールを取得

    Params
    -------
    monitoring: Monitoring,
        監視設定オブジェクト
    """
    # メールボックスへの接続
    credentials = (monitoring.client_id, monitoring.client_secret)
    token_backend = \
        FileSystemTokenBackend(
            token_path=monitoring.token_path,
            token_filename=monitoring.token_file,
        )
    account = Account(credentials, token_backend=token_backend)
    mailbox = account.mailbox()
    # 件名と時刻でメールを絞り込む
    query = \
        mailbox.new_query() \
        .on_attribute('subject') \
        .contains(monitoring.search_word) \
        .on_attribute('receivedDateTime') \
        .greater_equal(from_time)

    messages = mailbox.get_messages(query=query, download_attachments=True)
    return list(messages)