package name.abuchen.portfolio.math;

import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;

import name.abuchen.portfolio.math.NewtonGoalSeek.Function;

public final class IRR
{
    private IRR()
    {
    }

    @SuppressWarnings("nls")
    private static double halving(Function f, double left, double right, double fLeft, double fRight)
    {
        if (fLeft * fRight >= 0)
            throw new UnsupportedOperationException("Endpoints of interval must have different sign in f");

        double center = (left + right) / 2;

        if (right - left < 0.001d)
            return center;

        double fCenter = f.compute(center);
        if (fCenter == 0)
            return center;
        else if (fCenter * fLeft < 0)
            return halving(f, left, center, fLeft, fCenter);
        else
            return halving(f, center, right, fCenter, fRight);
    }

    public static double calculate(List<LocalDate> dates, List<Double> values)
    {
        Function npv = new NPVFunction(dates, values);
        Function derivative = new PseudoDerivativeFunction(npv);

        // find a crude initial guess in interval (0,1)
        // npv(0) is undefined, but diverges with sign given by last term
        double fLeft = Double.POSITIVE_INFINITY * values.get(values.size() - 1);
        // npv(1) is the sum of undiscounted flows
        double fRight = values.stream().collect(Collectors.summingDouble(f -> f));
        // if they have the same sign, we hopefully don't have a very extreme case, so just guess 5%
        double guess = fLeft * fRight < 0 ? halving(npv, 0, 1, fLeft, fRight) : 1.05;

        return NewtonGoalSeek.seek(npv, derivative, guess) - 1;
    }
}

@@ -389,27 +389,49 @@
 if (
-fLeft * 
+Math.signum(fLeft) == Math.signum(
 fRight
- %3E= 0
+)
 )%0A  
@@ -763,27 +763,51 @@
 if (
-fCenter * fLeft %3C 0
+Math.signum(fCenter) == Math.signum(fRight)
 )%0A  
@@ -1210,16 +1210,17 @@
 l (0,1)%0A
+%0A
         
@@ -1247,16 +1247,26 @@
 ed, but 
+the limit 
 diverges
@@ -1285,16 +1285,99 @@
 iven by 
+most discounted cashflow%0A        // we only care about the sign, so we can use the 
 last ter
@@ -1404,35 +1404,8 @@
 ft =
- Double.POSITIVE_INFINITY *
  val
@@ -1565,16 +1565,39 @@
  -%3E f));
+%0A%0A        double guess;
 %0A       
@@ -1632,113 +1632,173 @@
 gn, 
-we hopefully don't have a very extreme case, so just guess 5%25%0A        double guess = fLeft * fRight %3C 0 ?
+let's hope the zero is reasonable, so just guess 5%25%0A        if (Math.signum(fLeft) == Math.signum(fRight))%0A            guess = 1.05;%0A        else%0A            guess =
  hal
@@ -1831,15 +1831,8 @@
 ght)
- : 1.05
 ;%0A%0A 

Make interval halving clearer by using Math.signum(NPV)