import java.util.*;

/**
 * Companion for "SOLID Principles in Java with Examples". Each principle: the violating design, the proof
 * that it hurts (real output), then the refactored design.   Run: java SolidDemo.java  (JDK 21+)
 */
public class SolidDemo {
    public static void main(String[] args) {
        srp();
        ocp();
        lsp();
        isp();
        dip();
    }

    // ===== S: Single Responsibility ===========================================================
    // BEFORE: three reasons to change (pricing rules, output format, storage) in one class
    static class InvoiceBefore {
        double total(List<Double> lines) { return lines.stream().mapToDouble(d -> d).sum() * 1.18; }
        String printable(List<Double> lines) { return "INVOICE total=" + total(lines); }
        void saveToDb(List<Double> lines) { /* JDBC code lives here too */ }
    }

    // AFTER: one reason to change per class
    record InvoiceCalculator(double taxRate) {
        double total(List<Double> lines) { return lines.stream().mapToDouble(d -> d).sum() * (1 + taxRate); }
    }
    static class InvoiceFormatter {
        String format(double total) { return String.format("INVOICE total=%.2f", total); }
    }
    interface InvoiceStore { void save(double total); }

    static void srp() {
        System.out.println("[S] single responsibility");
        List<Double> lines = List.of(100.0, 50.0);
        System.out.println("    before: " + new InvoiceBefore().printable(lines)
                + "   <- tax rule, format and storage all change this class");
        double total = new InvoiceCalculator(0.18).total(lines);
        System.out.println("    after : " + new InvoiceFormatter().format(total)
                + "   <- a new tax rate touches only InvoiceCalculator");
    }

    // ===== O: Open/Closed ===============================================================================
    // BEFORE: every new customer type edits this method
    static double discountBefore(String type, double amount) {
        if (type.equals("REGULAR")) return amount * 0.0;
        else if (type.equals("PREMIUM")) return amount * 0.10;
        else throw new IllegalArgumentException("unknown type " + type);
    }

    // AFTER: new types are new classes; existing code is never edited
    interface DiscountPolicy { double discount(double amount); }
    record RegularDiscount() implements DiscountPolicy { public double discount(double a) { return 0; } }
    record PremiumDiscount() implements DiscountPolicy { public double discount(double a) { return a * 0.10; } }
    record StudentDiscount() implements DiscountPolicy { public double discount(double a) { return a * 0.25; } }  // added later

    static void ocp() {
        System.out.println("[O] open/closed");
        System.out.println("    before: PREMIUM on 200 = " + discountBefore("PREMIUM", 200));
        try { discountBefore("STUDENT", 200); }
        catch (IllegalArgumentException e) { System.out.println("    before: STUDENT fails -> " + e.getMessage() + " (must edit the method)"); }
        List<DiscountPolicy> policies = List.of(new RegularDiscount(), new PremiumDiscount(), new StudentDiscount());
        for (DiscountPolicy p : policies)
            System.out.println("    after : " + p.getClass().getSimpleName() + " on 200 = " + p.discount(200));
    }

    // ===== L: Liskov Substitution ==========================================================================
    static class Rectangle {
        protected int w, h;
        void setWidth(int w) { this.w = w; }
        void setHeight(int h) { this.h = h; }
        int area() { return w * h; }
    }
    static class Square extends Rectangle {              // "is-a" in geometry, not in behaviour
        @Override void setWidth(int w) { this.w = w; this.h = w; }
        @Override void setHeight(int h) { this.w = h; this.h = h; }
    }
    static int stretch(Rectangle r) { r.setWidth(5); r.setHeight(4); return r.area(); }

    sealed interface Shape permits Rect, Sq {  int area(); }
    record Rect(int w, int h) implements Shape { public int area() { return w * h; } }
    record Sq(int side) implements Shape { public int area() { return side * side; } }

    static void lsp() {
        System.out.println("[L] liskov substitution");
        System.out.println("    Rectangle: width 5, height 4 -> area " + stretch(new Rectangle()) + " (expected 20)");
        System.out.println("    Square   : width 5, height 4 -> area " + stretch(new Square()) + " (expected 20)  <- caller's contract broken");
        System.out.println("    after: immutable Rect(5,4).area()=" + new Rect(5, 4).area() + ", Sq(4).area()=" + new Sq(4).area());
    }

    // ===== I: Interface Segregation ===================================================================================
    interface FatWorker { void work(); void eat(); }
    static class Robot implements FatWorker {
        public void work() { /* ok */ }
        public void eat() { throw new UnsupportedOperationException("robots do not eat"); }
    }
    interface Workable { void work(); }
    interface Eatable { void eat(); }
    static class Human implements Workable, Eatable { public void work() {} public void eat() {} }
    static class Robot2 implements Workable { public void work() {} }

    static void isp() {
        System.out.println("[I] interface segregation");
        try { new Robot().eat(); }
        catch (UnsupportedOperationException e) { System.out.println("    before: Robot.eat() -> " + e.getMessage() + " (forced to implement it)"); }
        List<Workable> crew = List.of(new Human(), new Robot2());
        System.out.println("    after : " + crew.size() + " Workable implementations, Robot2 never mentions eating");
    }

    // ===== D: Dependency Inversion =======================================================================================
    static class MySqlOrderRepo { String save(String o) { throw new IllegalStateException("no database in this demo"); } }
    static class OrderServiceBefore {
        private final MySqlOrderRepo repo = new MySqlOrderRepo();     // hard-wired concrete class
        String place(String o) { return repo.save(o); }
    }

    interface OrderRepo { String save(String order); }
    static class OrderService {
        private final OrderRepo repo;
        OrderService(OrderRepo repo) { this.repo = repo; }               // injected abstraction
        String place(String o) { return repo.save(o); }
    }
    static class InMemoryOrderRepo implements OrderRepo {
        final List<String> rows = new ArrayList<>();
        public String save(String o) { rows.add(o); return "saved " + o + " (row " + rows.size() + ")"; }
    }

    static void dip() {
        System.out.println("[D] dependency inversion");
        try { new OrderServiceBefore().place("order-1"); }
        catch (IllegalStateException e) { System.out.println("    before: can't test without the database -> " + e.getMessage()); }
        System.out.println("    after : " + new OrderService(new InMemoryOrderRepo()).place("order-1"));
    }
}
