/*
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.swaption;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Period;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.pricer.option.RawOptionData;
import com.opengamma.strata.pricer.option.TenorRawOptionData;

/**
 * Utilities for the different tests related to {@link SabrSwaptionCalibrator}
 */
public class SabrSwaptionCalibratorSmileTestUtils {

  /**
   * Create a {@link RawOptionData} object for calibration from data.
   * 
   * @param tenors  the list of tenors
   * @param expiries  the list of expiries
   * @param strikeLikeType  the type of the strike-like dimension
   * @param strikeLikeData  the data related to the strike-like dimension
   * @param dataType  the type of the data
   * @param dataArray  the array with the raw data, including potential Double.NaN for missing data.
   * @return the raw option data object
   */
  public static TenorRawOptionData rawData(
      List<Tenor> tenors,
      List<Period> expiries,
      ValueType strikeLikeType,
      DoubleArray strikeLikeData,
      ValueType dataType,
      double[][][] dataArray) {

    Map<Tenor, RawOptionData> raw = new TreeMap<>();
    for (int looptenor = 0; looptenor < dataArray.length; looptenor++) {
      DoubleMatrix matrix = DoubleMatrix.ofUnsafe(dataArray[looptenor]);
      raw.put(tenors.get(looptenor), RawOptionData.of(expiries, strikeLikeData, strikeLikeType, matrix, dataType));
    }
    return TenorRawOptionData.of(raw);
  }

  /**
   * Create a {@link RawOptionData} object for calibration from data and shift one point.
   * 
   * @param tenors  the list of tenors
   * @param expiries  the list of expiries
   * @param strikeLikeType  the type of the strike-like dimension
   * @param strikeLikeData  the data related to the strike-like dimension
   * @param dataType  the type of the data
   * @param dataArray  the array with the raw data, including potential Double.NaN for missing data.
   * @param i  the index of the tenor to shift
   * @param j  the index of the expiry to shift
   * @param k  the index of the strike-like dimension to shift
   * @param shift  the size of the shift
   * @return the raw option data object
   */
  public static TenorRawOptionData rawDataShiftPoint(
      List<Tenor> tenors,
      List<Period> expiries,
      ValueType strikeLikeType,
      DoubleArray strikeLikeData,
      ValueType dataType,
      double[][][] dataArray,
      int i,
      int j,
      int k,
      double shift) {

    Map<Tenor, RawOptionData> raw = new TreeMap<>();
    for (int looptenor = 0; looptenor < dataArray.length; looptenor++) {
      double[][] shiftedData = Arrays.stream(dataArray[looptenor])
          .map(row -> row.clone())
          .toArray(l -> new double[l][]); // deep copy of 2d array
      if (looptenor == i) {
        shiftedData[j][k] += shift;
      }
      DoubleMatrix matrix = DoubleMatrix.ofUnsafe(shiftedData);
      raw.put(tenors.get(looptenor), RawOptionData.of(expiries, strikeLikeData, strikeLikeType, matrix, dataType));
    }
    return TenorRawOptionData.of(raw);
  }

  /**
   * Create a {@link RawOptionData} object for calibration from data and shift one smile.
   * 
   * @param tenors  the list of tenors
   * @param expiries  the list of expiries
   * @param strikeLikeType  the type of the strike-like dimension
   * @param strikeLikeData  the data related to the strike-like dimension
   * @param dataType  the type of the data
   * @param dataArray  the array with the raw data, including potential Double.NaN for missing data.
   * @param i  the index of the tenor to shift
   * @param j  the index of the expiry to shift
   * @param shift  the size of the shift
   * @return the raw option data object
   */
  public static TenorRawOptionData rawDataShiftSmile(
      List<Tenor> tenors,
      List<Period> expiries,
      ValueType strikeLikeType,
      DoubleArray strikeLikeData,
      ValueType dataType,
      double[][][] dataArray,
      int i,
      int j,
      double shift) {

    Map<Tenor, RawOptionData> raw = new TreeMap<>();
    for (int looptenor = 0; looptenor < dataArray.length; looptenor++) {
      double[][] shiftedData = Arrays.stream(dataArray[looptenor])
          .map(row -> row.clone())
          .toArray(l -> new double[l][]); // deep copy of 2d array
      if (looptenor == i) {
        for (int k = 0; k < shiftedData[j].length; k++) {
          shiftedData[j][k] += shift;
        }
      }
      DoubleMatrix matrix = DoubleMatrix.ofUnsafe(shiftedData);
      raw.put(tenors.get(looptenor), RawOptionData.of(expiries, strikeLikeData, strikeLikeType, matrix, dataType));
    }
    return TenorRawOptionData.of(raw);
  }

  /**
   * Check that the results are acceptable by checking that the absolute difference or the relative difference
   * is below a given threshold.
   * 
   * @param value1  the first value
   * @param value2  the second value to compare
   * @param tolerance  the tolerance
   * @param msg  the message to return in case of failure
   */
  public static void checkAcceptable(double value1, double value2, double tolerance, String msg) {
    assertThat((Math.abs(value1 - value2) < tolerance) ||
        (Math.abs((value1 - value2) / value2) < tolerance)).as(msg).isTrue();
  }

}
