/**
 * 依据《Head First 设计模式》第 4 章结构编写的简化教学示例，不是原书源码全文。
 * 原料抽象工厂负责创建同一地区的产品族；披萨店的工厂方法负责选择披萨类型。
 * 所有辅助类型均嵌套在本类中，可以独立编译，也不会与旧示例的同名类冲突。
 */
public class AbstractFactoryPizzaDemo {

    public interface Ingredient {
        String name();
    }

    // 六种独立的原料类型：共同的 name() 仅用于展示，不替代类型约束。
    public interface Dough extends Ingredient { }
    public interface Sauce extends Ingredient { }
    public interface Cheese extends Ingredient { }
    public interface Veggies extends Ingredient { }
    public interface Pepperoni extends Ingredient { }
    public interface Clams extends Ingredient { }

    public abstract static class NamedIngredient implements Ingredient {
        private final String name;

        protected NamedIngredient(String name) {
            this.name = name;
        }

        @Override
        public final String name() {
            return name;
        }
    }

    public static final class ThinCrustDough extends NamedIngredient implements Dough {
        public ThinCrustDough() { super("Thin Crust Dough"); }
    }

    public static final class ThickCrustDough extends NamedIngredient implements Dough {
        public ThickCrustDough() { super("Thick Crust Dough"); }
    }

    public static final class MarinaraSauce extends NamedIngredient implements Sauce {
        public MarinaraSauce() { super("Marinara Sauce"); }
    }

    public static final class PlumTomatoSauce extends NamedIngredient implements Sauce {
        public PlumTomatoSauce() { super("Plum Tomato Sauce"); }
    }

    public static final class ReggianoCheese extends NamedIngredient implements Cheese {
        public ReggianoCheese() { super("Reggiano Cheese"); }
    }

    public static final class MozzarellaCheese extends NamedIngredient implements Cheese {
        public MozzarellaCheese() { super("Mozzarella Cheese"); }
    }

    public static final class FreshClams extends NamedIngredient implements Clams {
        public FreshClams() { super("Fresh Clams"); }
    }

    public static final class FrozenClams extends NamedIngredient implements Clams {
        public FrozenClams() { super("Frozen Clams"); }
    }

    public static final class Garlic extends NamedIngredient implements Veggies {
        public Garlic() { super("Garlic"); }
    }

    public static final class Onion extends NamedIngredient implements Veggies {
        public Onion() { super("Onion"); }
    }

    public static final class Mushroom extends NamedIngredient implements Veggies {
        public Mushroom() { super("Mushroom"); }
    }

    public static final class RedPepper extends NamedIngredient implements Veggies {
        public RedPepper() { super("Red Pepper"); }
    }

    public static final class BlackOlives extends NamedIngredient implements Veggies {
        public BlackOlives() { super("Black Olives"); }
    }

    public static final class Spinach extends NamedIngredient implements Veggies {
        public Spinach() { super("Spinach"); }
    }

    public static final class Eggplant extends NamedIngredient implements Veggies {
        public Eggplant() { super("Eggplant"); }
    }

    public static final class SlicedPepperoni extends NamedIngredient implements Pepperoni {
        public SlicedPepperoni() { super("Sliced Pepperoni"); }
    }

    public interface PizzaIngredientFactory {
        Dough createDough();
        Sauce createSauce();
        Cheese createCheese();
        Veggies[] createVeggies();
        Pepperoni createPepperoni();
        Clams createClam();
    }

    public static final class NYPizzaIngredientFactory implements PizzaIngredientFactory {
        @Override
        public Dough createDough() { return new ThinCrustDough(); }

        @Override
        public Sauce createSauce() { return new MarinaraSauce(); }

        @Override
        public Cheese createCheese() { return new ReggianoCheese(); }

        @Override
        public Veggies[] createVeggies() {
            return new Veggies[] { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        }

        @Override
        public Pepperoni createPepperoni() { return new SlicedPepperoni(); }

        @Override
        public Clams createClam() { return new FreshClams(); }
    }

    public static final class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
        @Override
        public Dough createDough() { return new ThickCrustDough(); }

        @Override
        public Sauce createSauce() { return new PlumTomatoSauce(); }

        @Override
        public Cheese createCheese() { return new MozzarellaCheese(); }

