import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Gatherers;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

/** 50 Java Stream API interview questions. Every answer is executed; run with: java StreamInterviewQuestions.java */
public class StreamInterviewQuestions {

    record Employee(String name, String dept, double salary, int age) {}

    static final List<Employee> EMP = List.of(
            new Employee("Asha", "Eng", 120000, 30), new Employee("Bilal", "Eng", 95000, 28),
            new Employee("Chitra", "Eng", 130000, 41), new Employee("Dev", "Ops", 70000, 35),
            new Employee("Esha", "Ops", 82000, 29), new Employee("Farid", "HR", 64000, 45),
            new Employee("Gita", "HR", 71000, 38), new Employee("Hari", "Sales", 90000, 33),
            new Employee("Indu", "Sales", 90000, 27), new Employee("Jay", "Sales", 60000, 24));
    static final List<Integer> NUMS = List.of(5, 3, 9, 1, 9, 7, 3, 8, 2, 8);
    static final String SENTENCE = "the quick brown fox jumps over the lazy dog the end";

    static void say(int n, String title, Object result) {
        System.out.printf("Q%02d %s -> %s%n", n, title, result);
    }

    // ---------- A. Filtering and basics ----------
    static void q01() {
        say(1, "even numbers squared", NUMS.stream().filter(n -> n % 2 == 0).map(n -> n * n).toList());
    }

    static void q02() {
        var s = NUMS.stream().mapToInt(Integer::intValue).summaryStatistics();
        say(2, "sum / average / max", s.getSum() + " / " + s.getAverage() + " / " + s.getMax());
    }

    static void q03() {
        say(3, "second highest", NUMS.stream().distinct().sorted(Comparator.reverseOrder()).skip(1).findFirst().get());
    }

    static void q04() {
        int n = 3;
        say(4, "3rd highest", NUMS.stream().distinct().sorted(Comparator.reverseOrder()).skip(n - 1).findFirst().get());
    }

