// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技（广东）有限公司所有。
// 所有权利保留。
// 官方网站：https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------

namespace ThingsGateway.TimeCrontab;

/// <summary>
/// Cron 表达式抽象类
/// </summary>
/// <remarks>主要将 Cron 表达式转换成 OOP 类进行操作</remarks>
public partial class Crontab
{
    /// <summary>
    /// 解析 Cron 表达式字段并存储其 所有发生值 字符解析器
    /// </summary>
    /// <param name="expression">Cron 表达式</param>
    /// <param name="format">Cron 表达式格式化类型</param>
    /// <returns><see cref="Dictionary{TKey, TValue}"/></returns>
    /// <exception cref="TimeCrontabException"></exception>
    private static Dictionary<CrontabFieldKind, List<ICronParser>> ParseToDictionary(string expression, CronStringFormat format)
    {
        // Cron 表达式空检查
        if (string.IsNullOrWhiteSpace(expression))
        {
            throw new TimeCrontabException("The provided cron string is null, empty or contains only whitespace.");
        }

        // 通过空白符切割 Cron 表达式每个字段域
        var instructions = expression.Split(separator, StringSplitOptions.RemoveEmptyEntries);

        // 验证当前 Cron 格式化类型字段数量和表达式字段数量是否一致
        var expectedCount = Constants.ExpectedFieldCounts[format];
        if (instructions.Length > expectedCount)
        {
            throw new TimeCrontabException(string.Format("The provided cron string <{0}> has too many parameters.", expression));
        }
        if (instructions.Length < expectedCount)
        {
            throw new TimeCrontabException(string.Format("The provided cron string <{0}> has too few parameters.", expression));
        }

        // 初始化字段偏移量和字段字符解析器
        var defaultFieldOffset = 0;
        var fieldParsers = new Dictionary<CrontabFieldKind, List<ICronParser>>();

        // 判断当前 Cron 格式化类型是否包含秒字段域，如果包含则优先解析秒字段域字符解析器
        if (format == CronStringFormat.WithSeconds || format == CronStringFormat.WithSecondsAndYears)
        {
            fieldParsers.Add(CrontabFieldKind.Second, ParseField(instructions[0], CrontabFieldKind.Second));
            defaultFieldOffset = 1;
        }

        // Cron 常规字段域
        fieldParsers.Add(CrontabFieldKind.Minute, ParseField(instructions[defaultFieldOffset + 0], CrontabFieldKind.Minute));   // 偏移量 1
        fieldParsers.Add(CrontabFieldKind.Hour, ParseField(instructions[defaultFieldOffset + 1], CrontabFieldKind.Hour));   // 偏移量 2
        fieldParsers.Add(CrontabFieldKind.Day, ParseField(instructions[defaultFieldOffset + 2], CrontabFieldKind.Day)); // 偏移量 3
        fieldParsers.Add(CrontabFieldKind.Month, ParseField(instructions[defaultFieldOffset + 3], CrontabFieldKind.Month)); // 偏移量 4
        fieldParsers.Add(CrontabFieldKind.DayOfWeek, ParseField(instructions[defaultFieldOffset + 4], CrontabFieldKind.DayOfWeek)); // 偏移量 5

        // 判断当前 Cron 格式化类型是否包含年字段域，如果包含则解析年字段域字符解析器
        if (format == CronStringFormat.WithYears || format == CronStringFormat.WithSecondsAndYears)
        {
            fieldParsers.Add(CrontabFieldKind.Year, ParseField(instructions[defaultFieldOffset + 5], CrontabFieldKind.Year));   // 偏移量 6
        }

        // 检查非法字符解析器，如 2 月没有 30 和 31 号
        CheckForIllegalParsers(fieldParsers);

        return fieldParsers;
    }

    private static readonly char[] separator = new[] { ' ' };

