class BlackFormulaRepository {

  public static double theta(
      double forward,
      double strike,
      double timeToExpiry,
      double lognormalVol,
      boolean isCall,
      double interestRate) {

    ArgChecker.isTrue(forward >= 0d, "negative/NaN forward; have {}", forward);
    ArgChecker.isTrue(strike >= 0d, "negative/NaN strike; have {}", strike);
    ArgChecker.isTrue(timeToExpiry >= 0d, "negative/NaN timeToExpiry; have {}", timeToExpiry);
    ArgChecker.isTrue(lognormalVol >= 0d, "negative/NaN lognormalVol; have {}", lognormalVol);
    ArgChecker.isFalse(Double.isNaN(interestRate), "interestRate is NaN");

    if (-interestRate > LARGE) {
      return 0d;
    }
    double driftLess = driftlessTheta(forward, strike, timeToExpiry, lognormalVol);
    if (Math.abs(interestRate) < SMALL) {
      return driftLess;
    }

    double rootT = Math.sqrt(timeToExpiry);
    double sigmaRootT = lognormalVol * rootT;
    if (Double.isNaN(sigmaRootT)) {
      log.info("lognormalVol * Math.sqrt(timeToExpiry) ambiguous");
      sigmaRootT = 1d;
    }
    int sign = isCall ? 1 : -1;

    boolean bFwd = (forward > LARGE);
    boolean bStr = (strike > LARGE);
    boolean bSigRt = (sigmaRootT > LARGE);
    double d1 = 0d;
    double d2 = 0d;

    double priceLike = Double.NaN;
    double rt =
        (timeToExpiry < SMALL && Math.abs(interestRate) > LARGE) ? (interestRate > 0d ? 1d : -1d) : interestRate * timeToExpiry;
    if (bFwd && bStr) {
      log.info("(large value)/(large value) ambiguous");
      priceLike = isCall ? (forward >= strike ? forward : 0d) : (strike >= forward ? strike : 0d);
    } else {
      if (sigmaRootT < SMALL) {
        if (rt > LARGE) {
          priceLike = isCall ? (forward > strike ? forward : 0d) : (forward > strike ? 0d : -forward);
        } else {
          priceLike = isCall ?
              (forward > strike ? forward - strike * Math.exp(-rt) : 0d) :
              (forward > strike ? 0d : -forward + strike * Math.exp(-rt));
        }
      } else {
        if (Math.abs(forward - strike) < SMALL | bSigRt) {
          d1 = 0.5 * sigmaRootT;
          d2 = -0.5 * sigmaRootT;
        } else {
          d1 = Math.log(forward / strike) / sigmaRootT + 0.5 * sigmaRootT;
          d2 = d1 - sigmaRootT;
        }
        double nF = NORMAL.getCDF(sign * d1);
        double nS = NORMAL.getCDF(sign * d2);
        double first = nF == 0d ? 0d : forward * nF;
        double second = ((nS == 0d) | (Math.exp(-interestRate * timeToExpiry) == 0d)) ?
            0d :
            strike * Math.exp(-interestRate * timeToExpiry) * nS;
        priceLike = sign * (first - second);
      }
    }

    double res = (interestRate > LARGE && Math.abs(priceLike) < SMALL) ? 0d : interestRate * priceLike;
    return Math.abs(res) > LARGE ? res : driftLess + res;
  }

  private  BlackFormulaRepository();

}