import java.util.function.*;

/** Experiments.
 * @author Jaanus
 * @since 1.8
 */
public class J8example9 {

  public static void main (String[] args) {
    Function<Double, Double> f = Math::sin;
    double res = f.apply (Math.PI/2.);
    System.out.println (res);
    Function<Double, Double> g = ((Function<Double, Double>)Math::sin).compose(Math::toRadians);
    System.out.println (g.apply (45.));
    ((Consumer<String>)System.out::println).accept (String.valueOf (g.apply (45.)));
    BiFunction<Integer, Integer, Integer> p = J8example9::minus;
    BiFunction<Integer, Integer, Integer> q = Math::max;
    Function<Integer, Integer> p1 = proj1 (p, 2);          // p1(x) = 2 - x
    System.out.println (p1.apply (8));                     // 2 - 8 = -6
    Function<Integer, Integer> p2 = proj2 (p, 1);          // p2(x) = x - 1
    System.out.println (p2.apply (9));                     // 9 - 1 = 8
    System.out.println (combine (p1, p2).apply (4));       // ([2-x][x-1])(4) = [2-x]([x-1](4)) = [2-x](3) = 2-3 = -1
    System.out.println (sCombine (p, p1).apply (11));      // [x - [2-x] ](11) = 11-(2-11) = 11--9 = 20
    System.out.println (sBiCombine (p, q).apply (17, 29)); // [x - max(x,y)](17,29) = 17-29 = -12
    // System.out.println (sBiCombine (J8example9::sBiCombine, J8example9::sBiCombine).apply (17, 29));
  } 
  
  public static int minus (int a1, int a2) {
    System.out.println ("("+a1+"-"+a2+") ");
    return a1-a2;
  }
  
  public static <T, U> Function<U, U> proj1 (BiFunction<T, U, U> b, T arg) {
    System.out.println ("(" + b + " in proj1 applied to " + arg);
    return y -> b.apply (arg, y);
  }
  
  public static <T, U> Function<T, T> proj2 (BiFunction<T, U, T> b, U arg) {
    System.out.println ("(" + b + " in proj2 applied to " + arg);
    return x -> b.apply (x, arg);
  }
  
  public static <T, U> Function<T, U> combine (Function<U, U> f, Function<T, U> g) {
    return x -> f.apply (g.apply (x));
  }
  
  public static <T, U, V> Function<T, V> sCombine (BiFunction<T, U, V> f, Function<T, U> g) {
    return x -> f.apply (x, g.apply (x));
  }
  
  public static <T, U, V> BiFunction<T, U, V> sBiCombine (BiFunction<T, U, V> f, BiFunction<T, U, U> g) {
    return (x,  y) -> f.apply (x, g.apply (x, y));
  }

}
