// Lambdas.java — lambda syntax, sorting with comparators, lambdas as arguments.
import java.util.*;
import java.util.function.*;

public class Lambdas {

    record Order(String id, String customer, double amount) { }

    // A method that accepts behaviour as a parameter
    static List<Order> filter(List<Order> orders, Predicate<Order> rule) {
        List<Order> result = new ArrayList<>();
        for (Order o : orders) {
            if (rule.test(o)) result.add(o);
        }
        return result;
    }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("PO-1", "Acme", 250.0),
                new Order("PO-2", "Globex", 90.0),
                new Order("PO-3", "Acme", 1200.0));

        // 1. The old way: an anonymous class
        Comparator<Order> byAmountOld = new Comparator<>() {
            @Override
            public int compare(Order a, Order b) {
                return Double.compare(a.amount(), b.amount());
            }
        };

        // 2. The same thing as a lambda
        Comparator<Order> byAmount = (a, b) -> Double.compare(a.amount(), b.amount());

        // 3. Even shorter with a comparator factory
        Comparator<Order> byAmountShort = Comparator.comparingDouble(Order::amount);

        List<Order> sorted = new ArrayList<>(orders);
        sorted.sort(byAmountShort.reversed());
        sorted.forEach(o -> System.out.println(o.id() + " " + o.amount()));
        System.out.println(byAmountOld.compare(orders.get(0), orders.get(1)) == byAmount.compare(orders.get(0), orders.get(1)));

        // Lambda syntax variations
        Runnable hello = () -> System.out.println("No parameters");
        Function<Integer, Integer> square = x -> x * x;                 // one parameter
        BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;     // two parameters
        Function<String, String> describe = s -> {                       // block body
            String trimmed = s.trim();
            return trimmed.isEmpty() ? "(empty)" : trimmed;
        };
        hello.run();
        System.out.println(square.apply(7) + " " + add.apply(2, 3) + " " + describe.apply("   "));

        // Passing behaviour into our own method
        List<Order> big = filter(orders, o -> o.amount() > 200);
        List<Order> acme = filter(orders, o -> o.customer().equals("Acme"));
        System.out.println("Big: " + big.size() + ", Acme: " + acme.size());

        // Collections methods that take lambdas
        Map<String, Double> totals = new TreeMap<>();
        orders.forEach(o -> totals.merge(o.customer(), o.amount(), Double::sum));
        System.out.println(totals);                                      // {Acme=1450.0, Globex=90.0}

        List<String> ids = new ArrayList<>(List.of("po-3", "po-1"));
        ids.replaceAll(String::toUpperCase);
        System.out.println(ids);                                         // [PO-3, PO-1]
    }
}
