package it.integry.integrywmsnative.gest.picking_resi;

import androidx.databinding.ObservableArrayList;
import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Optional;
import com.annimon.stream.Stream;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.CommonConst;
import it.integry.integrywmsnative.core.data_recover.ColliDataRecoverService;
import it.integry.integrywmsnative.core.exception.InvalidLUException;
import it.integry.integrywmsnative.core.exception.NoArtsFoundException;
import it.integry.integrywmsnative.core.exception.NoLUFoundException;
import it.integry.integrywmsnative.core.exception.NoPrintersFoundException;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.expansion.RunnableArgss;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUBaseOperationsListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUPrintListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILoadingListener;
import it.integry.integrywmsnative.core.model.CommonModelConsts;
import it.integry.integrywmsnative.core.model.MtbAart;
import it.integry.integrywmsnative.core.model.MtbColr;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.dto.PickDataDTO;
import it.integry.integrywmsnative.core.report.ReportManager;
import it.integry.integrywmsnative.core.rest.consumers.ArticoloRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.OrdiniRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PrinterRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.DocumentoResoDTO;
import it.integry.integrywmsnative.core.utility.UtilityBarcode;
import it.integry.integrywmsnative.core.utility.UtilityBigDecimal;
import it.integry.integrywmsnative.core.utility.UtilityDate;
import it.integry.integrywmsnative.core.utility.UtilityString;
import it.integry.integrywmsnative.gest.picking_resi.exceptions.DocumentsLoadException;
import it.integry.integrywmsnative.gest.picking_resi.rest.PickingResiRESTConsumer;
import it.integry.integrywmsnative.gest.picking_resi.rest.WithdrawableDtbDocr;
import it.integry.integrywmsnative.gest.spedizione.exceptions.NotCurrentYearLUException;
import it.integry.integrywmsnative.gest.spedizione.model.PickedQuantityDTO;

public class PickingResiViewModel {


    private Listener mListener;

    private final MutableLiveData<List<WithdrawableDtbDocr>> mPickingList = new MutableLiveData<>();
    private final List<MtbColt> mColliRegistrati = new ArrayList<>();

    private MtbColt mCurrentMtbColt = null;

    private String mDefaultGestioneOfLU;
    private String mDefaultCodAnagOfLU;
    private int mDefaultSegnoOfLU;
    private String mDefaultCodMdepOfLU;

    private final ArticoloRESTConsumer mArticoloRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final ColliDataRecoverService mColliDataRecoverService;
    private final OrdiniRESTConsumer mOrdiniRestConsumerService;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final PrinterRESTConsumer mPrinterRESTConsumer;
    private final PickingResiRESTConsumer mPickingResiRESTConsumer;


    @Inject
    public PickingResiViewModel(ArticoloRESTConsumer articoloRESTConsumer,
                                BarcodeRESTConsumer barcodeRESTConsumer,
                                ColliDataRecoverService colliDataRecoverService,
                                OrdiniRESTConsumer ordiniRESTConsumer,
                                ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer,
                                PrinterRESTConsumer printerRESTConsumer,
                                PickingResiRESTConsumer mPickingResiRESTConsumer) {
        this.mArticoloRESTConsumer = articoloRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mColliDataRecoverService = colliDataRecoverService;
        this.mOrdiniRestConsumerService = ordiniRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mPrinterRESTConsumer = printerRESTConsumer;
        this.mPickingResiRESTConsumer = mPickingResiRESTConsumer;
    }

    public void init(List<DocumentoResoDTO> documentList, int defaultSegnoLU, String codMdep) {
        this.mDefaultSegnoOfLU = defaultSegnoLU;
        this.mDefaultCodMdepOfLU = codMdep;

        this.sendOnLoadingStarted();

        this.initDatiPicking(documentList);

        this.mPickingResiRESTConsumer.loadDocRows(documentList, withdrawableDtbDocr -> {
            this.mPickingList.postValue(withdrawableDtbDocr);
            this.sendOnLoadingEnded();
        }, ex -> this.sendError(new DocumentsLoadException(ex)));
    }

