import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public final class CollectionsStreamLab {
    private CollectionsStreamLab() {
    }

    record Order(String id, String customer, int cents) {
    }

    public static void main(String[] args) {
        String mode = args.length == 0 ? "normal" : args[0];
        switch (mode) {
            case "normal" -> normal();
            case "cme" -> concurrentModification();
            case "duplicate-key" -> duplicateKey();
            case "reuse-stream" -> reuseStream();
            case "bad-reduction" -> badReduction();
            default -> {
                System.err.println("unknown-mode=" + mode);
                System.exit(64);
            }
        }
    }

    private static void normal() {
        var states = List.of("CREATED", "PAID", "CREATED", "SHIPPED");
        var firstSeen = new LinkedHashSet<>(states);
        System.out.println("deduplicated=" + firstSeen);

        var source = new ArrayList<>(List.of("CREATED", "PAID"));
        var view = Collections.unmodifiableList(source);
        var snapshot = List.copyOf(source);
        source.add("SHIPPED");
        System.out.printf("ownership=source:%d view:%d snapshot:%d%n",
                source.size(), view.size(), snapshot.size());

        var mutable = new ArrayList<>(List.of("CREATED", "CANCELLED", "PAID"));
        for (Iterator<String> cursor = mutable.iterator(); cursor.hasNext();) {
            if (cursor.next().equals("CANCELLED")) {
                cursor.remove();
            }
        }
        System.out.println("iterator-remove=" + mutable);

        var mapCalls = new AtomicInteger();
        Stream<String> pipeline = states.stream()
                .filter(state -> !state.equals("CREATED"))
                .map(state -> {
                    mapCalls.incrementAndGet();
                    return state.toLowerCase();
                });
        System.out.println("lazy-before-terminal=" + mapCalls.get());
        List<String> terminalResult = pipeline.toList();
        System.out.printf("lazy-after-terminal=%d result=%s%n",
                mapCalls.get(), terminalResult);

        var orders = List.of(
                new Order("o-1", "alice", 1200),
                new Order("o-2", "bob", 800),
                new Order("o-3", "alice", 300));
        Map<String, Integer> spendByCustomer = orders.stream().collect(
                Collectors.toMap(
                        Order::customer,
                        Order::cents,
                        Integer::sum,
                        LinkedHashMap::new));
        System.out.println("merged-by-customer=" + spendByCustomer);

        ConcurrentMap<String, Integer> counts = new ConcurrentHashMap<>();
        states.forEach(state -> counts.merge(state, 1, Integer::sum));
        System.out.printf("atomic-merge=CREATED:%d PAID:%d SHIPPED:%d%n",
                counts.get("CREATED"), counts.get("PAID"), counts.get("SHIPPED"));

        int characteristics = firstSeen.spliterator().characteristics();
        boolean ordered = (characteristics & Spliterator.ORDERED) != 0;
        boolean distinct = (characteristics & Spliterator.DISTINCT) != 0;
        System.out.printf("spliterator=ORDERED:%s DISTINCT:%s%n", ordered, distinct);

        long sequential = IntStream.rangeClosed(1, 10_000).asLongStream().sum();
        long parallel = IntStream.rangeClosed(1, 10_000).parallel().asLongStream().sum();
        System.out.printf("associative-sum=sequential:%d parallel:%d%n",
                sequential, parallel);
    }

    private static void concurrentModification() {
        var values = new ArrayList<>(List.of("a", "b", "c", "d"));
        try {
            for (String ignored : values) {
                values.remove("d");
            }
            System.err.println("mode=cme unexpected=no-exception");
            System.exit(70);
        } catch (ConcurrentModificationException error) {
            System.err.println("mode=cme exception=" + error.getClass().getSimpleName());
            System.exit(2);
        }
    }

    private static void duplicateKey() {
        var orders = List.of(
                new Order("o-1", "alice", 1200),
                new Order("o-1", "alice", 300));
        try {
            orders.stream().collect(Collectors.toMap(Order::id, Function.identity()));
            System.err.println("mode=duplicate-key unexpected=no-exception");
            System.exit(70);
        } catch (IllegalStateException error) {
            System.err.println("mode=duplicate-key exception="
                    + error.getClass().getSimpleName());
            System.exit(3);
        }
    }

    private static void reuseStream() {
        Stream<String> stream = Stream.of("CREATED", "PAID");
        long firstCount = stream.count();
        try {
            stream.count();
            System.err.println("mode=reuse-stream unexpected=no-exception");
            System.exit(70);
        } catch (IllegalStateException error) {
            System.err.printf("mode=reuse-stream first-count=%d exception=%s%n",
                    firstCount, error.getClass().getSimpleName());
            System.exit(4);
        }
    }

    private static void badReduction() {
        int a = 20;
        int b = 5;
        int c = 2;
        int leftGrouped = (a - b) - c;
        int rightGrouped = a - (b - c);
        int sequential = IntStream.rangeClosed(1, 12).boxed()
                .reduce(0, (left, right) -> left - right);
        int parallel = IntStream.rangeClosed(1, 12).parallel().boxed()
                .reduce(0, (left, right) -> left - right);
        System.err.printf(
                "mode=bad-reduction associative=false left=%d right=%d sequential=%d parallel=%d%n",
                leftGrouped, rightGrouped, sequential, parallel);
        System.exit(5);
    }
}
