class VaRCovarianceMatrixCalculator implements Function<SensitivityAndReturnDataBundle, Map<Integer, ParametricVaRDataBundle>> {

  @Override
  public Map<Integer, ParametricVaRDataBundle> evaluate(final SensitivityAndReturnDataBundle... data) {
    Validate.notNull(data, "data");
    Validate.notEmpty(data, "data");
    Validate.noNullElements(data, "data");
    List<String> firstOrderNames = null;
    final List<Double> firstOrderSensitivity = new ArrayList<>();
    final List<DoubleTimeSeries<?>> firstOrderTimeSeries = new ArrayList<>();
    final List<DoubleTimeSeries<?>> secondOrderTimeSeries = new ArrayList<>();
    List<String> secondOrderNames = null;
    for (final SensitivityAndReturnDataBundle dataForSensitivity : data) {
      final Sensitivity<?> sensitivity = dataForSensitivity.getSensitivity();
      final String identifier = sensitivity.getIdentifier();
      if (sensitivity.getOrder() == 1) {
        final UnderlyingType type = sensitivity.getUnderlyingTypes().get(0);
        final String name = identifier + "_" + type;
        if (firstOrderNames == null) {
          firstOrderNames = new ArrayList<>();
        }
        if (!firstOrderNames.contains(name)) {
          firstOrderNames.add(name);
          firstOrderSensitivity.add(dataForSensitivity.getValue());
          firstOrderTimeSeries.add(dataForSensitivity.getReturnTimeSeriesForUnderlying(type));
        } else {
          final int index = firstOrderNames.indexOf(name);
          firstOrderSensitivity.set(index, firstOrderSensitivity.get(index) + dataForSensitivity.getValue());
        }
      } else if (sensitivity.getOrder() == 2) {
        if (secondOrderNames == null) {
          secondOrderNames = new ArrayList<>();
        }
        if (sensitivity.getUnderlying() instanceof NthOrderUnderlying) {
          final UnderlyingType type = sensitivity.getUnderlyingTypes().get(0);
          final String name = identifier + "_" + type;
          if (!secondOrderNames.contains(name)) {
            secondOrderNames.add(name);
            secondOrderTimeSeries.add(dataForSensitivity.getReturnTimeSeriesForUnderlying(type));
          }
        } else if (sensitivity.getUnderlying() instanceof MixedOrderUnderlying) {
          final UnderlyingType type1 = sensitivity.getUnderlyingTypes().get(0);
          final UnderlyingType type2 = sensitivity.getUnderlyingTypes().get(1);
          final String name1 = identifier + "_" + type1;
          final String name2 = identifier + "_" + type2;
          if (!secondOrderNames.contains(name1)) {
            secondOrderNames.add(name1);
            secondOrderTimeSeries.add(dataForSensitivity.getReturnTimeSeriesForUnderlying(type1));
          }
          if (!secondOrderNames.contains(name2)) {
            secondOrderNames.add(name2);
            secondOrderTimeSeries.add(dataForSensitivity.getReturnTimeSeriesForUnderlying(type2));
          }
        }
      } else {
        throw new IllegalArgumentException("Can only handle first and second order sensitivities");
      }
    }
    final Map<Integer, ParametricVaRDataBundle> result = new HashMap<>();
    DoubleMatrix2D firstOrderCovarianceMatrix = null;
    DoubleMatrix1D firstOrderSensitivityMatrix = null;
    DoubleMatrix2D secondOrderCovarianceMatrix = null;
    DoubleMatrix2D secondOrderSensitivityMatrix = null;
    if (firstOrderNames != null) {
      firstOrderCovarianceMatrix = _calculator.evaluate(firstOrderTimeSeries.toArray(EMPTY));
      firstOrderSensitivityMatrix = new DoubleMatrix1D(firstOrderSensitivity.toArray(new Double[firstOrderSensitivity.size()]));
      result.put(1, new ParametricVaRDataBundle(firstOrderNames, firstOrderSensitivityMatrix, firstOrderCovarianceMatrix, 1));
    }
    if (secondOrderNames != null) {
      final int n = secondOrderNames.size();
      final double[][] secondOrderSensitivities = new double[n][n];
      for (final SensitivityAndReturnDataBundle bundle : data) {
        final Sensitivity<?> sensitivity = bundle.getSensitivity();
        final String identifier = sensitivity.getIdentifier();
        if (sensitivity.getOrder() == 2) {
          if (sensitivity.getUnderlying() instanceof NthOrderUnderlying) {
            final UnderlyingType type = sensitivity.getUnderlyingTypes().get(0);
            final String name = identifier + "_" + type;
            final int index = secondOrderNames.indexOf(name);
            secondOrderSensitivities[index][index] += bundle.getValue();
          } else if (sensitivity.getUnderlying() instanceof MixedOrderUnderlying) {
            final UnderlyingType type1 = sensitivity.getUnderlyingTypes().get(0);
            final UnderlyingType type2 = sensitivity.getUnderlyingTypes().get(1);
            final String name1 = identifier + "_" + type1;
            final String name2 = identifier + "_" + type2;
            final int index1 = secondOrderNames.indexOf(name1);
            final int index2 = secondOrderNames.indexOf(name2);
            secondOrderSensitivities[index1][index2] += bundle.getValue();
            secondOrderSensitivities[index2][index1] = secondOrderSensitivities[index1][index2];
          }
        }
      }
      secondOrderCovarianceMatrix = _calculator.evaluate(secondOrderTimeSeries.toArray(EMPTY));
      secondOrderSensitivityMatrix = new DoubleMatrix2D(secondOrderSensitivities);
      result.put(2, new ParametricVaRDataBundle(secondOrderNames, secondOrderSensitivityMatrix, secondOrderCovarianceMatrix, 2));
    }
    return result;
  }

  public  VaRCovarianceMatrixCalculator(final CovarianceMatrixCalculator calculator);

}