    private void initDatiPicking(List<DocumentoResoDTO> documentList) {
        List<String> foundGestioni = Stream.of(documentList)
                .map(DocumentoResoDTO::getGestione)
                .distinct()
                .withoutNulls()
                .toList();

        if (foundGestioni.size() > 1) {
            this.sendError(new Exception("Sono stati caricati documenti con diverse gestioni"));
        } else mDefaultGestioneOfLU = foundGestioni.get(0);


        List<String> foundCodAnags = Stream.of(documentList)
                .map(DocumentoResoDTO::getCodAnag)
                .distinct()
                .toList();

        if (foundCodAnags.size() > 1) {
            this.sendError(new Exception("Sono stati caricati documenti con diversi codici anagrafici"));
        } else mDefaultCodAnagOfLU = foundCodAnags.get(0);
    }

    public void createNewLU(Integer customNumCollo, String customSerCollo, Runnable onComplete) {
        MtbColt mtbColt = new MtbColt();
        mtbColt.initDefaultFields();
        mtbColt.setGestione(mDefaultGestioneOfLU)
                .setSegno(mDefaultSegnoOfLU)
                .setCodAnag(mDefaultCodAnagOfLU)
                .setCodMdep(mDefaultCodMdepOfLU)
                .setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

        if (customNumCollo != null) mtbColt.setNumCollo(customNumCollo);
        if (!UtilityString.isNullOrEmpty(customSerCollo)) mtbColt.setSerCollo(customSerCollo);

        mColliMagazzinoRESTConsumer.saveCollo(mtbColt, value -> {
            mtbColt
                    .setNumCollo(value.getNumCollo())
                    .setDataCollo(value.getDataColloS())
                    .setMtbColr(new ObservableArrayList<>());

            this.mCurrentMtbColt = mtbColt;

            if (onComplete != null) onComplete.run();
            this.sendLUOpened(mtbColt);
        }, this::sendError);
    }


    public void closeLU(boolean shouldPrint) {
        if (mCurrentMtbColt == null) return;

        this.sendOnLoadingStarted();

        mColliMagazzinoRESTConsumer.canLUBeDeleted(mCurrentMtbColt, canBeDeleted -> {

            if (canBeDeleted) {
                deleteLU(() -> {
                    this.sendLUClosed();
                    this.sendOnLoadingEnded();
                });
            } else {
                this.sendOnNoteAggiuntiveRequired(noteAggiuntive -> {

                    if (!UtilityString.isNullOrEmpty(noteAggiuntive)) {
                        this.mCurrentMtbColt.setAnnotazioni(noteAggiuntive);
                    }

                    this.mColliMagazzinoRESTConsumer.updateDataFine(mCurrentMtbColt, () -> {

                        if (shouldPrint) {
                            printCollo(mCurrentMtbColt, () -> {
                                postCloseOperations(mCurrentMtbColt, () -> {
                                    this.sendLUClosed();
                                    this.sendOnLoadingEnded();
                                });
                            });
                        } else {
                            postCloseOperations(mCurrentMtbColt, () -> {
                                this.sendLUClosed();
                                this.sendOnLoadingEnded();
                            });
                        }

                    }, this::sendError);

                }, () -> {
                    this.sendLUClosed();
                    this.sendOnLoadingEnded();
                });
            }
        }, this::sendError);
    }

    private void deleteLU(Runnable onComplete) {
        mColliMagazzinoRESTConsumer.deleteCollo(mCurrentMtbColt, () -> {

            this.mCurrentMtbColt = null;
            if (onComplete != null) onComplete.run();

        }, this::sendError);
    }

    private void printCollo(MtbColt mtbColtToPrint, Runnable onComplete) {
        this.sendLUPrintRequest(shouldPrint -> {
            if (!shouldPrint) {
                onComplete.run();
            } else {
                this.mPrinterRESTConsumer.getAvailablePrinters(mDefaultCodMdepOfLU, PrinterRESTConsumer.Type.PRIMARIA, printerList -> {
                    if (printerList == null || printerList.size() == 0) {
                        this.sendError(new NoPrintersFoundException());
                        onComplete.run();
                        return;
                    }
                    singlePrint(mtbColtToPrint, printerList.get(0), onComplete, ex -> this.sendLUPrintError(ex, onComplete));
                }, this::sendError);
            }
        });
    }

    private void singlePrint(MtbColt mtbColtToPrint, String printerName, Runnable onComplete, RunnableArgs<Exception> onFailed) {
        String reportName = ReportManager.getReportNameLUFromGestione(mtbColtToPrint.getGestioneEnum());

        this.mPrinterRESTConsumer.printCollo(
                printerName,
                mtbColtToPrint,
                1,
                reportName, onComplete, onFailed);

    }

