package org.openlmis.core.model.service;

import com.google.inject.Inject;

import org.joda.time.DateTime;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.exceptions.StockMovementIsNullException;
import org.openlmis.core.manager.MovementReasonManager;
import org.openlmis.core.manager.SharedPreferenceMgr;
import org.openlmis.core.model.Cmm;
import org.openlmis.core.model.Period;
import org.openlmis.core.model.StockCard;
import org.openlmis.core.model.StockMovementItem;
import org.openlmis.core.model.repository.CmmRepository;
import org.openlmis.core.model.repository.StockMovementRepository;
import org.openlmis.core.model.repository.StockRepository;
import org.openlmis.core.utils.DateUtil;
import org.roboguice.shaded.goole.common.base.Optional;
import org.roboguice.shaded.goole.common.base.Predicate;
import org.roboguice.shaded.goole.common.collect.Ordering;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import static org.openlmis.core.utils.DateUtil.today;
import static org.roboguice.shaded.goole.common.collect.FluentIterable.from;

public class StockService {

    private final int LOW_STOCK_CALCULATE_MONTH_QUANTITY = 3;

    @Inject
    StockRepository stockRepository;
    @Inject
    CmmRepository cmmRepository;
    @Inject
    StockMovementRepository stockMovementRepository;

    public StockService() {
    }

    protected Date queryFirstPeriodBegin(final StockCard stockCard) throws LMISException {
        StockMovementItem stockMovementItem = stockMovementRepository.queryFirstStockMovementByStockCardId(stockCard.getId());
        if (stockMovementItem == null) {
            throw new StockMovementIsNullException(stockCard);
        }
        return stockMovementItem.getMovementPeriod().getBegin().toDate();
    }

    public void monthlyUpdateAvgMonthlyConsumption() {
        DateTime recordLowStockAvgPeriod = SharedPreferenceMgr.getInstance().getLatestUpdateLowStockAvgTime();
        Period period = Period.of(today());
        if (recordLowStockAvgPeriod.isBefore(period.getBegin())) {
            immediatelyUpdateAvgMonthlyConsumption();
        }
    }

    public void immediatelyUpdateAvgMonthlyConsumption() {
        try {
            List<StockCard> stockCards = stockRepository.list();
            for (StockCard stockCard : stockCards) {
                stockCard.setAvgMonthlyConsumption(calculateAverageMonthlyConsumption(stockCard));
                stockRepository.createOrUpdate(stockCard);
                cmmRepository.save(Cmm.initWith(stockCard, Period.of(today())));
            }
            SharedPreferenceMgr.getInstance().updateLatestLowStockAvgTime();
        } catch (LMISException e) {
            e.reportToFabric();
        }
    }

    protected float calculateAverageMonthlyConsumption(StockCard stockCard) {
        Date firstPeriodBegin;
        try {
            firstPeriodBegin = queryFirstPeriodBegin(stockCard);
        } catch (LMISException e) {
            e.reportToFabric();
            return -1;
        }

        List<Long> issuePerMonths = new ArrayList<>();
        Period period = Period.of(today());
        int periodQuantity = DateUtil.calculateDateMonthOffset(firstPeriodBegin, period.getBegin().toDate());

        if (periodQuantity < LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
            return -1;
        }

        for (int i = 0; i < periodQuantity; i++) {
            period = period.previous();
            Long totalIssuesEachMonth = calculateTotalIssuesPerPeriod(stockCard, period);

            if (totalIssuesEachMonth == null) {
                continue;
            }

            issuePerMonths.add(totalIssuesEachMonth);

            if (issuePerMonths.size() == LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
                break;
            }
        }

        if (issuePerMonths.size() < LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
            return -1;
        }
        return getTotalIssues(issuePerMonths) * 1f / LOW_STOCK_CALCULATE_MONTH_QUANTITY;
    }

    private long getTotalIssues(List<Long> issuePerMonths) {
        long total = 0;
        for (Long totalIssues : issuePerMonths) {
            total += totalIssues;
        }
        return total;
    }

    private Long calculateTotalIssuesPerPeriod(StockCard stockCard, Period period) {
        long totalIssued = 0;
        try {
            List<StockMovementItem> stockMovementItems = stockMovementRepository.queryStockMovementsByMovementDate(stockCard.getId(), period.getBegin().toDate(), period.getEnd().toDate());
            //the query above is actually wasteful, the movement items have already been queried and associated to the stock card

            if (periodHasStockOut(stockCard, stockMovementItems, period)) {
                return null;
            }

            for (StockMovementItem item : stockMovementItems) {
                if (MovementReasonManager.MovementType.ISSUE == item.getMovementType()) {
                    totalIssued += item.getMovementQuantity();
                }
            }
            return totalIssued;
        } catch (LMISException e) {
            e.reportToFabric();
            return null;
        }
    }

    private boolean periodHasStockOut(StockCard stockCard, List<StockMovementItem> stockMovementItems, final Period period) {
        if (stockMovementItems.isEmpty()) {
            return isStockOutStatusInherited(stockCard, period);
        } else {
            return hasStockOutInThisPeriod(stockMovementItems);
        }
    }

    private boolean hasStockOutInThisPeriod(List<StockMovementItem> stockMovementItems) {
        return from(stockMovementItems).anyMatch(new Predicate<StockMovementItem>() {
            @Override
            public boolean apply(StockMovementItem stockMovementItem) {
                return stockMovementItem.getStockOnHand() == 0;
            }
        });
    }

    private boolean isStockOutStatusInherited(StockCard stockCard, final Period period) {
        List<StockMovementItem> orderedMovements = Ordering.from(new Comparator<StockMovementItem>() {
            @Override
            public int compare(StockMovementItem lhs, StockMovementItem rhs) {
                return lhs.getMovementDate().compareTo(rhs.getMovementDate());
            }
        }).sortedCopy(stockCard.getStockMovementItemsWrapper());

        Optional<StockMovementItem> lastMovementBeforePeriod = from(orderedMovements)
                .filter(new Predicate<StockMovementItem>() {
                    @Override
                    public boolean apply(StockMovementItem stockMovementItem) {
                        return new DateTime(stockMovementItem.getMovementDate()).isBefore(period.getBegin());
                    }
                })
                .last();

        return lastMovementBeforePeriod.isPresent() && lastMovementBeforePeriod.get().getStockOnHand() == 0;
    }
}

@@ -1156,16 +1156,79 @@
 vice %7B%0A%0A
+    private final int HIGH_STOCK_CALCULATE_MONTH_QUANTITY = 3;%0A
     priv
@@ -1278,17 +1278,17 @@
 NTITY = 
-3
+2
 ;%0A%0A    @
@@ -3092,32 +3092,63 @@
 portToFabric();%0A
+            //TODO: not period%0A
             retu
@@ -3453,33 +3453,32 @@
          return 
--
 1;%0A        %7D%0A%0A  
@@ -3737,33 +3737,32 @@
 ;%0A            %7D%0A
-%0A
             issu
@@ -3799,17 +3799,16 @@
 Month);%0A
-%0A
         
@@ -3840,19 +3840,20 @@
 ze() == 
-LOW
+HIGH
 _STOCK_C
@@ -3931,115 +3931,8 @@
  %7D%0A%0A
-        if (issuePerMonths.size() %3C LOW_STOCK_CALCULATE_MONTH_QUANTITY) %7B%0A            return -1;%0A        %7D%0A
     
@@ -3984,42 +3984,29 @@
 f / 
-LOW_STOCK_CALCULATE_MONTH_QUANTITY
+issuePerMonths.size()
 ;%0A  

update CMM value