import java.io.*;
import java.lang.reflect.Constructor;
import java.util.*;

/** Enums in depth: the generated methods, fields, constant-specific bodies, switch, EnumMap/EnumSet, singleton, and what reflection cannot do. */
public class EnumInDepth {
    enum Level { LOW, MEDIUM, HIGH }

    enum Planet {
        MERCURY(3.303e+23, 2.4397e6), EARTH(5.976e+24, 6.37814e6);
        final double mass, radius;
        Planet(double mass, double radius) { this.mass = mass; this.radius = radius; }
        double surfaceGravity() { return 6.67300E-11 * mass / (radius * radius); }
    }

    enum Op {
        ADD("+") { int apply(int a, int b) { return a + b; } },
        MUL("*") { int apply(int a, int b) { return a * b; } };
        final String sym;
        Op(String sym) { this.sym = sym; }
        abstract int apply(int a, int b);
    }

    enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

    enum Registry { INSTANCE; private int hits; int hit() { return ++hits; } }

    public static void main(String[] args) throws Exception {
        System.out.println("== 1. generated methods ==");
        System.out.println("values()      : " + Arrays.toString(Level.values()));
        System.out.println("valueOf(HIGH) : " + Level.valueOf("HIGH") + ", ordinal=" + Level.HIGH.ordinal() + ", name=" + Level.HIGH.name());
        System.out.println("compareTo     : " + Level.LOW.compareTo(Level.HIGH) + " (ordinal difference)");
        try { Level.valueOf("high"); } catch (IllegalArgumentException e) { System.out.println("valueOf(\"high\") : " + e.getMessage()); }
        System.out.println("== is a singleton per constant: " + (Level.valueOf("LOW") == Level.LOW));

        System.out.println("\n== 2. fields and constructor ==");
        System.out.printf("Earth surface gravity = %.2f m/s^2, Mercury = %.2f m/s^2%n", Planet.EARTH.surfaceGravity(), Planet.MERCURY.surfaceGravity());

        System.out.println("\n== 3. constant-specific bodies ==");
        for (Op op : Op.values()) System.out.println("3 " + op.sym + " 4 = " + op.apply(3, 4) + "   getClass=" + op.getClass().getName() + "  getDeclaringClass=" + op.getDeclaringClass().getName());

        System.out.println("\n== 4. switch ==");
        for (Day d : new Day[] { Day.SAT, Day.WED }) {
            String kind = switch (d) { case SAT, SUN -> "weekend"; case MON, TUE, WED, THU, FRI -> "weekday"; };
            System.out.println(d + " is a " + kind);
        }

        System.out.println("\n== 5. EnumMap and EnumSet ==");
        EnumMap<Day, String> plan = new EnumMap<>(Day.class);
        plan.put(Day.FRI, "deploy"); plan.put(Day.MON, "standup"); plan.put(Day.WED, "review");
        System.out.println("EnumMap order (by ordinal, not insertion): " + plan);
        System.out.println("range(TUE, THU)    : " + EnumSet.range(Day.TUE, Day.THU));
        System.out.println("complementOf(weekend): " + EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN)));
        System.out.println("noneOf / allOf     : " + EnumSet.noneOf(Day.class) + " / size " + EnumSet.allOf(Day.class).size());

        System.out.println("\n== 6. enum singleton ==");
        Registry.INSTANCE.hit(); Registry.INSTANCE.hit();
        System.out.println("hits via INSTANCE : " + Registry.INSTANCE.hit());
        Constructor<?> ctor = Registry.class.getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        try { ctor.newInstance("COPY", 1); } catch (Exception e) { System.out.println("reflection       : " + e.getClass().getSimpleName() + ": " + e.getMessage()); }
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(Registry.INSTANCE); }
        Object back = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())).readObject();
        System.out.println("serialization    : same instance after round trip = " + (back == Registry.INSTANCE));
    }
}
