using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace BreakfastMakerSystem
{
    public class Item<T>
    {

        public string Name { get; }
        // 物品名称 (公开只读)
        // 例如: "咖啡", "鸡蛋"

        public T Spec { get; }
        // 物品规格 (泛型,公开只读)
        // 例如: 对于咖啡, T是int, Spec是容量(ml); 对于土豆, T是string, Spec是描述。

        public Item(string name, T spec)
        {
            Name = name;
            Spec = spec;
        }
        // 构造函数,用于初始化物品。
    }

    public class BreakfastMaker
    {


        #region 委托与事件定义

        public delegate Task CookingStep();
        // 定义一个委托,用于封装所有无参数、返回Task的烹饪步骤。
        // 委托就像一个方法的“模板”或“签名”,所有符合这个签名的方法都可以被它引用。
        // 返回 Task 是为了适配异步方法。

        public delegate void StepStartedHandler(string stepName);
        // 定义步骤开始事件的委托。
        // 它规定了所有订阅 StepStarted 事件的方法必须接受一个 string 参数 (步骤名) 且无返回值。

        public delegate void StepCompletedHandler(string stepName, TimeSpan duration);
        // 定义步骤完成事件的委托。
        // 它规定了所有订阅 StepCompleted 事件的方法必须接受 string (步骤名) 和 TimeSpan (耗时) 两个参数。

        public event StepStartedHandler StepStarted;
        // 步骤开始事件。
        // 外部代码可以 "订阅" (+=) 这个事件,当事件被 "触发" 时,所有订阅的方法都会被调用。
        // 这是一种典型的 "发布-订阅" 模式,实现了逻辑解耦。

        public event StepCompletedHandler StepCompleted;
        // 步骤完成事件。

        #endregion

        #region 异步制作方法
       

        public async Task PourCoffeeAsync(Item<int> coffee)
        {
            Stopwatch stopwatch = new();
            StepStarted?.Invoke("倒咖啡");
            // 触发步骤开始事件,通知所有订阅者

            await Task.Delay(500);
            // 使用 Task.Delay 模拟倒咖啡的耗时操作 (500毫秒)

            Console.WriteLine($"倒入 {coffee.Spec}ml {coffee.Name}");

            stopwatch.Stop();
            StepCompleted?.Invoke("倒咖啡", stopwatch.Elapsed);
            // 触发步骤完成事件,并传递耗时
        }
        // 异步倒咖啡

        public async Task FryEggsAsync(Item<int> eggs)
        {
            var stopwatch = Stopwatch.StartNew();
            StepStarted?.Invoke("煎鸡蛋");

            for (int i = 0; i < eggs.Spec; i++)
            {
                Console.WriteLine($"开始煎第 {i + 1} 个鸡蛋...");
                await Task.Delay(1500);
                // 模拟煎一个鸡蛋耗时1.5秒
                Console.WriteLine($"煎好第 {i + 1} 个鸡蛋");
            }
            // 根据鸡蛋数量,循环模拟煎鸡蛋的过程

            stopwatch.Stop();
            StepCompleted?.Invoke("煎鸡蛋", stopwatch.Elapsed);
        }
        // 异步煎鸡蛋

        public async Task BakeHashBrownsAsync(Item<string> potato)
        {
            var stopwatch = Stopwatch.StartNew();
            StepStarted?.Invoke("烤薯饼");

            await Task.Delay(3000);
            // 模拟烤薯饼的耗时 (3秒)

            Console.WriteLine($"用 {potato.Spec} 烤好薯饼");

            stopwatch.Stop();
            StepCompleted?.Invoke("烤薯饼", stopwatch.Elapsed);
        }
        // 异步烤薯饼

        public async Task PlateAsync()
        {
            var stopwatch = Stopwatch.StartNew();
            StepStarted?.Invoke("装盘");

            await Task.Delay(1000);
            // 模拟装盘耗时

            Console.WriteLine("将鸡蛋和薯饼装入餐盘");

            stopwatch.Stop();
            StepCompleted?.Invoke("装盘", stopwatch.Elapsed);
        }
        // 异步装盘

        public async Task PourJuiceAsync(Item<string> juice)
        {
            var stopwatch = Stopwatch.StartNew();
            StepStarted?.Invoke("倒果汁");

            await Task.Delay(800);
            // 模拟倒果汁耗时

            Console.WriteLine($"倒入 {juice.Spec}");

            stopwatch.Stop();
            StepCompleted?.Invoke("倒果汁", stopwatch.Elapsed);
        }
        // 异步倒果汁

        #endregion
    }

    class Program
    {


        static async Task Main(string[] args)
        // Main 方法标记为 async Task,因为我们需要在其中使用 await 来等待异步操作完成。
        {
            Console.WriteLine("--- 早餐制作流程管理系统启动 ---");

            var totalStopwatch = Stopwatch.StartNew();
            // 创建一个总计时器,计算整个流程的耗时

            // (1) 创建 BreakfastMaker 实例和物品实例
            var maker = new BreakfastMaker();
            var coffee = new Item<int>("咖啡", 200);
            var eggs = new Item<int>("鸡蛋", 2);
            var potato = new Item<string>("土豆", "中等大小土豆");
            var juice = new Item<string>("果汁", "苹果汁");

            // (2) 订阅事件
            maker.StepStarted += (stepName) =>
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                // Console.ForegroundColor 可以在控制台输出不同颜色的文本,便于观察
                Console.WriteLine($"[开始] {stepName}");
                Console.ResetColor();
            };

            maker.StepCompleted += (stepName, duration) =>
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"[完成] {stepName} (耗时: {duration.TotalSeconds:F1} 秒)");
                // duration.TotalSeconds: 将 TimeSpan 转换为秒,并保留小数部分。F1表示格式化为1位小数。
                Console.ResetColor();
            };
            // 使用 Lambda 表达式为事件添加处理逻辑,当事件被触发时,这些代码块会被执行。


            // (3) 组织与执行步骤
            Console.WriteLine("\n--- 开始制作早餐 ---");

            // --- 阶段 1 (并行执行) ---
            Console.WriteLine("\n--- 阶段 1: 并行准备咖啡、鸡蛋和薯饼 ---");
            Task pourCoffeeTask = maker.PourCoffeeAsync(coffee);
            Task fryEggsTask = maker.FryEggsAsync(eggs);
            Task bakeHashBrownsTask = maker.BakeHashBrownsAsync(potato);
            // 这三个步骤(倒咖啡、煎鸡蛋、烤薯饼)没有相互依赖,可以同时进行。
            // 我们为每个步骤创建一个 Task。Task在这里可以理解为一个“工作票据”,代表一个将要完成的工作。

            await Task.WhenAll(pourCoffeeTask, fryEggsTask, bakeHashBrownsTask);
            // Task.WhenAll 会等待传入的所有 Task 都完成后再继续执行。
            // 这就是实现并行执行的关键。程序不会在这里卡住,而是会“让出”线程去做其他事,
            // 等到这三个任务都完成后,再回到这里继续往下走。

            Console.WriteLine("--- 阶段 1 全部完成 ---");


            // --- 阶段 2 (顺序执行) ---
            Console.WriteLine("\n--- 阶段 2: 装盘 ---");
            await maker.PlateAsync();
            // 装盘必须在煎鸡蛋和烤薯饼之后。由于上一步的 await Task.WhenAll 保证了这两个任务已完成,
            // 所以我们可以在这里安全地执行装盘。
            Console.WriteLine("--- 阶段 2 完成 ---");


            // --- 阶段 3 (顺序执行) ---
            Console.WriteLine("\n--- 阶段 3: 倒果汁 ---");
            await maker.PourJuiceAsync(juice);
            // 倒果汁在装盘之后进行。
            Console.WriteLine("--- 阶段 3 完成 ---");

            totalStopwatch.Stop();
            // 停止总计时器

            Console.WriteLine("\n--- 早餐制作完成 ---");
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine($"\n>>> 总耗时: {totalStopwatch.Elapsed.TotalSeconds:F1} 秒");
            Console.ResetColor();

            Console.WriteLine("\n按任意键退出...");
            Console.ReadKey();
        }
    }
}