/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2012 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.libreplan.business.planner.entities;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import org.joda.time.LocalDate;
import org.libreplan.business.orders.entities.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Diego Pino García <dpino@igalia.com>
 */
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class OrderEarnedValueCalculator extends EarnedValueCalculator implements IOrderEarnedValueCalculator {

    @Autowired
    private ICostCalculator hoursCostCalculator;

    @Transactional(readOnly = true)
    @Override
    public BigDecimal getActualCostWorkPerformedAt(Order order, LocalDate date) {
        SortedMap<LocalDate, BigDecimal> actualCost = calculateActualCostWorkPerformed(order);
        return getValueAt(actualCost, date);
    }

    @Transactional(readOnly = true)
    @Override
    public SortedMap<LocalDate, BigDecimal> calculateActualCostWorkPerformed(
            Order order) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
        for (TaskElement taskElement : getAllTaskElements(order)) {
            if (taskElement instanceof Task) {
                addCost(result, getWorkReportCost((Task) taskElement));
            }
        }
        return accumulateResult(result);
    }

    private SortedMap<LocalDate, BigDecimal> accumulateResult(
            SortedMap<LocalDate, BigDecimal> map) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
        if (map.isEmpty()) {
            return result;
        }

        BigDecimal accumulatedResult = BigDecimal.ZERO;
        for (LocalDate day : map.keySet()) {
            BigDecimal value = map.get(day);
            accumulatedResult = accumulatedResult.add(value);
            result.put(day, accumulatedResult);
        }
        return result;
    }

    private void addCost(SortedMap<LocalDate, BigDecimal> currentCost,
            SortedMap<LocalDate, BigDecimal> additionalCost) {
        for (LocalDate day : additionalCost.keySet()) {
            if (!currentCost.containsKey(day)) {
                currentCost.put(day, BigDecimal.ZERO);
            }
            currentCost.put(day,
                    currentCost.get(day).add(additionalCost.get(day)));
        }
    }

    private List<TaskElement> getAllTaskElements(Order order) {
        List<TaskElement> result = order.getAllChildrenAssociatedTaskElements();
        result.add(order.getAssociatedTaskElement());
        return result;
    }

    private SortedMap<LocalDate, BigDecimal> getWorkReportCost(Task taskElement) {
        return hoursCostCalculator.getWorkReportCost(taskElement);
    }

    @Override
    @Transactional(readOnly = true)
    public BigDecimal getBudgetAtCompletion(Order order) {
		SortedMap<LocalDate, BigDecimal> budgedtedCost = calculateBudgetedCostWorkScheduled(order);
		return !budgedtedCost.isEmpty() ? budgedtedCost.get(budgedtedCost
				.lastKey()) : BigDecimal.ZERO;
    }

    @Override
    @Transactional(readOnly = true)
    public SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkScheduled(
            Order order) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
        for (TaskElement taskElement : getAllTaskElements(order)) {
            if (taskElement instanceof Task) {
                addCost(result, getEstimatedCost((Task) taskElement));
            }
        }
        return accumulateResult(result);
    }

    private SortedMap<LocalDate, BigDecimal> getEstimatedCost(Task task) {
        return hoursCostCalculator.getEstimatedCost(task);
    }

    @Override
    @Transactional(readOnly = true)
    public BigDecimal getBudgetedCostWorkPerformedAt(Order order, LocalDate date) {
        SortedMap<LocalDate, BigDecimal> budgetedCost = calculateBudgetedCostWorkPerformed(order);
        return getValueAt(budgetedCost, date);
    }

    private BigDecimal getValueAt(SortedMap<LocalDate, BigDecimal> map,
            LocalDate date) {
        BigDecimal result = map.get(date);
        if (result != null) {
            return result;
        }
        for (LocalDate each : map.keySet()) {
            if (date.isBefore(each)) {
                return map.get(each);
            }
        }
        if (date.isAfter(map.lastKey())) {
            return map.get(map.lastKey());
        }
        return BigDecimal.ZERO;
    }

    @Override
    @Transactional(readOnly = true)
    public SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkPerformed(
            Order order) {
        SortedMap<LocalDate, BigDecimal> estimatedCost = new TreeMap<LocalDate, BigDecimal>();
        for (TaskElement taskElement : getAllTaskElements(order)) {
            if (taskElement instanceof Task) {
                addCost(estimatedCost, getAdvanceCost((Task) taskElement));
            }
        }
        return accumulateResult(estimatedCost);
    }

    private SortedMap<LocalDate, BigDecimal> getAdvanceCost(Task task) {
        return hoursCostCalculator.getAdvanceCost(task);
    }

    @Override
    public BigDecimal getCostPerformanceIndex(BigDecimal budgetedCost,
            BigDecimal actualCost) {
        if (BigDecimal.ZERO.compareTo(actualCost) == 0) {
            return BigDecimal.ZERO;
        }
        return budgetedCost.setScale(4)
                .divide(actualCost, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100))
                .setScale(2, RoundingMode.HALF_UP);
    }

    @Override
    public BigDecimal getCostVariance(BigDecimal budgetedCost,
            BigDecimal actualCost) {
        return budgetedCost.subtract(actualCost);
    }

    @Override
    public BigDecimal getEstimateAtCompletion(BigDecimal budgetAtCompletion,
            BigDecimal costPerformanceIndex) {
        if (BigDecimal.ZERO.compareTo(costPerformanceIndex) == 0) {
            return BigDecimal.ZERO;
        }
        return budgetAtCompletion.setScale(2)
                .divide(costPerformanceIndex, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100));
    }

    @Override
    public BigDecimal getEstimateToComplete(BigDecimal estimateAtCompletion,
            BigDecimal actualCostWorkPerformed) {
        return estimateAtCompletion.subtract(actualCostWorkPerformed);
    }

}

@@ -5095,32 +5095,107 @@
 calDate date) %7B%0A
+        if (map.isEmpty()) %7B%0A            return BigDecimal.ZERO;%0A        %7D%0A
         BigDecim

Fix exception in progress dashboard if there are no progress in the project yet