/**
         * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
         * the date list is returned unmodified.
         * @param dates
         * @return
         */
        static private List <DateTime> GetDayVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand)
        {
            if (expand == null || pattern.ByDay.Count == 0)
            {
                return(dates);
            }

            if (expand.HasValue && expand.Value)
            {
                // Expand behavior
                List <DateTime> weekDayDates = new List <DateTime>();
                for (int i = 0; i < dates.Count; i++)
                {
                    DateTime date = dates[i];
                    for (int j = 0; j < pattern.ByDay.Count; j++)
                    {
                        weekDayDates.AddRange(GetAbsWeekDays(date, pattern.ByDay[j], pattern, expand));
                    }
                }

                return(weekDayDates);
            }
            else
            {
                // Limit behavior
                for (int i = dates.Count - 1; i >= 0; i--)
                {
                    DateTime date = dates[i];
                    for (int j = 0; j < pattern.ByDay.Count; j++)
                    {
                        WeekDay weekDay = pattern.ByDay[j];
                        if (weekDay.DayOfWeek.Equals(date.DayOfWeek))
                        {
                            // If no offset is specified, simply test the day of week!
                            // FIXME: test with offset...
                            if (date.DayOfWeek.Equals(weekDay.DayOfWeek))
                            {
                                goto Next;
                            }
                        }
                    }
                    dates.RemoveAt(i);
                    Next :;
                }

                return(dates);
            }
        }
        /**
         * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
         * specified by this recurrence rule.
         * @param date
         * @param weekDay
         * @return
         */
        static private List <DateTime> GetAbsWeekDays(DateTime date, WeekDay weekDay, RecurrencePattern pattern, bool?expand)
        {
            List <DateTime> days = new List <DateTime>();

            DayOfWeek dayOfWeek = weekDay.DayOfWeek;

            if (pattern.Frequency == FrequencyType.Daily)
            {
                if (date.DayOfWeek == dayOfWeek)
                {
                    days.Add(date);
                }
            }
            else if (pattern.Frequency == FrequencyType.Weekly || pattern.ByWeekNo.Count > 0)
            {
                // Rewind to the first day of the week
                while (date.DayOfWeek != pattern.FirstDayOfWeek)
                {
                    date = date.AddDays(-1);
                }

                // Step forward until we're on the day of week we're interested in
                while (date.DayOfWeek != dayOfWeek)
                {
                    date = date.AddDays(1);
                }

                days.Add(date);
            }
            else if (pattern.Frequency == FrequencyType.Monthly || pattern.ByMonth.Count > 0)
            {
                int month = date.Month;

                // construct a list of possible month days..
                date = date.AddDays(-date.Day + 1);
                while (date.DayOfWeek != dayOfWeek)
                {
                    date = date.AddDays(1);
                }

                while (date.Month == month)
                {
                    days.Add(date);
                    date = date.AddDays(7);
                }
            }
            else if (pattern.Frequency == FrequencyType.Yearly)
            {
                int year = date.Year;

                // construct a list of possible year days..
                date = date.AddDays(-date.DayOfYear + 1);
                while (date.DayOfWeek != dayOfWeek)
                {
                    date = date.AddDays(1);
                }

                while (date.Year == year)
                {
                    days.Add(date);
                    date = date.AddDays(7);
                }
            }
            return(GetOffsetDates(days, weekDay.Offset));
        }
        public object Deserialize(TextReader tr)
        {
            string value = tr.ReadToEnd();

            // Instantiate the data type
            RecurrencePattern r = new RecurrencePattern();

            if (r != null)
            {
                Match match = Regex.Match(value, @"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    // Parse the frequency type
                    r.Frequency = (FrequencyType)Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true);

                    // NOTE: fixed a bug where the group 2 match
                    // resulted in an empty string, which caused
                    // an error.
                    if (match.Groups[2].Success &&
                        match.Groups[2].Length > 0)
                    {
                        string[] keywordPairs = match.Groups[2].Value.Split(';');
                        foreach (string keywordPair in keywordPairs)
                        {
                            string[] keyValues = keywordPair.Split('=');
                            string   keyword   = keyValues[0];
                            string   keyValue  = keyValues[1];

                            switch (keyword.ToUpper())
                            {
                            case "UNTIL":
                            {
                                try
                                {
                                    r.Until = DateTime.Parse(keyValue, System.Globalization.CultureInfo.InvariantCulture);
                                }
                                catch (Exception ex)
                                {
                                    Log.e(ex);
                                }
                            } break;

                            case "COUNT": r.Count = Convert.ToInt32(keyValue); break;

                            case "INTERVAL": r.Interval = Convert.ToInt32(keyValue); break;

                            case "BYSECOND": AddInt32Values(r.BySecond, keyValue); break;

                            case "BYMINUTE": AddInt32Values(r.ByMinute, keyValue); break;

                            case "BYHOUR": AddInt32Values(r.ByHour, keyValue); break;

                            case "BYDAY":
                            {
                                string[] days = keyValue.Split(',');
                                foreach (string day in days)
                                {
                                    WeekDay wd     = new WeekDay();
                                    Match   match2 = Regex.Match(value, @"(\+|-)?(\d{1,2})?(\w{2})");
                                    if (match2.Success)
                                    {
                                        if (match2.Groups[2].Success)
                                        {
                                            wd.Offset = Convert.ToInt32(match2.Groups[2].Value);
                                            if (match2.Groups[1].Success && match2.Groups[1].Value.Contains("-"))
                                            {
                                                wd.Offset *= -1;
                                            }
                                        }

                                        wd.DayOfWeek = GetDayOfWeek(match2.Groups[3].Value);

                                        r.ByDay.Add(wd);
                                    }
                                }
                            } break;

                            case "BYMONTHDAY": AddInt32Values(r.ByMonthDay, keyValue); break;

                            case "BYYEARDAY": AddInt32Values(r.ByYearDay, keyValue); break;

                            case "BYWEEKNO": AddInt32Values(r.ByWeekNo, keyValue); break;

                            case "BYMONTH": AddInt32Values(r.ByMonth, keyValue); break;

                            case "BYSETPOS": AddInt32Values(r.BySetPosition, keyValue); break;

                            case "WKST": r.FirstDayOfWeek = GetDayOfWeek(keyValue); break;
                            }
                        }
                    }
                }

                //
                // This matches strings such as:
                //
                // "Every 6 minutes"
                // "Every 3 days"
                //
                else if ((match = Regex.Match(value, @"every\s+(?<Interval>other|\d+)?\w{0,2}\s*(?<Freq>second|minute|hour|day|week|month|year)s?,?\s*(?<More>.+)", RegexOptions.IgnoreCase)).Success)
                {
                    if (match.Groups["Interval"].Success)
                    {
                        int interval;
                        if (!int.TryParse(match.Groups["Interval"].Value, out interval))
                        {
                            r.Interval = 2; // "other"
                        }
                        else
                        {
                            r.Interval = interval;
                        }
                    }
                    else
                    {
                        r.Interval = 1;
                    }

                    switch (match.Groups["Freq"].Value.ToLower())
                    {
                    case "second": r.Frequency = FrequencyType.Secondly; break;

                    case "minute": r.Frequency = FrequencyType.Minutely; break;

                    case "hour": r.Frequency = FrequencyType.Hourly; break;

                    case "day": r.Frequency = FrequencyType.Daily; break;

                    case "week": r.Frequency = FrequencyType.Weekly; break;

                    case "month": r.Frequency = FrequencyType.Monthly; break;

                    case "year": r.Frequency = FrequencyType.Yearly; break;
                    }

                    string[] values = match.Groups["More"].Value.Split(',');
                    foreach (string item in values)
                    {
                        if ((match = Regex.Match(item, @"(?<Num>\d+)\w\w\s+(?<Type>second|minute|hour|day|week|month)", RegexOptions.IgnoreCase)).Success ||
                            (match = Regex.Match(item, @"(?<Type>second|minute|hour|day|week|month)\s+(?<Num>\d+)", RegexOptions.IgnoreCase)).Success)
                        {
                            int num;
                            if (int.TryParse(match.Groups["Num"].Value, out num))
                            {
                                switch (match.Groups["Type"].Value.ToLower())
                                {
                                case "second":
                                    r.BySecond.Add(num);
                                    break;

                                case "minute":
                                    r.ByMinute.Add(num);
                                    break;

                                case "hour":
                                    r.ByHour.Add(num);
                                    break;

                                case "day":
                                    switch (r.Frequency)
                                    {
                                    case FrequencyType.Yearly:
                                        r.ByYearDay.Add(num);
                                        break;

                                    case FrequencyType.Monthly:
                                        r.ByMonthDay.Add(num);
                                        break;
                                    }
                                    break;

                                case "week":
                                    r.ByWeekNo.Add(num);
                                    break;

                                case "month":
                                    r.ByMonth.Add(num);
                                    break;
                                }
                            }
                        }
                        else if ((match = Regex.Match(item, @"(?<Num>\d+\w{0,2})?(\w|\s)+?(?<First>first)?(?<Last>last)?\s*((?<Day>sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*(and|or)?\s*)+", RegexOptions.IgnoreCase)).Success)
                        {
                            int num = int.MinValue;
                            if (match.Groups["Num"].Success)
                            {
                                if (int.TryParse(match.Groups["Num"].Value, out num))
                                {
                                    if (match.Groups["Last"].Success)
                                    {
                                        // Make number negative
                                        num *= -1;
                                    }
                                }
                            }
                            else if (match.Groups["Last"].Success)
                            {
                                num = -1;
                            }
                            else if (match.Groups["First"].Success)
                            {
                                num = 1;
                            }

                            foreach (Capture capture in match.Groups["Day"].Captures)
                            {
                                WeekDay wd = new WeekDay();
                                wd.Offset    = num;
                                wd.DayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), capture.Value, true);
                                r.ByDay.Add(wd);
                            }
                        }
                        else if ((match = Regex.Match(item, @"at\s+(?<Hour>\d{1,2})(:(?<Minute>\d{2})((:|\.)(?<Second>\d{2}))?)?\s*(?<Meridian>(a|p)m?)?", RegexOptions.IgnoreCase)).Success)
                        {
                            int hour, minute, second;

                            if (int.TryParse(match.Groups["Hour"].Value, out hour))
                            {
                                // Adjust for PM
                                if (match.Groups["Meridian"].Success &&
                                    match.Groups["Meridian"].Value.ToUpper().StartsWith("P"))
                                {
                                    hour += 12;
                                }

                                r.ByHour.Add(hour);

                                if (match.Groups["Minute"].Success &&
                                    int.TryParse(match.Groups["Minute"].Value, out minute))
                                {
                                    r.ByMinute.Add(minute);
                                    if (match.Groups["Second"].Success &&
                                        int.TryParse(match.Groups["Second"].Value, out second))
                                    {
                                        r.BySecond.Add(second);
                                    }
                                }
                            }
                        }
                        else if ((match = Regex.Match(item, @"^\s*until\s+(?<DateTime>.+)$", RegexOptions.IgnoreCase)).Success)
                        {
                            DateTime dt = DateTime.Parse(match.Groups["DateTime"].Value);
                            DateTime.SpecifyKind(dt, DateTimeKind.Utc);

                            r.Until = dt;
                        }
                        else if ((match = Regex.Match(item, @"^\s*for\s+(?<Count>\d+)\s+occurrences\s*$", RegexOptions.IgnoreCase)).Success)
                        {
                            int count;
                            if (!int.TryParse(match.Groups["Count"].Value, out count))
                            {
                                return(false);
                            }
                            else
                            {
                                r.Count = count;
                            }
                        }
                    }
                }
                else
                {
                    // Couldn't parse the object, return null!
                    r = null;
                }

                if (r != null)
                {
                    CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until);
                    CheckRange("INTERVAL", r.Interval, 0, int.MaxValue);
                    CheckRange("COUNT", r.Count, 0, int.MaxValue);
                    CheckRange("BYSECOND", r.BySecond, 0, 59);
                    CheckRange("BYMINUTE", r.ByMinute, 0, 59);
                    CheckRange("BYHOUR", r.ByHour, 0, 23);
                    CheckRange("BYMONTHDAY", r.ByMonthDay, -31, 31);
                    CheckRange("BYYEARDAY", r.ByYearDay, -366, 366);
                    CheckRange("BYWEEKNO", r.ByWeekNo, -53, 53);
                    CheckRange("BYMONTH", r.ByMonth, 1, 12);
                    CheckRange("BYSETPOS", r.BySetPosition, -366, 366);
                }
            }

            return(r);
        }