/*
 * Design a Bird
 *
 * Problem
 *   Model Pigeon, Crow, Sparrow, Ostrich, Penguin.
 *   Penguin and Ostrich cannot fly.
 *   Crow and Sparrow fly the same way. Pigeon flies differently.
 *   Every bird eats and sings the same way.
 *
 * These are different ways people first design this. Not all of them work.
 *
 *   1. One Bird class + if (sparrow) / else if (penguin) in fly()
 *      Breaks SRP and OCP. Unreadable at 20 species. Tests pile into
 *      one file. Two people collide on the same class.
 *
 *   2. Abstract Bird with fly() on the parent
 *      Penguin then "implements" fly with an empty body, a log, or
 *      an exception. Callers still think every Bird can fly. Breaks LSP.
 *
 *   3. FlyableBird / NonFlyableBird extra parent classes
 *      Fine for one behavior. Add eat variants and the tree explodes
 *      into 2^n classes. Flying and eating are behaviors, not kinds of bird.
 *
 *   4. The design in this file (the one that works)
 *      Bird holds identity + eat/sing. No fly() on Bird.
 *      Only flyers implement Flyable (ISP + LSP).
 *      Flying style is a separate class, injected in the constructor
 *      (Strategy + DIP + DI).
 *      Crow and Sparrow share CrowSparrowFlyingBehavior.
 *      A new bird is a new class. If it flies like Crow, reuse that
 *      behavior. Do not edit old species (OCP).
 *
 * Usage
 *   List<Bird>     → describe / eat / sing for everyone
 *   List<Flyable>  → fly() with no instanceof and no try/catch
 */

import java.util.ArrayList;
import java.util.List;

/* Small interfaces so a bird only takes on behaviors it actually has. */

interface Flyable {
    void fly();
}

interface FlyingBehavior {
    void makeFly();
}

interface EatingBehavior {
    void eat();
}

interface SingingBehavior {
    void sing();
}

/* Flying styles sit in their own classes so birds stay small. */

class CrowSparrowFlyingBehavior implements FlyingBehavior {
    @Override
    public void makeFly() {
        System.out.println("  flying with short rapid wing flaps");
    }
}

class PigeonFlyingBehavior implements FlyingBehavior {
    @Override
    public void makeFly() {
        System.out.println("  flying in a steady glide with occasional flaps");
    }
}

class DefaultEatingBehavior implements EatingBehavior {
    @Override
    public void eat() {
        System.out.println("  pecking food");
    }
}

class DefaultSingingBehavior implements SingingBehavior {
    @Override
    public void sing() {
        System.out.println("  singing");
    }
}

/*
 * Shared name / color / size / weight, plus eat and sing.
 * fly() is not here — Penguin and Ostrich would otherwise have to fake it.
 */
abstract class Bird {
    private final String name;
    private final String color;
    private final String size;
    private final double weightKg;
    private final EatingBehavior eating;
    private final SingingBehavior singing;

    protected Bird(String name, String color, String size, double weightKg,
                   EatingBehavior eating, SingingBehavior singing) {
        this.name = name;
        this.color = color;
        this.size = size;
        this.weightKg = weightKg;
        this.eating = eating;
        this.singing = singing;
    }

    public String getName() { return name; }

    public void eat() { eating.eat(); }
    public void sing() { singing.sing(); }

    public void describe() {
        System.out.println(name + " [" + color + ", " + size + ", " + weightKg + "kg]");
    }
}

/* Only species that fly implement Flyable. */

class Pigeon extends Bird implements Flyable {
    private final FlyingBehavior flying;

    public Pigeon(String color, String size, double weightKg,
                  EatingBehavior eating, SingingBehavior singing,
                  FlyingBehavior flying) {
        super("Pigeon", color, size, weightKg, eating, singing);
        this.flying = flying;
    }

    @Override
    public void fly() {
        System.out.print(getName());
        flying.makeFly();
    }
}

class Crow extends Bird implements Flyable {
    private final FlyingBehavior flying;

    public Crow(String color, String size, double weightKg,
                EatingBehavior eating, SingingBehavior singing,
                FlyingBehavior flying) {
        super("Crow", color, size, weightKg, eating, singing);
        this.flying = flying;
    }

    @Override
    public void fly() {
        System.out.print(getName());
        flying.makeFly();
    }
}

class Sparrow extends Bird implements Flyable {
    private final FlyingBehavior flying;

    public Sparrow(String color, String size, double weightKg,
                   EatingBehavior eating, SingingBehavior singing,
                   FlyingBehavior flying) {
        super("Sparrow", color, size, weightKg, eating, singing);
        this.flying = flying;
    }

    @Override
    public void fly() {
        System.out.print(getName());
        flying.makeFly();
    }
}

class Ostrich extends Bird {
    public Ostrich(String color, String size, double weightKg,
                   EatingBehavior eating, SingingBehavior singing) {
        super("Ostrich", color, size, weightKg, eating, singing);
    }
}

class Penguin extends Bird {
    public Penguin(String color, String size, double weightKg,
                   EatingBehavior eating, SingingBehavior singing) {
        super("Penguin", color, size, weightKg, eating, singing);
    }
}

/* Main creates the behaviors and hands them to each bird. */
public class Main {
    public static void main(String[] args) {
        EatingBehavior eat = new DefaultEatingBehavior();
        SingingBehavior sing = new DefaultSingingBehavior();
        FlyingBehavior crowSparrowFly = new CrowSparrowFlyingBehavior();
        FlyingBehavior pigeonFly = new PigeonFlyingBehavior();

        Pigeon pigeon = new Pigeon("grey", "medium", 0.3, eat, sing, pigeonFly);
        Crow crow = new Crow("black", "medium", 0.5, eat, sing, crowSparrowFly);
        Sparrow sparrow = new Sparrow("brown", "small", 0.03, eat, sing, crowSparrowFly);
        Ostrich ostrich = new Ostrich("brown", "large", 110.0, eat, sing);
        Penguin penguin = new Penguin("black-white", "medium", 15.0, eat, sing);

        List<Bird> sanctuary = new ArrayList<Bird>();
        sanctuary.add(pigeon);
        sanctuary.add(crow);
        sanctuary.add(sparrow);
        sanctuary.add(ostrich);
        sanctuary.add(penguin);

        System.out.println("=== all birds eat + sing ===");
        for (Bird bird : sanctuary) {
            bird.describe();
            bird.eat();
            bird.sing();
        }

        System.out.println();
        System.out.println("=== only Flyable birds fly ===");
        List<Flyable> flyers = new ArrayList<Flyable>();
        flyers.add(pigeon);
        flyers.add(crow);
        flyers.add(sparrow);
        for (Flyable flyer : flyers) {
            flyer.fly();
        }

        System.out.println();
        System.out.println("Crow and Sparrow share CrowSparrowFlyingBehavior");
        System.out.println("Pigeon uses PigeonFlyingBehavior");
        System.out.println("Ostrich and Penguin are Birds, not Flyable");
    }
}