        @Override
        public Veggies[] createVeggies() {
            return new Veggies[] { new BlackOlives(), new Spinach(), new Eggplant() };
        }

        @Override
        public Pepperoni createPepperoni() { return new SlicedPepperoni(); }

        @Override
        public Clams createClam() { return new FrozenClams(); }
    }

    public abstract static class Pizza {
        protected String name;
        protected Dough dough;
        protected Sauce sauce;
        protected Cheese cheese;
        protected Veggies[] veggies;
        protected Pepperoni pepperoni;
        protected Clams clams;

        protected Pizza(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public abstract void prepare();

        public void bake() {
            System.out.println("Bake for 25 minutes at 350 F");
        }

        public void cut() {
            System.out.println("Cut into diagonal slices");
        }

        public void box() {
            System.out.println("Box in the official PizzaStore box");
        }

        protected void printIngredients() {
            System.out.println("  Dough: " + dough.name());
            System.out.println("  Sauce: " + sauce.name());
            System.out.println("  Cheese: " + cheese.name());
            if (clams != null) {
                System.out.println("  Clams: " + clams.name());
            }
        }
    }

    public static final class CheesePizza extends Pizza {
        private final PizzaIngredientFactory ingredientFactory;

        public CheesePizza(PizzaIngredientFactory ingredientFactory) {
            super("Cheese Pizza");
            this.ingredientFactory = java.util.Objects.requireNonNull(ingredientFactory);
        }

        @Override
        public void prepare() {
            System.out.println("Prepare " + name);
            dough = ingredientFactory.createDough();
            sauce = ingredientFactory.createSauce();
            cheese = ingredientFactory.createCheese();
            printIngredients();
        }
    }

    public static final class ClamPizza extends Pizza {
        private final PizzaIngredientFactory ingredientFactory;

        public ClamPizza(PizzaIngredientFactory ingredientFactory) {
            super("Clam Pizza");
            this.ingredientFactory = java.util.Objects.requireNonNull(ingredientFactory);
        }

        @Override
        public void prepare() {
            System.out.println("Prepare " + name);
            dough = ingredientFactory.createDough();
            sauce = ingredientFactory.createSauce();
            cheese = ingredientFactory.createCheese();
            clams = ingredientFactory.createClam();
            printIngredients();
        }
    }

    public abstract static class PizzaStore {
        // 固定订单流程；具体门店通过工厂方法决定创建哪一种披萨。
        public final Pizza orderPizza(String type) {
            Pizza pizza = createPizza(type);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }

        protected abstract Pizza createPizza(String type);
    }

    public static final class NYPizzaStore extends PizzaStore {
        @Override
        protected Pizza createPizza(String type) {
            PizzaIngredientFactory factory = new NYPizzaIngredientFactory();
            Pizza pizza;
            if ("cheese".equals(type)) {
                pizza = new CheesePizza(factory);
            } else if ("clam".equals(type)) {
                pizza = new ClamPizza(factory);
            } else {
                throw new IllegalArgumentException("Unsupported pizza type: " + type
                        + "; expected cheese or clam");
            }
            pizza.name = "New York Style " + pizza.getName();
            return pizza;
        }
    }

    public static final class ChicagoPizzaStore extends PizzaStore {
        @Override
        protected Pizza createPizza(String type) {
            PizzaIngredientFactory factory = new ChicagoPizzaIngredientFactory();
            Pizza pizza;
            if ("cheese".equals(type)) {
                pizza = new CheesePizza(factory);
            } else if ("clam".equals(type)) {
                pizza = new ClamPizza(factory);
            } else {
                throw new IllegalArgumentException("Unsupported pizza type: " + type
                        + "; expected cheese or clam");
            }
            pizza.name = "Chicago Style " + pizza.getName();
            return pizza;
        }
    }

    public static void main(String[] args) {
        PizzaStore[] stores = { new NYPizzaStore(), new ChicagoPizzaStore() };
        String[] types = { "cheese", "clam" };
        for (PizzaStore store : stores) {
            for (String type : types) {
                System.out.println("=== " + store.getClass().getSimpleName() + " / " + type + " ===");
                Pizza pizza = store.orderPizza(type);
                System.out.println("Ready: " + pizza.getName());
                System.out.println();
            }
        }
    }
}