    private void postCloseOperations(MtbColt mtbColt, Runnable onComplete) {
        this.mColliRegistrati.add(mtbColt);

        List<WithdrawableDtbDocr> tmpList = getPickingList().getValue();

        for (int i = 0; i < tmpList.size(); i++) {
            WithdrawableDtbDocr x = tmpList.get(i);
            List<MtbColr> withdraws = x.getWithdrawRows();

            for (int k = 0; k < withdraws.size(); k++) {
                MtbColr withdrawRow = withdraws.get(k);
                x.setQtaDoc(x.getQtaDoc().subtract(withdrawRow.getQtaCol()));
            }

            x.getWithdrawRows().clear();

            if (UtilityBigDecimal.equalsOrLowerThan(x.getQtaDoc(), BigDecimal.ZERO)) {
                tmpList.remove(x);
                i--;
            }
        }

        this.mPickingList.postValue(tmpList);
        onComplete.run();
    }

    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        //Se non c'è una UL aperta
        if (mCurrentMtbColt == null) {

            //Se è un'etichetta anonima
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                //Se il collo non esiste allora lo creo associandolo a questa etichetta anonima
                //invece se esiste apro un collo nuovo e cerco gli articoli presenti nell'ul
                //dell'etichetta anonima
                this.executeEtichettaAnonimaNotOpenedLU(barcodeScanDTO, onComplete);
            } else {
                this.processBarcodeNotOpenedLU(barcodeScanDTO, onComplete);
            }

        } else {
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                //Cerco gli articoli presenti nell'ul dell'etichetta anonima
                this.executeEtichettaLU(barcodeScanDTO.getStringValue(), onComplete);

            } else {
                this.processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete);
            }
        }
    }

    private void processBarcodeNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        this.createNewLU(
                null,
                null,
                () -> processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete));
    }


    private void processBarcodeAlreadyOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            //Cerco tramite etichetta ean 128 (che può indicarmi un articolo o una UL)
            this.executeEtichettaEan128(barcodeScanDTO, onComplete);


        } else onComplete.run();
    }


    private void executeEtichettaAnonimaNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        mColliMagazzinoRESTConsumer.getBySSCC(barcodeScanDTO.getStringValue(), true, false, mtbColt -> {

            if (mtbColt == null) {

                if (!UtilityBarcode.isEtichettaAnonimaOfCurrentYear(barcodeScanDTO.getStringValue())) {
                    this.sendError(new NotCurrentYearLUException());
                } else {

                    this.createNewLU(
                            UtilityBarcode.getNumColloFromULAnonima(barcodeScanDTO.getStringValue()),
                            CommonConst.Config.DEFAULT_ANONYMOUS_UL_SERIE, onComplete);
                }
            } else {

                this.createNewLU(
                        null,
                        null,
                        () -> searchArtFromUL(mtbColt, onComplete)
                );
            }

        }, this::sendError);
    }

    private void executeEtichettaLU(String SSCC, Runnable onComplete) {
        mColliMagazzinoRESTConsumer.getBySSCC(SSCC, true, false, mtbColt -> {

            if (mtbColt != null && mtbColt.getMtbColr() != null && mtbColt.getMtbColr().size() > 0) {
                if (mtbColt.getSegno() != -1) {
                    searchArtFromUL(mtbColt, onComplete);
                } else this.sendError(new InvalidLUException());
            } else {
                this.sendError(new NoArtsFoundException());
            }

        }, this::sendError);
    }


    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        mBarcodeRESTConsumer.decodeEan128(barcodeScanDTO, ean128Model -> {

            String barcodeProd = null;

            if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) barcodeProd = ean128Model.Sscc;
            if (!UtilityString.isNullOrEmpty(ean128Model.Gtin)) barcodeProd = ean128Model.Gtin;
            if (!UtilityString.isNullOrEmpty(ean128Model.Content))
                barcodeProd = ean128Model.Content;


            if (!UtilityString.isNullOrEmpty(barcodeProd)) {

                if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) {
                    this.executeEtichettaLU(ean128Model.Sscc, onComplete);
                } else {
                    this.sendError(new NoLUFoundException());
                }
            } else {
                //EAN 128 non completo o comunque mancano i riferimenti al prodotto
                onComplete.run();
            }
        }, this::sendError);
    }

    private void searchArtFromUL(MtbColt scannedUL, Runnable onComplete) {

        final List<WithdrawableDtbDocr> pickingList = mPickingList.getValue();
        final List<WithdrawableDtbDocr> matchPickingObject = new ArrayList<>();


        //Controllo se nel collo ho degli articoli che corrispondono per codice / taglia / colore / lotto
        Stream.of(scannedUL.getMtbColr())
                .filter(x -> !UtilityString.isNullOrEmpty(x.getCodMart()))
                .forEach(x -> {

                    for (WithdrawableDtbDocr pickingObject : pickingList) {
                        //Da verificare se il controllo per partita deve essere sempre effettuato
                        if (UtilityString.equalsIgnoreCase(x.getCodMart(), pickingObject.getCodMart()) &&
                                UtilityString.equalsIgnoreCase(x.getCodTagl(), pickingObject.getCodTagl()) &&
                                UtilityString.equalsIgnoreCase(x.getCodCol(), pickingObject.getCodCol())) {

                            if (!matchPickingObject.contains(pickingObject)) {
                                matchPickingObject.add(pickingObject);
                            }

                        }
                    }
                });

        for (WithdrawableDtbDocr matchedObject : matchPickingObject) {
            MtbColt cloneMtbColt = (MtbColt) scannedUL.clone();
            ObservableArrayList<MtbColr> cloneMtbColrs = (ObservableArrayList<MtbColr>) cloneMtbColt.getMtbColr().clone();


            Stream.of(cloneMtbColt.getMtbColr())
                    .filter(x -> !(UtilityString.equalsIgnoreCase(x.getCodMart(), matchedObject.getCodMart()) &&
                            UtilityString.equalsIgnoreCase(x.getCodTagl(), matchedObject.getCodTagl()) &&
                            UtilityString.equalsIgnoreCase(x.getCodCol(), matchedObject.getCodCol())))
                    .forEach(cloneMtbColrs::remove);

            cloneMtbColt.setMtbColr(cloneMtbColrs);

            PickDataDTO tempPickData = new PickDataDTO()
                    .setSourceMtbColt(cloneMtbColt);

            matchedObject.setTempPickData(tempPickData);
        }


        this.loadMatchedRows(matchPickingObject);
        onComplete.run();
    }


    private void loadMatchedRows(List<WithdrawableDtbDocr> matchedRows) {
        if (matchedRows == null || matchedRows.size() == 0) {
            this.sendError(new NoArtsFoundException());
        } else if (matchedRows.size() == 1) {
            WithdrawableDtbDocr matchedItem = matchedRows.get(0);

            if (matchedItem.getWithdrawRows() != null && matchedItem.getWithdrawRows().size() > 1) {

                List<WithdrawableDtbDocr> pickingList = mPickingList.getValue();

                Stream.of(pickingList)
                        .filter(x -> x != matchedItem)
                        .forEach(x -> x.setHidden(true));

                this.sendFilterApplied(null);
                this.getPickingList().postValue(pickingList);
            } else {
                this.dispatchOrdineRow(matchedItem, matchedItem.getRefMtbColt());
            }

        } else {
            List<WithdrawableDtbDocr> pickingList = mPickingList.getValue();

            for (int i = 0; i < pickingList.size(); i++) {
                if (!matchedRows.contains(pickingList.get(i))) {
                    pickingList.get(i).setHidden(true);
                }
            }

            this.sendFilterApplied(null);
            this.getPickingList().postValue(pickingList);

        }
    }


    public void dispatchOrdineRow(final WithdrawableDtbDocr withdrawableDtbDocr, final MtbColt refMtbColt) {
        BigDecimal totalQtaDoc = withdrawableDtbDocr.getQtaDoc();
        BigDecimal totalNumCnfDoc = withdrawableDtbDocr.getNumCnf();
        BigDecimal qtaCnfDoc = withdrawableDtbDocr.getQtaCnf();

        //TODO: Al posto di prelevare la prima riga bisognerebbe controllare se c'è ne una che corrisponde con la partita richiesta
        MtbColr mtbColrToDispatch = withdrawableDtbDocr.getTempPickData() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr().size() > 0 ?
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr().get(0) : null;

        BigDecimal totalAvailableQtaCol;
        BigDecimal totalAvailableQtaCnf;
        BigDecimal totalAvailableNumCnf;

        if (mtbColrToDispatch != null) {
            totalAvailableQtaCol = mtbColrToDispatch.getQtaCol();
            totalAvailableQtaCnf = mtbColrToDispatch.getQtaCnf();
            totalAvailableNumCnf = mtbColrToDispatch.getNumCnf();
        } else {
            totalAvailableQtaCol = totalQtaDoc;
            totalAvailableQtaCnf = qtaCnfDoc;
            totalAvailableNumCnf = totalNumCnfDoc;
        }

        this.sendOnItemDispatched(
                withdrawableDtbDocr.getMtbAart(),
                totalAvailableNumCnf,
                totalAvailableQtaCnf,
                totalAvailableQtaCol,
                totalAvailableQtaCol,
                totalAvailableNumCnf,
                totalAvailableQtaCnf,
                withdrawableDtbDocr.getPartitaMag(),
                withdrawableDtbDocr.getDataScadPartitaMagD(),
                (pickedQuantityDTO, shouldCloseLU) -> {

                    this.saveNewRow(withdrawableDtbDocr,
                            refMtbColt,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });
    }

    public void dispatchRowEdit(final MtbColr mtbColrToEdit) {

        this.sendOnItemDispatched(
                mtbColrToEdit.getMtbAart(),
                mtbColrToEdit.getNumCnf(),
                mtbColrToEdit.getQtaCnf(),
                mtbColrToEdit.getQtaCol(),
                null,
                null,
                null,
                mtbColrToEdit.getPartitaMag(),
                mtbColrToEdit.getDataScadPartitaD(),
                (pickedQuantityDTO, shouldCloseLU) -> {

                    this.saveEditedRow(mtbColrToEdit,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });
    }


    public void saveNewRow(WithdrawableDtbDocr withdrawableDtbDocr, MtbColt refMtbColt, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {
        this.sendOnLoadingStarted();

        final MtbColr mtbColr = new MtbColr()
                .setCodMart(withdrawableDtbDocr.getMtbAart().getCodMart())
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad)
                .setQtaCol(qtaTot)
                .setQtaCnf(qtaCnf)
                .setNumCnf(numCnf)
                .setDescrizione(withdrawableDtbDocr.getMtbAart().getDescrizioneEstesa())
                .setDatetimeRow(UtilityDate.getDateInstance())

                .setCodAnagDoc(withdrawableDtbDocr.getCodAnag())
                .setCodDtipDoc(withdrawableDtbDocr.getCodDtip())
                .setSerDoc(withdrawableDtbDocr.getSerDoc())
                .setNumDoc(withdrawableDtbDocr.getNumDoc())
                .setDataDoc(withdrawableDtbDocr.getDataDocS())
                .setIdRigaDoc(withdrawableDtbDocr.getIdRiga());

        //TODO: Al posto di prelevare la prima riga bisognerebbe controllare se c'è ne una che corrisponde con la partita richiesta
        MtbColr mtbColrToDispatch = withdrawableDtbDocr.getTempPickData() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr() != null &&
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr().size() > 0 ?
                withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getMtbColr().get(0) : null;

        if (mtbColrToDispatch != null) {
            if (UtilityString.isNullOrEmpty(mCurrentMtbColt.getCodTcol()))
                mCurrentMtbColt.setCodTcol(UtilityString.empty2null(withdrawableDtbDocr.getTempPickData().getSourceMtbColt().getCodTcol()));

            mtbColr
                    .setCodJcom(UtilityString.empty2null(mtbColrToDispatch.getCodJcom()))
                    .setSerColloRif(UtilityString.empty2null(mtbColrToDispatch.getSerCollo()))
                    .setNumColloRif(mtbColrToDispatch.getNumCollo())
                    .setGestioneRif(UtilityString.empty2null(mtbColrToDispatch.getGestione()))
                    .setDataColloRif(UtilityString.empty2null(mtbColrToDispatch.getDataColloS()));


            if (mtbColrToDispatch.getPesoNettoKg() != null) {
                //Proporzione
                BigDecimal pesoNettoKg = UtilityBigDecimal.divide(UtilityBigDecimal.multiply(qtaTot, mtbColrToDispatch.getPesoNettoKg()), mtbColrToDispatch.getQtaCol());
                mtbColr.setPesoNettoKg(pesoNettoKg);
            }

            if (mtbColrToDispatch.getPesoLordoKg() != null) {
                //Proporzione
                BigDecimal pesoLordoKg = UtilityBigDecimal.divide(UtilityBigDecimal.multiply(qtaTot, mtbColrToDispatch.getPesoLordoKg()), mtbColrToDispatch.getQtaCol());
                mtbColr.setPesoLordoKg(pesoLordoKg);
            }
        }


        mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

        MtbColt cloneMtbColt = (MtbColt) mCurrentMtbColt.clone();
        cloneMtbColt.setOperation(CommonModelConsts.OPERATION.UPDATE);

        cloneMtbColt.setMtbColr(new ObservableArrayList<>());
        cloneMtbColt.getMtbColr().add((MtbColr) mtbColr.clone());

        boolean shouldPrint = true;

        //Se è l'unico articolo del collo (controllo se è uguale a 0 perché ancora non è stato aggiunto nella lista delle righe)
        if (shouldCloseLU && mCurrentMtbColt.getMtbColr().size() == 0) {
            shouldPrint = false;
        }

        if (UtilityBigDecimal.equalsTo(numCnf, BigDecimal.ZERO) && UtilityBigDecimal.equalsTo(qtaTot, BigDecimal.ZERO)) {
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU(shouldPrint);
            return;
        }

        boolean finalShouldPrint = shouldPrint;
        this.mColliMagazzinoRESTConsumer.saveCollo(cloneMtbColt, value -> {

            mtbColr
                    .setDataCollo(value.getDataColloS())
                    .setNumCollo(value.getNumCollo())
                    .setGestione(value.getGestione())
                    .setSerCollo(value.getSerCollo())
                    .setRiga(value.getMtbColr().get(value.getMtbColr().size() - 1).getRiga())
                    .setUntMis(withdrawableDtbDocr.getMtbAart().getUntMis())
                    .setMtbAart(withdrawableDtbDocr.getMtbAart());

            withdrawableDtbDocr.getWithdrawRows().add(mtbColr);
            mCurrentMtbColt.getMtbColr().add(mtbColr);

            this.mPickingList.postValue(this.mPickingList.getValue());

            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU(finalShouldPrint);
        }, this::sendError);
    }

    private void saveEditedRow(MtbColr mtbColrToUpdate, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {

        this.sendOnLoadingStarted();

        MtbColt mtbColt = new MtbColt()
                .setNumCollo(mtbColrToUpdate.getNumCollo())
                .setDataCollo(mtbColrToUpdate.getDataColloS())
                .setSerCollo(mtbColrToUpdate.getSerCollo())
                .setGestione(mtbColrToUpdate.getGestione())
                .setMtbColr(new ObservableArrayList<>());
        mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

        final MtbColr mtbColr = (MtbColr) mtbColrToUpdate.clone();
        mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
        mtbColr.setRiga(null)
                .setNumCnf(numCnf.subtract(mtbColrToUpdate.getNumCnf()))
                .setQtaCnf(qtaCnf)
                .setQtaCol(qtaTot.subtract(mtbColrToUpdate.getQtaCol()))
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad);

        mtbColt.getMtbColr().add(mtbColr);

        this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

            mtbColr.setNumCnf(numCnf)
                    .setQtaCnf(qtaCnf)
                    .setQtaCol(qtaTot);

            Optional<WithdrawableDtbDocr> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                    .filter(x -> Stream.of(x.getWithdrawRows()).anyMatch(y -> y == mtbColrToUpdate))
                    .findSingle();

            if (pickingObjectDTO.isPresent()) {
                pickingObjectDTO.get().getWithdrawRows().remove(mtbColrToUpdate);
                pickingObjectDTO.get().getWithdrawRows().add(mtbColr);
            }

            this.mCurrentMtbColt.getMtbColr().remove(mtbColrToUpdate);
            this.mCurrentMtbColt.getMtbColr().add(mtbColr);

            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

        }, this::sendError);
    }

    public void deleteRow(MtbColr mtbColrToDelete) {
        this.sendMtbColrDeleteRequest(canDelete -> {
            if (canDelete) {
                this.sendOnLoadingStarted();

                MtbColt mtbColt = new MtbColt()
                        .setNumCollo(mtbColrToDelete.getNumCollo())
                        .setDataCollo(mtbColrToDelete.getDataColloS())
                        .setSerCollo(mtbColrToDelete.getSerCollo())
                        .setGestione(mtbColrToDelete.getGestione())
                        .setMtbColr(new ObservableArrayList<>());
                mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

                MtbColr mtbColr = (MtbColr) mtbColrToDelete.clone();
                mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
                mtbColr.setQtaCol(mtbColr.getQtaCol().multiply(new BigDecimal(-1)))
                        .setNumCnf(mtbColr.getNumCnf().multiply(new BigDecimal(-1)))
                        .setRiga(null);

                mtbColt.getMtbColr().add(mtbColr);

                this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

                    Optional<WithdrawableDtbDocr> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                            .filter(x -> Stream.of(x.getWithdrawRows()).anyMatch(y -> y == mtbColrToDelete))
                            .findSingle();

                    if (pickingObjectDTO.isPresent()) {
                        pickingObjectDTO.get().getWithdrawRows().remove(mtbColrToDelete);
                    }

                    this.mCurrentMtbColt.getMtbColr().remove(mtbColrToDelete);

                    this.sendOnRowSaved();
                    this.sendOnLoadingEnded();

                }, this::sendError);
            }
        });
    }

    public void resetMatchedRows() {
        for (WithdrawableDtbDocr pickingObjectDTO : this.mPickingList.getValue()) {
            pickingObjectDTO
                    .setTempPickData(null)
                    .setHidden(false);
        }

        this.mPickingList.postValue(this.mPickingList.getValue());
        this.sendFilterRemoved();
    }


    public MutableLiveData<List<WithdrawableDtbDocr>> getPickingList() {
        return mPickingList;
    }

    private void sendOnNoteAggiuntiveRequired(RunnableArgs<String> onComplete, Runnable onAbort) {
        if (this.mListener != null) this.mListener.onNoteAggiuntiveRequired(onComplete, onAbort);
    }

    private void sendOnLoadingStarted() {
        if (this.mListener != null) mListener.onLoadingStarted();
    }

    private void sendOnLoadingEnded() {
        if (this.mListener != null) mListener.onLoadingEnded();
    }

    private void sendError(Exception ex) {
        if (this.mListener != null) mListener.onError(ex);
    }

    private void sendLUOpened(MtbColt mtbColt) {
        if (this.mListener != null) mListener.onLUOpened(mtbColt);
    }

    private void sendLUClosed() {
        if (this.mListener != null) mListener.onLUClosed();
    }

    private void sendLUPrintRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) mListener.onLUPrintRequest(onComplete);
    }

    private void sendLUPrintError(Exception ex, Runnable onComplete) {
        if (this.mListener != null) mListener.onLUPrintError(ex, onComplete);
    }

    private void sendMtbColrDeleteRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) mListener.onMtbColrDeleteRequest(onComplete);
    }

    private void sendOnItemDispatched(MtbAart mtbAart,
                                      BigDecimal initialNumCnf,
                                      BigDecimal initialQtaCnf,
                                      BigDecimal initialQtaTot,
                                      BigDecimal totalQtaAvailable,
                                      BigDecimal totalNumCnfAvailable,
                                      BigDecimal qtaCnfAvailable,
                                      String partitaMag,
                                      Date dataScad,
                                      RunnableArgss<PickedQuantityDTO, Boolean> onComplete) {
        if (this.mListener != null) mListener.onItemDispatched(mtbAart,
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                onComplete);
    }

    private void sendOnRowSaved() {
        if (this.mListener != null) mListener.onRowSaved();
    }

    private void sendFilterApplied(String newValue) {
        if (this.mListener != null) mListener.onFilterApplied(newValue);
    }

    private void sendFilterRemoved() {
        if (this.mListener != null) mListener.onFilterRemoved();
    }


    public PickingResiViewModel setListener(Listener listener) {
        this.mListener = listener;
        return this;
    }

    public interface Listener extends ILUPrintListener, ILoadingListener, ILUBaseOperationsListener {

        void onNoteAggiuntiveRequired(RunnableArgs<String> onComplete, Runnable onAbort);

        void onError(Exception ex);

        void onItemDispatched(MtbAart mtbAart,
                              BigDecimal initialNumCnf,
                              BigDecimal initialQtaCnf,
                              BigDecimal initialQtaTot,
                              BigDecimal totalQtaAvailable,
                              BigDecimal totalNumCnfAvailable,
                              BigDecimal qtaCnfAvailable,
                              String partitaMag,
                              Date dataScad,
                              RunnableArgss<PickedQuantityDTO, Boolean> onComplete);

        void onFilterApplied(String newValue);

        void onFilterRemoved();

    }
}
