// Shapes.java — inheritance, abstract classes, interfaces and polymorphism.
import java.util.List;

public class Shapes {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Circle(1), new Rectangle(2, 3), new Square(2));

        double total = 0;
        for (Shape s : shapes) {          // one loop, many shapes: polymorphism
            System.out.println(s.label());
            total += s.area();
        }
        System.out.printf("Total area: %.2f%n", total);

        Shape s = shapes.get(2);
        if (s instanceof Square sq) {     // pattern matching for instanceof (Java 16+)
            System.out.println("Found a square: " + sq.width);
        }
    }
}

interface Describable {
    String describe();

    // Default methods give interfaces shared behaviour (Java 8+)
    default String label() {
        return "[" + describe() + "]";
    }
}

abstract class Shape implements Describable {
    abstract double area();

    @Override
    public String describe() {
        return getClass().getSimpleName() + " with area " + String.format("%.2f", area());
    }
}

class Circle extends Shape {
    private final double radius;
    Circle(double radius) { this.radius = radius; }

    @Override
    double area() { return Math.PI * radius * radius; }
}

class Rectangle extends Shape {
    protected final double width, height;
    Rectangle(double width, double height) { this.width = width; this.height = height; }

    @Override
    double area() { return width * height; }
}

class Square extends Rectangle {
    Square(double side) { super(side, side); }   // call the parent constructor

    @Override
    public String describe() {
        return "Square " + width + "x" + width + " -> " + super.describe();
    }
}