    static void q05() {
        char c = "swiss".chars().mapToObj(x -> (char) x)
                .collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()))
                .entrySet().stream().filter(e -> e.getValue() == 1).map(Map.Entry::getKey).findFirst().get();
        say(5, "first non-repeated char in swiss", c);
    }

    static void q06() {
        Set<Integer> seen = new HashSet<>();
        say(6, "duplicates", NUMS.stream().filter(n -> !seen.add(n)).distinct().toList());
    }

    static void q07() {
        say(7, "remove duplicates, keep order", NUMS.stream().distinct().toList());
    }

    static void q08() {
        say(8, "allMatch / anyMatch / noneMatch",
                NUMS.stream().allMatch(n -> n > 0) + " / " + NUMS.stream().anyMatch(n -> n > 8) + " / "
                        + NUMS.stream().noneMatch(n -> n > 9));
    }

    // ---------- B. Grouping and counting ----------
    static void q09() {
        say(9, "frequency of each number",
                NUMS.stream().collect(Collectors.groupingBy(Function.identity(), TreeMap::new, Collectors.counting())));
    }

    static void q10() {
        say(10, "character frequency in banana", "banana".chars().mapToObj(c -> (char) c)
                .collect(Collectors.groupingBy(Function.identity(), TreeMap::new, Collectors.counting())));
    }

    static void q11() {
        say(11, "names grouped by department", EMP.stream().collect(Collectors.groupingBy(Employee::dept, TreeMap::new,
                Collectors.mapping(Employee::name, Collectors.toList()))));
    }

    static void q12() {
        say(12, "head count per department",
                EMP.stream().collect(Collectors.groupingBy(Employee::dept, TreeMap::new, Collectors.counting())));
    }

    static void q13() {
        say(13, "average salary per department", EMP.stream().collect(Collectors.groupingBy(Employee::dept,
                TreeMap::new, Collectors.averagingDouble(Employee::salary))));
    }

    static void q14() {
        say(14, "highest paid per department", EMP.stream().collect(Collectors.groupingBy(Employee::dept, TreeMap::new,
                Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingDouble(Employee::salary)),
                        o -> o.get().name()))));
    }

    static void q15() {
        say(15, "partition salary >= 90000", EMP.stream().collect(Collectors.partitioningBy(e -> e.salary() >= 90000,
                Collectors.mapping(Employee::name, Collectors.toList()))));
    }

    static void q16() {
        say(16, "names per department, joined", EMP.stream().collect(Collectors.groupingBy(Employee::dept, TreeMap::new,
                Collectors.mapping(Employee::name, Collectors.joining("/")))));
    }

    static Map<String, Long> wordFreq() {
        return Arrays.stream(SENTENCE.split(" ")).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    }

    static void q17() {
        say(17, "words sorted by count desc, then word", wordFreq().entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
                .map(e -> e.getKey() + "=" + e.getValue()).toList());
    }

    static void q18() {
        say(18, "top 3 most frequent words", wordFreq().entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
                .limit(3).map(e -> e.getKey() + "=" + e.getValue()).toList());
    }

    static void q19() {
        say(19, "words grouped by length", Arrays.stream(SENTENCE.split(" "))
                .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toList())));
    }

    static void q20() {
        say(20, "group anagrams", Stream.of("listen", "silent", "enlist", "google", "gogole", "cat", "act")
                .collect(Collectors.groupingBy(w -> {
                    char[] c = w.toCharArray();
                    Arrays.sort(c);
                    return new String(c);
                }, TreeMap::new, Collectors.toList())));
    }

    // ---------- C. Sorting ----------
    static void q21() {
        say(21, "salary desc, then name", EMP.stream()
                .sorted(Comparator.comparingDouble(Employee::salary).reversed().thenComparing(Employee::name))
                .map(Employee::name).toList());
    }

    static void q22() {
        say(22, "length, then alphabetical", Stream.of("pear", "fig", "apple", "kiwi", "date")
                .sorted(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder())).toList());
    }

    static void q23() {
        say(23, "map sorted by key into LinkedHashMap (first 3)", wordFreq().entrySet().stream()
                .sorted(Map.Entry.comparingByKey()).limit(3)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new)));
    }

    static void q24() {
        List<String> list = List.of("a", "b", "c", "d");
        int n = list.size();
        say(24, "reverse a list", IntStream.range(0, n).map(i -> n - 1 - i).mapToObj(list::get).toList());
    }

    static void q25() {
        say(25, "numbers in reverse order", NUMS.stream().sorted(Comparator.reverseOrder()).toList());
    }

    static void q26() {
        say(26, "top 3 / bottom 3 salaries",
                EMP.stream().map(Employee::salary).sorted(Comparator.reverseOrder()).limit(3).toList() + " / "
                        + EMP.stream().map(Employee::salary).sorted().limit(3).toList());
    }

    // ---------- D. Transforming ----------
    static void q27() {
        say(27, "flatten a list of lists",
                Stream.of(List.of(1, 2), List.of(3), List.of(4, 5, 6)).flatMap(List::stream).toList());
    }

    static void q28() {
        say(28, "words from sentences",
                Stream.of("hello world", "java streams").flatMap(s -> Arrays.stream(s.split(" "))).toList());
    }

    static void q29() {
        say(29, "unique letters across words", Stream.of("apple", "plum").flatMapToInt(String::chars)
                .mapToObj(c -> (char) c).distinct().sorted().map(String::valueOf).collect(Collectors.joining()));
    }

    static void q30() {
        say(30, "join with prefix and suffix", Stream.of("a", "b", "c").collect(Collectors.joining(", ", "[", "]")));
    }

    static void q31() {
        say(31, "toMap with a merge function", Stream.of("apple", "avocado", "banana", "blueberry", "cherry")
                .collect(Collectors.toMap(w -> w.charAt(0), w -> w, (a, b) -> a + "|" + b, TreeMap::new)));
    }

    static void q32() {
        String msg;
        try {
            Stream.of("apple", "avocado").collect(Collectors.toMap(w -> w.charAt(0), w -> w));
            msg = "no exception";
        } catch (IllegalStateException e) {
            msg = e.getMessage();
        }
        say(32, "toMap without merge function", msg);
    }

    static void q33() {
        say(33, "cartesian product",
                Stream.of("A", "B").flatMap(l -> Stream.of(1, 2).map(n -> l + n)).toList());
    }

    static void q34() {
        int[] arr = {3, 1, 2};
        int[] sortedDesc = Arrays.stream(arr).boxed().sorted(Comparator.reverseOrder()).mapToInt(Integer::intValue).toArray();
        say(34, "int[] sorted descending", Arrays.toString(sortedDesc));
    }

    // ---------- E. Numbers and ranges ----------
    static void q35() {
        say(35, "sum of digits of 9875", String.valueOf(9875).chars().map(c -> c - '0').sum());
    }

    static void q36() {
        say(36, "primes up to 30", IntStream.rangeClosed(2, 30)
                .filter(n -> IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0)).boxed().toList());
    }

    static void q37() {
        say(37, "20! with reduce", LongStream.rangeClosed(1, 20).mapToObj(BigInteger::valueOf)
                .reduce(BigInteger.ONE, BigInteger::multiply));
    }

    static void q38() {
        say(38, "first 10 Fibonacci numbers", Stream.iterate(new long[] {0, 1}, f -> new long[] {f[1], f[0] + f[1]})
                .limit(10).map(f -> f[0]).toList());
    }

    static void q39() {
        say(39, "range vs rangeClosed",
                IntStream.range(1, 5).boxed().toList() + " / " + IntStream.rangeClosed(1, 5).boxed().toList());
    }

    static void q40() {
        say(40, "min and max in one pass", NUMS.stream().collect(Collectors.teeing(
                Collectors.minBy(Integer::compare), Collectors.maxBy(Integer::compare),
                (mn, mx) -> mn.get() + ".." + mx.get())));
    }

    static void q41() {
        say(41, "numbers starting with 1", Stream.of(10, 15, 23, 1, 100, 45, 19).map(String::valueOf)
                .filter(s -> s.startsWith("1")).toList());
    }

    static boolean isPalindrome(String s) {
        return IntStream.range(0, s.length() / 2).allMatch(i -> s.charAt(i) == s.charAt(s.length() - 1 - i));
    }

    static void q42() {
        say(42, "palindrome check", "level=" + isPalindrome("level") + ", stream=" + isPalindrome("stream"));
    }

    // ---------- F. Advanced and modern ----------
    static void q43() {
        say(43, "takeWhile / dropWhile (n < 5)",
                Stream.of(1, 2, 3, 10, 4, 5).takeWhile(n -> n < 5).toList() + " / "
                        + Stream.of(1, 2, 3, 10, 4, 5).dropWhile(n -> n < 5).toList());
    }

    static void q44() {
        say(44, "longest string with reduce", Stream.of("kiwi", "banana", "fig", "cherry")
                .reduce((a, b) -> a.length() >= b.length() ? a : b).get());
    }

    static void q45() {
        say(45, "average with teeing", NUMS.stream().collect(Collectors.teeing(
                Collectors.summingInt(n -> n), Collectors.counting(), (s, c) -> (double) s / c)));
    }

    static void q46() {
        say(46, "batches of 3 (Gatherers.windowFixed)",
                IntStream.rangeClosed(1, 8).boxed().gather(Gatherers.windowFixed(3)).toList());
    }

    static void q47() {
        say(47, "moving average of 3 (Gatherers.windowSliding)", Stream.of(10, 20, 30, 40, 50)
                .gather(Gatherers.windowSliding(3))
                .map(w -> w.stream().mapToInt(Integer::intValue).average().getAsDouble()).toList());
    }

    static void q48() {
        say(48, "running total (Gatherers.scan)",
                Stream.of(1, 2, 3, 4, 5).gather(Gatherers.scan(() -> 0, Integer::sum)).toList());
    }

    static void q49() {
        List<String> log = new ArrayList<>();
        Stream.of("a", "b", "c").peek(s -> log.add("peek " + s)).map(String::toUpperCase)
                .filter(s -> s.equals("B")).findFirst();
        List<String> noTerminal = new ArrayList<>();
        Stream.of("a", "b", "c").peek(s -> noTerminal.add("peek " + s)).map(String::toUpperCase);
        say(49, "laziness: with findFirst / with no terminal op", log + " / " + noTerminal);
    }

    static void q50() {
        Stream<Integer> s = Stream.of(1, 2, 3);
        s.forEach(x -> { });
        String msg;
        try {
            s.forEach(x -> { });
            msg = "no exception";
        } catch (IllegalStateException e) {
            msg = e.getMessage();
        }
        say(50, "reusing a stream", msg);
    }

    public static void main(String[] args) {
        q01(); q02(); q03(); q04(); q05(); q06(); q07(); q08(); q09(); q10();
        q11(); q12(); q13(); q14(); q15(); q16(); q17(); q18(); q19(); q20();
        q21(); q22(); q23(); q24(); q25(); q26(); q27(); q28(); q29(); q30();
        q31(); q32(); q33(); q34(); q35(); q36(); q37(); q38(); q39(); q40();
        q41(); q42(); q43(); q44(); q45(); q46(); q47(); q48(); q49(); q50();
    }
}