    /// <summary>
    /// 解析 Cron 单个字段域所有发生值 字符解析器
    /// </summary>
    /// <param name="field">字段值</param>
    /// <param name="kind">Cron 表达式格式化类型</param>
    /// <returns><see cref="List{T}"/></returns>
    /// <exception cref="TimeCrontabException"></exception>
    private static List<ICronParser> ParseField(string field, CrontabFieldKind kind)
    {
        /*
         * 在 Cron 表达式中，单个字段域值也支持定义多个值（我们称为值中值），如 1,2,3 或 SUN,FRI,SAT
         * 所以，这里需要将字段域值通过 , 进行切割后独立处理
         */

        try
        {
            return field.Split(',').Select(parser => ParseParser(parser, kind)).ToList();
        }
        catch (Exception ex)
        {
            throw new TimeCrontabException(
                string.Format("There was an error parsing '{0}' for the {1} field.", field, Enum.GetName(typeof(CrontabFieldKind), kind))
                , ex);
        }
    }

    /// <summary>
    /// 解析 Cron 字段域值中值
    /// </summary>
    /// <param name="parser">字段值中值</param>
    /// <param name="kind">Cron 表达式格式化类型</param>
    /// <returns><see cref="ICronParser"/></returns>
    /// <exception cref="TimeCrontabException"></exception>
    private static ICronParser ParseParser(string parser, CrontabFieldKind kind)
    {
        // Cron 字段中所有字母均采用大写方式，所以需要转换所有为大写再操作
        var newParser = parser.ToUpperInvariant();

        try
        {
            // 判断值是否以 * 字符开头
            if (newParser.StartsWith('*'))
            {
                // 继续往后解析
                newParser = newParser[1..];

                // 判断是否以 / 字符开头，如果是，则该值为带步长的 Cron 值
                if (newParser.StartsWith('/'))
                {
                    // 继续往后解析
                    newParser = newParser[1..];

                    // 解析 Cron 值步长并创建 StepParser 解析器
                    var steps = GetValue(ref newParser, kind);
                    return new StepParser(0, steps, kind);
                }

                // 处理 * 携带意外值
                if (!string.IsNullOrEmpty(newParser))
                {
                    throw new TimeCrontabException(string.Format("Invalid parser '{0}'.", parser));
                }

                // 否则，创建 AnyParser 解析器
                return new AnyParser(kind);
            }

            // 判断值是否以 L 字符开头
            if (newParser.StartsWith('L') && kind == CrontabFieldKind.Day)
            {
                // 继续往后解析
                newParser = newParser[1..];

                // 是否是 LW 字符，如果是，创建 LastWeekdayOfMonthParser 解析器
                if (newParser == "W")
                {
                    return new LastWeekdayOfMonthParser(kind);
                }
                // 否则创建 LastDayOfMonthParser 解析器
                else
                {
                    return new LastDayOfMonthParser(kind);
                }
            }

            // 判断值是否等于 R
            if (newParser == "R")
            {
                return new RandomParser(kind);
            }

            // 判断值是否等于 ?
            if (newParser == "?")
            {
                // 创建 BlankDayOfMonthOrWeekParser 解析器
                return new BlankDayOfMonthOrWeekParser(kind);
            }

            /*
             * 如果上面均不匹配，那么该值类似取值有：2，1/2，1-10，1-10/2，SUN，SUNDAY，SUNL，JAN，3W，3L，2#5 等
             */

            // 继续推进解析
            var firstValue = GetValue(ref newParser, kind);

            // 如果没有返回新的待解析字符，则认为这是一个具体值
            if (string.IsNullOrEmpty(newParser))
            {
                // 对年份进行特别处理
                if (kind == CrontabFieldKind.Year)
                {
                    return new SpecificYearParser(firstValue, kind);
                }
                else
                {
                    // 创建 SpecificParser 解析器
                    return new SpecificParser(firstValue, kind);
                }
            }

            // 如果存在待解析字符，如 - / # L W 值，则进一步解析
            switch (newParser[0])
            {
                // 判断值是否以 / 字符开头
                case '/':
                    {
                        // 继续往后解析
                        newParser = newParser[1..];

                        // 解析 Cron 值步长并创建 StepParser 解析器
                        var steps = GetValue(ref newParser, kind);
                        return new StepParser(firstValue, steps, kind);
                    }
                // 判断值是否以 - 字符开头
                case '-':
                    {
                        // 继续往后解析
                        newParser = newParser[1..];

                        // 获取范围结束值
                        var endValue = GetValue(ref newParser, kind);
                        int? steps = null;

                        // 继续推进解析，判断是否以 / 开头，如果是，则获取步长
                        if (newParser.StartsWith('/'))
                        {
                            newParser = newParser[1..];
                            steps = GetValue(ref newParser, kind);
                        }

                        // 创建 RangeParser 解析器
                        return new RangeParser(firstValue, endValue, steps, kind);
                    }
                // 判断值是否以 # 字符开头
                case '#':
                    {
                        // 继续往后解析
                        newParser = newParser[1..];

                        // 获取第几个
                        var weekNumber = GetValue(ref newParser, kind);

                        // 继续推进解析，如果存在其他字符，则抛异常
                        if (!string.IsNullOrEmpty(newParser))
                        {
                            throw new TimeCrontabException(string.Format("Invalid parser '{0}.'", parser));
                        }

                        // 创建 SpecificDayOfWeekInMonthParser 解析器
                        return new SpecificDayOfWeekInMonthParser(firstValue, weekNumber, kind);
                    }
                // 判断解析值是否等于 L 或 W
                default:
                    // 创建 LastDayOfWeekInMonthParser 解析器
                    if (newParser == "L" && kind == CrontabFieldKind.DayOfWeek)
                    {
                        return new LastDayOfWeekInMonthParser(firstValue, kind);
                    }
                    // 创建 NearestWeekdayParser 解析器
                    else if (newParser == "W" && kind == CrontabFieldKind.Day)
                    {
                        return new NearestWeekdayParser(firstValue, kind);
                    }
                    break;
            }

            throw new TimeCrontabException(string.Format("Invalid parser '{0}'.", parser));
        }
        catch (Exception ex)
        {
            throw new TimeCrontabException(string.Format("Invalid parser '{0}'. See inner exception for details.", parser), ex);
        }
    }

