/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.math.impl.interpolation;

import java.util.Arrays;

import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.DoubleArrayMath;

/**
 * Piecewise polynomial interpolator clamped at specified points. 
 * <p>
 * The clamped points are regarded as 'normal' data points in the interpolation result, i.e., 
 * {@code PiecewisePolynomialResult} or {@code PiecewisePolynomialResultsWithSensitivity}.  
 * A consequence of this is, for example, that the coefficient sensitivities involve the sensitivities to clamped points.
 */
public class ClampedPiecewisePolynomialInterpolator extends PiecewisePolynomialInterpolator {
  private final PiecewisePolynomialInterpolator _baseMethod;
  private final double[] _xValuesClamped;
  private final double[] _yValuesClamped;

  /**
   * Construct the interpolator with clamped points.
   * 
   * @param baseMethod The base interpolator must be not be itself
   * @param xValuesClamped X values of the clamped points
   * @param yValuesClamped Y values of the clamped points
   */
  public ClampedPiecewisePolynomialInterpolator(
      PiecewisePolynomialInterpolator baseMethod,
      double[] xValuesClamped,
      double[] yValuesClamped) {
    ArgChecker.notNull(baseMethod, "method");
    ArgChecker.notEmpty(xValuesClamped, "xValuesClamped");
    ArgChecker.notEmpty(yValuesClamped, "yValuesClamped");
    ArgChecker.isFalse(baseMethod instanceof ProductPiecewisePolynomialInterpolator,
        "baseMethod should not be ProductPiecewisePolynomialInterpolator");
    int nExtraPoints = xValuesClamped.length;
    ArgChecker.isTrue(yValuesClamped.length == nExtraPoints,
        "xValuesClamped and yValuesClamped should be the same length");
    _baseMethod = baseMethod;
    _xValuesClamped = Arrays.copyOf(xValuesClamped, nExtraPoints);
    _yValuesClamped = Arrays.copyOf(yValuesClamped, nExtraPoints);
  }

  @Override
  public PiecewisePolynomialResult interpolate(double[] xValues, double[] yValues) {
    ArgChecker.notNull(xValues, "xValues");
    ArgChecker.notNull(yValues, "yValues");
    ArgChecker.isTrue(xValues.length == yValues.length, "xValues length = yValues length");
    double[][] xyValuesAll = getDataTotal(xValues, yValues);
    return _baseMethod.interpolate(xyValuesAll[0], xyValuesAll[1]);
  }

  @Override
  public PiecewisePolynomialResult interpolate(double[] xValues, double[][] yValuesMatrix) {
    throw new UnsupportedOperationException("Use 1D interpolation method");
  }

  @Override
  public PiecewisePolynomialResultsWithSensitivity interpolateWithSensitivity(double[] xValues, double[] yValues) {
    ArgChecker.notNull(xValues, "xValues");
    ArgChecker.notNull(yValues, "yValues");
    ArgChecker.isTrue(xValues.length == yValues.length, "xValues length = yValues length");
    double[][] xyValuesAll = getDataTotal(xValues, yValues);
    return _baseMethod.interpolateWithSensitivity(xyValuesAll[0], xyValuesAll[1]);
  }

  @Override
  public PiecewisePolynomialInterpolator getPrimaryMethod() {
    return _baseMethod;
  }

  private double[][] getDataTotal(double[] xData, double[] yData) {
    int nExtraPoints = _xValuesClamped.length;
    int nData = xData.length;
    int nTotal = nExtraPoints + nData;
    double[] xValuesTotal = new double[nTotal];
    double[] yValuesTotal = new double[nTotal];
    System.arraycopy(xData, 0, xValuesTotal, 0, nData);
    System.arraycopy(yData, 0, yValuesTotal, 0, nData);
    System.arraycopy(_xValuesClamped, 0, xValuesTotal, nData, nExtraPoints);
    System.arraycopy(_yValuesClamped, 0, yValuesTotal, nData, nExtraPoints);
    DoubleArrayMath.sortPairs(xValuesTotal, yValuesTotal);
    return new double[][] {xValuesTotal, yValuesTotal };
  }

}
