/*
 * @test /nodynamiccopyright/
 * @bug 8206986 8222169 8224031 8240964 8267119 8268670 8321582
 * @summary Check expression switch works.
 * @compile ExpressionSwitch.java
 * @run main ExpressionSwitch
 */

// * @compile/fail/ref=ExpressionSwitch-old.out --release 9 -XDrawDiagnostics ExpressionSwitch.java
import java.util.Objects;
import java.util.function.Supplier;

public class ExpressionSwitch {
    public static void main(String... args) {
        new ExpressionSwitch().run();
    }

    private void run() {
        check(T.A, "A");
        check(T.B, "B");
        check(T.C, "other");
        assertEquals(exhaustive1(T.C), "C");
        assertEquals(scopesIsolated(T.B), "B");
        assertEquals(lambdas1(T.B).get(), "B");
        assertEquals(lambdas2(T.B).get(), "B");
        assertEquals(convert1("A"), 0);
        assertEquals(convert1("B"), 0);
        assertEquals(convert1("C"), 1);
        assertEquals(convert1(""), -1);
        assertEquals(convert1(null), -2);
        assertEquals(convert2("A"), 0);
        assertEquals(convert2("B"), 0);
        assertEquals(convert2("C"), 1);
        assertEquals(convert2(""), -1);
        localClass(T.A);
        assertEquals(castSwitchExpressions(T.A), "A");
        testTypeInference(true, 0);
        assertEquals(yieldPrimitiveDotClass("byte"), byte.class);
        assertEquals(yieldPrimitiveDotClass("char"), char.class);
        assertEquals(yieldPrimitiveDotClass("short"), short.class);
        assertEquals(yieldPrimitiveDotClass("int"), int.class);
        assertEquals(yieldPrimitiveDotClass("long"), long.class);
        assertEquals(yieldPrimitiveDotClass("float"), float.class);
        assertEquals(yieldPrimitiveDotClass("double"), double.class);
        assertEquals(yieldPrimitiveDotClass("void"), void.class);
        assertEquals(yieldPrimitiveDotClass("boolean"), boolean.class);
        assertEquals(yieldPrimitiveDotClass("other"), null);
    }

    private String print(T t) {
        return switch (t) {
            case A -> "A";
            case B -> { yield "B"; }
            default -> { yield "other"; }
        };
    }

    private String exhaustive1(T t) {
        return switch (t) {
            case A -> "A";
            case B -> { yield "B"; }
            case C -> "C";
            case D -> "D";
        };
    }

    private String exhaustive2(T t) {
        return switch (t) {
            case A -> "A";
            case B -> "B";
            case C -> "C";
            case D -> "D";
        };
    }

    private String scopesIsolated(T t) {
        return switch (t) {
            case A -> { String res = "A"; yield res;}
            case B -> { String res = "B"; yield res;}
            default -> { String res = "default"; yield res;}
        };
    }

    private Supplier<String> lambdas1(T t) {
        return switch (t) {
            case A -> () -> "A";
            case B -> { yield () -> "B"; }
            default -> () -> "default";
        };
    }

    private Supplier<String> lambdas2(T t) {
        return switch (t) {
            case A: yield () -> "A";
            case B: { yield () -> "B"; }
            default: yield () -> "default";
        };
    }

    private int convert1(String s) {
        return s == null
                ? -2
                : switch (s) {
                      case "A", "B" -> 0;
                      case "C" -> { yield 1; }
                      default -> -1;
                  };
    }

    private int convert2(String s) {
        return switch (s) {
            case "A", "B": yield 0;
            case "C": yield 1;
            default: yield -1;
        };
    }

    private Object yieldDisambiguationLiterals(String s) {
        return switch (s) {
            case "a": yield 0;
            case "b": yield 0L;
            case "c": yield 0.0f;
            case "d": yield 0.0d;
            case "e": yield true;
            case "f": yield false;
            case "g": yield '0';
            case "h": yield "";
            case "i": yield null;
            default: yield 0;
        };
    }

    private int yieldUnaryNumberOperator(String s, int a) {
        return switch (s) {
            case "a": yield +a;
            case "b": yield -a;
            case "c": yield ~a; // intentionally repeated ~a, test the case clause
            case "d": yield ++a;
            case "e": yield --a;
            case "f": yield a++;
            case "g": yield a--;
            default: yield ~a; // intentionally repeated ~a, test the default clause
        };
    }

    private boolean yieldUnaryNotOperator(String s, boolean b) {
        return switch (s) {
            case "a": yield !b; // intentionally repeated !b, test the case clause
            default: yield !b; // intentionally repeated !b, test the default clause
        };
    }

    private Class<?> yieldPrimitiveDotClass(String s) {
        return switch (s) {
            case "byte":    yield byte.class;
            case "char":    yield char.class;
            case "short":   yield short.class;
            case "int":     yield int.class;
            case "long":    yield long.class;
            case "float":   yield float.class;
            case "double":  yield double.class;
            case "void":    yield void.class;
            case "boolean": yield boolean.class;
            default: yield null;
        };
    }

    private void localClass(T t) {
        String good = "good";
        class L {
            public String c() {
                STOP: switch (t) {
                    default: break STOP;
                }
                return switch (t) {
                    default: yield good;
                };
            }
        }
        String result = new L().c();
        if (!Objects.equals(result, good)) {
            throw new AssertionError("Unexpected result: " + result);
        }
    }

    private String castSwitchExpressions(T t) {
        return (String) switch (t) {
            case A -> "A";
            default -> 1;
        };
    }

    private void testTypeInference(boolean b, int i) {
        m(s -> s.length(), String.class);
        m(b ? s -> s.length() : s -> s.length(), String.class);
        m(switch (i) {
            case 0 -> s -> s.length();
            default -> s -> s.length();
        }, String.class);
    }

    <Z> void m(Consumer<Z> c, Class<Z> cl) {}

    private void check(T t, String expected) {
        String result = print(t);
        assertEquals(result, expected);
    }

    private void assertEquals(Object result, Object expected) {
        if (!Objects.equals(result, expected)) {
            throw new AssertionError("Unexpected result: " + result);
        }
    }

    enum T {
        A, B, C, D;
    }
    void t() {
        Runnable r = () -> {};
        r.run();
    }

    interface Consumer<Z> {
        public void consume(Z z);
    }
}