    /// <summary>
    /// 将 Cron 字段值中值进一步解析
    /// </summary>
    /// <param name="parser">当前解析值</param>
    /// <param name="kind">Cron 表达式格式化类型</param>
    /// <returns><see cref="int"/></returns>
    /// <exception cref="TimeCrontabException"></exception>
    private static int GetValue(ref string parser, CrontabFieldKind kind)
    {
        // 值空检查
        if (string.IsNullOrEmpty(parser))
        {
            throw new TimeCrontabException("Expected number, but parser was empty.");
        }

        // 字符偏移量
        int offset;

        // 判断首个字符是数字还是字符串
        var isDigit = char.IsDigit(parser[0]);
        var isLetter = char.IsLetter(parser[0]);

        // 推进式遍历值并检查每一个字符，一旦出现类型不连贯则停止检查
        for (offset = 0; offset < parser.Length; offset++)
        {
            // 如果存在不连贯数字或字母则跳出循环
            if ((isDigit && !char.IsDigit(parser[offset])) || (isLetter && !char.IsLetter(parser[offset])))
            {
                break;
            }
        }

        var maximum = Constants.MaximumDateTimeValues[kind];

        // 前面连贯类型的值
        var valueToParse = parser[..offset];

        // 处理数字开头的连贯类型值
        if (int.TryParse(valueToParse, out var value))
        {
            // 导出下一轮待解析的值（依旧采用推进式）
            parser = parser[offset..];

            var returnValue = value;

            // 验证值范围
            if (returnValue > maximum)
            {
                throw new TimeCrontabException(string.Format("Value for {0} parser exceeded maximum value of {1}.", Enum.GetName(typeof(CrontabFieldKind), kind), maximum));
            }

            return returnValue;
        }
        // 处理字母开头的连贯类型值，通常认为这是一个单词，如SUN，JAN
        else
        {
            List<KeyValuePair<string, int>> replaceVal = null;

            // 判断当前 Cron 字段类型是否是星期，如果是，则查找该单词是否在 Constants.Days 定义之中
            if (kind == CrontabFieldKind.DayOfWeek)
            {
                replaceVal = Constants.Days.Where(x => valueToParse.StartsWith(x.Key)).ToList();
            }
            // 判断当前 Cron 字段类型是否是月份，如果是，则查找该单词是否在 Constants.Months 定义之中
            else if (kind == CrontabFieldKind.Month)
            {
                replaceVal = Constants.Months.Where(x => valueToParse.StartsWith(x.Key)).ToList();
            }

            // 如果存在且唯一，则进入下一轮判断
            // 接下来的判断是处理 SUN + L 的情况，如 SUNL == 0L == SUNDAY，它们都是合法的 Cron 值
            if (replaceVal?.Count == 1)
            {
                var missingParser = "";

                // 处理带 L 和不带 L 的单词问题
                if (parser.Length == offset
                    && parser.EndsWith('L')
                    && kind == CrontabFieldKind.DayOfWeek)
                {
                    missingParser = "L";
                }
                parser = parser[offset..] + missingParser;

                // 转换成 int 值返回（SUN，JAN.....）
                var returnValue = replaceVal.First().Value;

                // 验证值范围
                if (returnValue > maximum)
                {
                    throw new TimeCrontabException(string.Format("Value for {0} parser exceeded maximum value of {1}.", Enum.GetName(typeof(CrontabFieldKind), kind), maximum));
                }

                return returnValue;
            }
        }

        throw new TimeCrontabException("Parser does not contain expected number.");
    }

    /// <summary>
    /// 检查非法字符解析器，如 2 月没有 30 和 31 号
    /// </summary>
    /// <remarks>检查 2 月份是否存在 30 和 31 天的非法数值解析器</remarks>
    /// <param name="parsers">Cron 字段解析器字典集合</param>
    /// <exception cref="TimeCrontabException"></exception>
    private static void CheckForIllegalParsers(Dictionary<CrontabFieldKind, List<ICronParser>> parsers)
    {
        // 获取当前 Cron 表达式月字段和天字段所有数值
        var monthSingle = GetSpecificParsers(parsers, CrontabFieldKind.Month);
        var daySingle = GetSpecificParsers(parsers, CrontabFieldKind.Day);

        // 如果月份为 2 月单天数出现 30 和 31 天，则是无效数值
        if (monthSingle.Count != 0 && monthSingle.All(x => x.SpecificValue == 2))
        {
            if (daySingle.Count != 0 && daySingle.All(x => (x.SpecificValue == 30) || (x.SpecificValue == 31)))
            {
                throw new TimeCrontabException("The February 30 and 31 don't exist.");
            }
        }
    }

    /// <summary>
    /// 查找 Cron 字段类型所有具体值解析器
    /// </summary>
    /// <param name="parsers">Cron 字段解析器字典集合</param>
    /// <param name="kind">Cron 字段种类</param>
    /// <returns><see cref="List{T}"/></returns>
    private static List<SpecificParser> GetSpecificParsers(Dictionary<CrontabFieldKind, List<ICronParser>> parsers, CrontabFieldKind kind)
    {
        var kindParsers = parsers[kind];

        // 查找 Cron 字段类型所有具体值解析器
        return kindParsers.Where(x => x.GetType() == typeof(SpecificParser)).Cast<SpecificParser>()
            .Union(
            kindParsers.Where(x => x.GetType() == typeof(RangeParser)).SelectMany(x => ((RangeParser)x).SpecificParsers)
            ).Union(
                kindParsers.Where(x => x.GetType() == typeof(StepParser)).SelectMany(x => ((StepParser)x).SpecificParsers)
            ).ToList();
    }

    /// <summary>
    /// 获取特定时间范围下一个发生时间
    /// </summary>
    /// <param name="baseTime">起始时间</param>
    /// <param name="endTime">结束时间</param>
    /// <returns><see cref="DateTime"/></returns>
    private DateTime InternalGetNextOccurence(DateTime baseTime, DateTime endTime)
    {
        // 判断当前 Cron 格式化类型是否支持秒
        var isSecondFormat = Format == CronStringFormat.WithSeconds || Format == CronStringFormat.WithSecondsAndYears;

        // 由于 Cron 格式化类型不包含毫秒，则裁剪掉毫秒部分
        var newValue = baseTime;
        newValue = newValue.AddMilliseconds(-newValue.Millisecond);

        // 如果当前 Cron 格式化类型不支持秒，则裁剪掉秒部分
        if (!isSecondFormat)
        {
            newValue = newValue.AddSeconds(-newValue.Second);
        }
        // 初始化是否存在随机 R 标识符
        var randomSecond = false;
        var randomMinute = false;
        var randomHour = false;
        // 获取分钟、小时所有字符解析器
        var minuteParsers = Parsers[CrontabFieldKind.Minute].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
        randomMinute = minuteParsers.OfType<RandomParser>().Any();
        var hourParsers = Parsers[CrontabFieldKind.Hour].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
        randomHour = hourParsers.OfType<RandomParser>().Any();

        // 获取秒、分钟、小时解析器中最小起始值
        // 该值主要用来获取下一个发生值的输入参数
        var firstSecondValue = newValue.Second;
        var firstMinuteValue = minuteParsers.Select(x => x.First()).Min();
        var firstHourValue = hourParsers.Select(x => x.First()).Min();

        // 定义一个标识，标识当前时间下一个发生时间值是否进入新一轮循环
        // 如：如果当前时间的秒数为 59，那么下一个秒数应该为 00，那么当前时间分钟就应该 +1
        // 以此类推，如果 +1 后分钟数为 59，那么下一个分钟数也应该为 00，那么当前时间小时数就应该 +1
        // ....
        var overflow = true;

        // 处理 Cron 格式化类型包含秒的情况 =================================================================
        var newSeconds = newValue.Second;
        if (isSecondFormat)
        {
            // 获取秒所有字符解析器
            var secondParsers = Parsers[CrontabFieldKind.Second].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
            randomSecond = secondParsers.OfType<RandomParser>().Any();
            // 获取秒解析器最小起始值
            firstSecondValue = secondParsers.Select(x => x.First()).Min();

            // 获取秒下一个发生值
            newSeconds = Increment(secondParsers, newValue.Second, firstSecondValue, out overflow);

            // 设置起始时间为下一个秒时间
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);

            // 如果当前秒并没有进入下一轮循环但存在不匹配的字符过滤器
            if (!overflow && !IsMatch(newValue))
            {
                // 重置秒为起始值并标记 overflow 为 true 进入新一轮循环
                newSeconds = firstSecondValue;

                // 此时计算时间秒部分应该为起始值
                // 如 22:10:59 -> 22:10:00
                newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);

                // 标记进入下一轮循环
                overflow = true;
            }

            // 如果程序到达这里，说明并没有进入上面分支，则直接返回下一秒时间
            if (!overflow)
            {
                return MinDate(newValue, endTime);
            }
        }

        // 程序到达这里，说明秒部分已经标识进入新一轮循环，那么分支就应该获取下一个分钟发生值 =================================================================
        var newMinutes = Increment(minuteParsers, newValue.Minute + (overflow ? 0 : -1), firstMinuteValue, out overflow);

        // 设置起始时间为下一个分钟时间
        newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, overflow ? firstSecondValue : newSeconds);

        // 如果当前分钟并没有进入下一轮循环但存在不匹配的字符过滤器
        if (!overflow && !IsMatch(newValue))
        {
            // 重置秒，分钟为起始值并标记 overflow 为 true 进入新一轮循环
            newSeconds = firstSecondValue;
            newMinutes = firstMinuteValue;

            // 此时计算时间秒和分钟部分应该为起始值
            // 如 22:59:59 -> 22:00:00
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, firstSecondValue);

            // 标记进入下一轮循环
            overflow = true;
        }

        // 如果程序到达这里，说明并没有进入上面分支，则直接返回下一分钟时间
        if (!overflow)
        {
            return MinDate(newValue, endTime);
        }

        // 程序到达这里，说明分钟部分已经标识进入新一轮循环，那么分支就应该获取下一个小时发生值 =================================================================
        var newHours = Increment(hourParsers, newValue.Hour + (overflow ? 0 : -1), firstHourValue, out overflow);

        // 设置起始时间为下一个小时时间
        newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours,
             overflow && !randomMinute ? firstMinuteValue : newMinutes,
             overflow && !randomSecond ? firstSecondValue : newSeconds);

        // 如果当前小时并没有进入下一轮循环但存在不匹配的字符过滤器
        if (!overflow && !IsMatch(newValue))
        {
            // 此时计算时间秒，分钟和小时部分应该为起始值
            // 如 23:59:59 -> 23:00:00
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, firstHourValue, firstMinuteValue, firstSecondValue);

            // 标记进入下一轮循环
            overflow = true;
        }

        // 如果程序到达这里，说明并没有进入上面分支，则直接返回下一小时时间
        if (!randomHour && !overflow)
        {
            return MinDate(newValue, endTime);
        }

        // 如果程序达到这里，说明天数变了（一旦天数变了，那么月份可能也变了，星期可能也变了，年份也可能变了）
        // 所以这里的计算最为复杂
        List<ITimeParser> yearParsers = null;

        // 首先先判断当前 Cron 格式化类型是否支持年份
        var isYearFormat = Format == CronStringFormat.WithYears || Format == CronStringFormat.WithSecondsAndYears;

        // 如果支持，读取年份字符过滤器
        if (isYearFormat)
        {
            yearParsers = Parsers[CrontabFieldKind.Year].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
        }

        // 程序能够执行到这里，那么说明时间已经是 23:59:59，所以起始时间追加 1 天
        // 这里的代码看起来很奇怪，但是是为了处理终止时间为 12/31/9999 23:59:59.999 的情况，也就是世界末日了~~~
        try
        {
            newValue = newValue.AddDays(1);
        }
        catch
        {
            return endTime;
        }

        // 在有效的年份时间内死循环至天、周、月、年全部匹配才终止循环
        while (!(IsMatch(newValue, CrontabFieldKind.Day)
            && IsMatch(newValue, CrontabFieldKind.DayOfWeek)
            && IsMatch(newValue, CrontabFieldKind.Month)
            && (!isYearFormat || IsMatch(newValue, CrontabFieldKind.Year))))
        {
            // 如果当前匹配到的时间已经大于或等于终止时间，则直接返回
            if (newValue >= endTime)
            {
                return MinDate(newValue, endTime);
            }

            // 如果 Cron 年份字段域获取下一个发生值为 null，那么直接返回 终止时间
            // 也就是已经没有匹配项了
            if (isYearFormat && yearParsers!.Select(x => x.Next(newValue.Year - 1)).All(x => x == null))
            {
                return endTime;
            }

            // 同样防止终止时间为 12/31/9999 23:59:59.999 的情况
            try
            {
                // 不断增加 1 天直至匹配成功
                newValue = newValue.AddDays(1);
            }
            catch
            {
                return endTime;
            }
        }

        return MinDate(newValue, endTime);
    }

    /// <summary>
    /// 获取特定时间范围上一个发生时间
    /// </summary>
    /// <param name="baseTime">起始时间</param>
    /// <param name="endTime">结束时间</param>
    /// <returns><see cref="DateTime"/></returns>
    private DateTime InternalGetPreviousOccurence(DateTime baseTime, DateTime endTime)
    {
        // 判断当前 Cron 格式化类型是否支持秒
        var isSecondFormat = Format == CronStringFormat.WithSeconds || Format == CronStringFormat.WithSecondsAndYears;

        // 由于 Cron 格式化类型不包含毫秒，则裁剪掉毫秒部分
        var newValue = baseTime;
        newValue = newValue.AddMilliseconds(-newValue.Millisecond);

        // 如果当前 Cron 格式化类型不支持秒，则裁剪掉秒部分
        if (!isSecondFormat)
        {
            newValue = newValue.AddSeconds(-newValue.Second);
        }

        // 获取分钟、小时所有字符解析器
        var minuteParsers = Parsers[CrontabFieldKind.Minute].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
        var hourParsers = Parsers[CrontabFieldKind.Hour].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();

        // 获取秒、分钟、小时解析器中最小起始值
        // 该值主要用来获取上一个发生值的输入参数
        var lastSecondValue = newValue.Second;
        var lastMinuteValue = minuteParsers.Select(x => x.Last()).Max();
        var lastHourValue = hourParsers.Select(x => x.Last()).Max();

        // 定义一个标识，标识当前时间山歌一个发生时间值是否进入新一轮循环
        // 如：如果当前时间的秒数为 00，那么上一个秒数应该为 59，那么当前时间分钟就应该 -1
        // 以此类推，如果 -1 后分钟数为 00，那么上一个分钟数也应该为 59，那么当前时间小时数就应该 -1
        // ....
        var overflow = true;

        // 处理 Cron 格式化类型包含秒的情况 =================================================================
        var newSeconds = newValue.Second;
        if (isSecondFormat)
        {
            // 获取秒所有字符解析器
            var secondParsers = Parsers[CrontabFieldKind.Second].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();

            // 获取秒解析器最大末尾值
            lastSecondValue = secondParsers.Select(x => x.Last()).Max();

            // 获取秒上一个发生值
            newSeconds = Decrement(secondParsers, newValue.Second, lastSecondValue, out overflow);

            // 设置起始时间为上一个秒时间
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);

            // 如果当前秒并没有进入下一轮循环但存在不匹配的字符过滤器
            if (!overflow && !IsMatch(newValue))
            {
                // 重置秒为起始值并标记 overflow 为 true 进入新一轮循环
                newSeconds = lastSecondValue;

                // 此时计算时间秒部分应该为末尾值
                // 如 22:10:00 -> 22:09:59
                newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);

                // 标记进入下一轮循环
                overflow = true;
            }

            // 如果程序到达这里，说明并没有进入上面分支，则直接返回上一秒时间
            if (!overflow)
            {
                return MaxDate(newValue, endTime);
            }
        }

        // 程序到达这里，说明秒部分已经标识进入新一轮循环，那么分支就应该获取上一个分钟发生值 =================================================================
        var newMinutes = Decrement(minuteParsers, newValue.Minute + (overflow ? 0 : 1), lastMinuteValue, out overflow);

        // 设置起始时间为上一个分钟时间
        newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, overflow ? lastSecondValue : newSeconds);

        // 如果当前分钟并没有进入下一轮循环但存在不匹配的字符过滤器
        if (!overflow && !IsMatch(newValue))
        {
            // 重置秒，分钟为起始值并标记 overflow 为 true 进入新一轮循环
            newSeconds = lastSecondValue;
            newMinutes = lastMinuteValue;

            // 此时计算时间秒和分钟部分应该为起始值
            // 如 22:00:00 -> 21:59:59
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, lastSecondValue);

            // 标记进入下一轮循环
            overflow = true;
        }

        // 如果程序到达这里，说明并没有进入上面分支，则直接返回上一分钟时间
        if (!overflow)
        {
            return MaxDate(newValue, endTime);
        }

        // 程序到达这里，说明分钟部分已经标识进入新一轮循环，那么分支就应该获取下一个小时发生值 =================================================================
        var newHours = Decrement(hourParsers, newValue.Hour + (overflow ? 0 : 1), lastHourValue, out overflow);

        // 设置起始时间为上一个小时时间
        newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours,
            overflow ? lastMinuteValue : newMinutes,
            overflow ? lastSecondValue : newSeconds);

        // 如果当前小时并没有进入下一轮循环但存在不匹配的字符过滤器
        if (!overflow && !IsMatch(newValue))
        {
            // 此时计算时间秒，分钟和小时部分应该为末尾值
            // 如 24:00:00 -> 23:59:59
            newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, lastHourValue, lastMinuteValue, lastSecondValue);

            // 标记进入下一轮循环
            overflow = true;
        }

        // 如果程序到达这里，说明并没有进入上面分支，则直接返回下一小时时间
        if (!overflow)
        {
            return MaxDate(newValue, endTime);
        }

        // 如果程序达到这里，说明天数变了（一旦天数变了，那么月份可能也变了，星期可能也变了，年份也可能变了）
        // 所以这里的计算最为复杂
        List<ITimeParser> yearParsers = null;

        // 首先先判断当前 Cron 格式化类型是否支持年份
        var isYearFormat = Format == CronStringFormat.WithYears || Format == CronStringFormat.WithSecondsAndYears;

        // 如果支持，读取年份字符过滤器
        if (isYearFormat)
        {
            yearParsers = Parsers[CrontabFieldKind.Year].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
        }

        // 程序能够执行到这里，那么说明时间已经是 24:00:00，所以起始时间减 1 天
        // 这里的代码看起来很奇怪，但是是为了处理终止时间为 12/31/9999 23:59:59.999 的情况，也就是世界末日了~~~
        try
        {
            newValue = newValue.AddDays(-1);
        }
        catch
        {
            return endTime;
        }

        // 在有效的年份时间内死循环至天、周、月、年全部匹配才终止循环
        while (!(IsMatch(newValue, CrontabFieldKind.Day)
            && IsMatch(newValue, CrontabFieldKind.DayOfWeek)
            && IsMatch(newValue, CrontabFieldKind.Month)
            && (!isYearFormat || IsMatch(newValue, CrontabFieldKind.Year))))
        {
            // 如果当前匹配到的时间已经大于或等于终止时间，则直接返回
            if (newValue <= endTime)
            {
                return MaxDate(newValue, endTime);
            }

            // 如果 Cron 年份字段域获取下一个发生值为 null，那么直接返回 终止时间
            // 也就是已经没有匹配项了
            if (isYearFormat && yearParsers.Select(x => x.Previous(newValue.Year + 1)).All(x => x == null))
            {
                return endTime;
            }

            // 同样防止终止时间为 12/31/9999 23:59:59.999 的情况
            try
            {
                newValue = newValue.AddDays(-1);
            }
            catch
            {
                return endTime;
            }
        }

        return MaxDate(newValue, endTime);
    }

    /// <summary>
    /// 获取当前时间解析器下一个发生值
    /// </summary>
    /// <param name="parsers">解析器</param>
    /// <param name="value">当前值</param>
    /// <param name="defaultValue">默认值</param>
    /// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
    /// <returns><see cref="int"/></returns>
    private static int Increment(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
    {
        // 检查是否是随机 R 字符解析器
        if (parsers.Count == 1 && parsers.First() is RandomParser randomParser)
        {
            overflow = true;
            return randomParser.Next(value).Value;
        }

        var nextValue = parsers.Select(x => x.Next(value))
            .Where(x => x > value)
            .Min()
            ?? defaultValue;

        // 如果此时秒或分钟或23到达最大值，则应该返回起始值
        overflow = nextValue <= value;
        return nextValue;
    }

    /// <summary>
    /// 获取当前时间解析器上一个发生值
    /// </summary>
    /// <param name="parsers">解析器</param>
    /// <param name="value">当前值</param>
    /// <param name="defaultValue">默认值</param>
    /// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
    /// <returns><see cref="int"/></returns>
    private static int Decrement(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
    {
        var previousValue = parsers.Select(x => x.Previous(value))
            .Where(x => x < value)
            .Max()
            ?? defaultValue;

        // 如果此时秒或分钟或00到达最小值，则应该返回末尾值
        overflow = previousValue >= value;
        return previousValue;
    }

    /// <summary>
    /// 处理下一个发生时间边界值
    /// </summary>
    /// <remarks>如果发生时间大于终止时间，则返回终止时间，否则返回发生时间</remarks>
    /// <param name="newTime">下一个发生时间</param>
    /// <param name="endTime">终止时间</param>
    /// <returns><see cref="DateTime"/></returns>
    private static DateTime MinDate(DateTime newTime, DateTime endTime)
    {
        return newTime >= endTime ? endTime : newTime;
    }

    /// <summary>
    /// 处理上一个发生时间边界值
    /// </summary>
    /// <remarks>如果发生时间小于终止时间，则返回终止时间，否则返回发生时间</remarks>
    /// <param name="newTime">下一个发生时间</param>
    /// <param name="endTime">终止时间</param>
    /// <returns><see cref="DateTime"/></returns>
    private static DateTime MaxDate(DateTime newTime, DateTime endTime)
    {
        return newTime <= endTime ? endTime : newTime;
    }

    /// <summary>
    /// 判断 Cron 所有字段字符解析器是否都能匹配当前时间各个部分
    /// </summary>
    /// <param name="datetime">当前时间</param>
    /// <returns><see cref="bool"/></returns>
    private bool IsMatch(DateTime datetime)
    {
        return Parsers.All(fieldKind =>
            fieldKind.Value.Any(parser => parser.IsMatch(datetime))
        );
    }

    /// <summary>
    /// 判断当前 Cron 字段类型字符解析器和当前时间至少存在一种匹配
    /// </summary>
    /// <param name="datetime">当前时间</param>
    /// <param name="kind">Cron 字段种类</param>
    /// <returns></returns>
    private bool IsMatch(DateTime datetime, CrontabFieldKind kind)
    {
        return Parsers.Where(x => x.Key == kind)
            .SelectMany(x => x.Value)
            .Any(parser => parser.IsMatch(datetime));
    }

    /// <summary>
    /// 将 Cron 字段解析器转换成字符串
    /// </summary>
    /// <param name="paramList">Cron 字段字符串集合</param>
    /// <param name="kind">Cron 字段种类</param>
    private void JoinParsers(List<string> paramList, CrontabFieldKind kind)
    {
        paramList.Add(
            string.Join(",", Parsers
                .Where(x => x.Key == kind)
                .SelectMany(x => x.Value.Select(y => y.ToString())).ToArray()
            )
        );
    }
}