using System;
using System.Globalization;
using System.Linq;
using BS.Shared.Core;
using System.Collections.Generic;
using DevExpress.XtraScheduler;

namespace BS.Shared.Extensions
{
    public enum TimeUnit
    {
        Day,
        Week,
        Fortnightly,
        Month,
        Quarter,
        Halfyear,
        Year
    }

    public class DateTimeUnit
    {
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public int UnitCount { get; set; }
        public int RestDays { get; set; }
    }

    public static class DateTimeExtensions
    {
        public static DateTimeUnit GetDateTimeUnit(this DateTime Date1, DateTime Date2, TimeUnit unit) 
        {
            List<DateTime> period = new List<DateTime>() { Date1, Date2 };
            period.Sort(DateTime.Compare);

            DateTimeUnit ds = new DateTimeUnit();
            ds.StartDate = period[0];
            ds.EndDate = period[1].AddDays(1);

            int totaldays = (int)ds.EndDate.Subtract(ds.StartDate).TotalDays;

            DateTime startDate = ds.StartDate;
            switch (unit)
            {
                case TimeUnit.Day:
                    ds.UnitCount = totaldays;
                    ds.RestDays = 0;
                    break;
                case TimeUnit.Week:
                    ds.UnitCount = (int)(totaldays / 7m);
                    ds.RestDays = totaldays - (ds.UnitCount * 7);
                    break;
                case TimeUnit.Fortnightly:
                    ds.UnitCount = (int)(totaldays / 14m);
                    ds.RestDays = totaldays - (ds.UnitCount * 14);
                    break;
                case TimeUnit.Month:
                    int months = 0;
                    while (startDate.AddMonths(1) <= ds.EndDate)
                    {
                        startDate = startDate.AddMonths(1);
                        months++;
                    }
                    ds.UnitCount = months;
                    ds.RestDays = (int)ds.EndDate.Subtract(startDate).TotalDays;

                    break;
                case TimeUnit.Quarter:
                    int quarter = 0;
                    while (startDate.AddMonths(3) <= ds.EndDate)
                    {
                        startDate = startDate.AddMonths(3);
                        quarter++;
                    }
                    ds.UnitCount = quarter;
                    ds.RestDays = (int)ds.EndDate.Subtract(startDate).TotalDays;
                    break;
                case TimeUnit.Halfyear:
                    int halfyear = 0;
                    
                    while (startDate.AddMonths(6) <= ds.EndDate)
                    {
                        startDate = startDate.AddMonths(6);
                        halfyear++;
                    }
                    ds.UnitCount = halfyear;
                    ds.RestDays = (int)ds.EndDate.Subtract(startDate).TotalDays;
                    break;
                case TimeUnit.Year:
                    int year = 0;

                    while (startDate.AddYears(1) <= ds.EndDate)
                    {
                        startDate = startDate.AddYears(1);
                        year++;
                    }
                    ds.UnitCount = year;
                    ds.RestDays = (int)ds.EndDate.Subtract(startDate).TotalDays;
                    break;
                default:
                    break;
            }
            


            return ds;
        }

        public static DateTime AddMonthsDayDependent(this DateTime pDateTime, int pMonthsToAdd, bool pFirstDay)
        {
            var lResult = new DateTime(pDateTime.Year, pDateTime.Month, 1);
            lResult = lResult.AddMonths(pMonthsToAdd);
            lResult = FromWeekdayOccurence(lResult.Year, lResult.Month, pDateTime.DayOfWeek, pDateTime.GetWeekdayOccurence(pFirstDay), pFirstDay);
            lResult = lResult.AddHours(pDateTime.Hour);
            lResult = lResult.AddMinutes(pDateTime.Minute);
            lResult = lResult.AddSeconds(pDateTime.Second);
            lResult = lResult.AddMilliseconds(pDateTime.Millisecond);
            return lResult;
        }

        public static T DoIfNotNull<T>(this DateTime? pDateTime, Func<DateTime, T> pFunc)
        {
            if (pDateTime.HasValue)
            {
                return pFunc(pDateTime.Value);
            }
            else
            {
                return default(T);
            }
        }

        public static void DoIfNotNull<T>(this DateTime? pDateTime, Action<DateTime> pFunc)
        {
            if (pDateTime.HasValue)
            {
                pFunc(pDateTime.Value);
            }
        }

        public static DateTime GetInSameCalendarWeek(this DateTime pDateTime, DayOfWeek pDay)
        {
            DateTime lStart = pDateTime.GetLast(DayOfWeek.Monday);

            while (lStart.DayOfWeek != pDay)
            {
                lStart = lStart.AddDays(1);
            }

            return lStart;
        }

        public static DateTime GetLast(this DateTime pDateTime, DayOfWeek pDay)
        {
            DateTime lResult = pDateTime;

            while (lResult.DayOfWeek != pDay)
            {
                lResult = lResult.Date.AddDays(-1);
            }

            return lResult;
        }

        public static DateTime GetLastFridayIfWeekend(this DateTime pDateTime)
        {
            DateTime lResult = pDateTime;
            while (lResult.DayOfWeek == DayOfWeek.Saturday || lResult.DayOfWeek == DayOfWeek.Sunday)
            {
                lResult = lResult.Date.AddMilliseconds(-1);
            }

            return lResult;
        }

        public static DateTime GetNext(this DateTime pDateTime, DayOfWeek pDay)
        {
            DateTime lResult = pDateTime;

            while (lResult.DayOfWeek != pDay)
            {
                lResult = lResult.Date.AddDays(1);
            }

            return lResult;
        }

        public static DateTime GetNextMondayIfWeekend(this DateTime pDateTime)
        {
            DateTime lResult = pDateTime;

            while (lResult.DayOfWeek == DayOfWeek.Saturday || lResult.DayOfWeek == DayOfWeek.Sunday)
            {
                lResult = lResult.Date.AddDays(1);
            }

            return lResult;
        }

        public static int GetWeekdayOccurence(this DateTime pDateTime, bool pForward)
        {
	        if (pForward)
            {
                return (int)Math.Ceiling(pDateTime.Day / 7d);
            }

	        return (int)Math.Ceiling((DateTime.DaysInMonth(pDateTime.Year, pDateTime.Month) - pDateTime.Day) / 7d);
        }

	    public static bool InBetween(this DateTime pDateTime, DateTime pSpanStart, DateTime pSpanStop, bool ignoreTimeAndCompareWholeDays)
        {
            DateTime stop = pSpanStop;
            if (ignoreTimeAndCompareWholeDays)
            {
                if (stop < DateTime.MaxValue.Date)
                {
                    try
                    {
                        stop = stop.Date.AddDays(1);
                    }
                    catch (Exception)
                    {
                        stop = pSpanStop;
                    }
                }
                return pDateTime >= pSpanStart && pDateTime < stop;
            }
            return pDateTime >= pSpanStart && pDateTime <= stop;
        }

        public static bool InBetween(this DateTime pDateTime, DateTimeSpan pSpan, bool ignoreTimeAndCompareWholeDays)
        {
            if (pSpan == null)
            {
                return false;
            }

            return pDateTime.InBetween(pSpan.StartDateTime, pSpan.EndDateTime, ignoreTimeAndCompareWholeDays);
        }

        public static string ToMonthSlashShortYear(this DateTime pDate)
        {
            return pDate.Month + "/" + pDate.Year.ToString().Substring(2);
        }

        public static string ToVeryShortDateString(this DateTime ths)
        {
            return ths.ToShortDateString().Remove(ths.ToShortDateString().Length - 4, 2);
        }

        public static string ToMyDateTimeString(this DateTime pDate)
        {
            var lInfo = DateTimeFormatInfo.CurrentInfo;

            return lInfo.GetAbbreviatedDayName(pDate.DayOfWeek) + ", " + string.Format("{0:00}", pDate.Day) + "." + string.Format("{0:00}", pDate.Month) + "." + pDate.Year.ToString().Substring(2);
        }

        private static DateTime FromWeekdayOccurence(int pYear, int pMonth, DayOfWeek pDay, int pWeekdayOccurence, bool pFromStart)
        {
            if (pFromStart)
            {
                DateTime lResult = new DateTime(pYear, pMonth, 1);
                while (lResult.DayOfWeek != pDay)
                {
                    lResult = lResult.AddDays(1);
                }

                return lResult.AddDays(7 * (pWeekdayOccurence - 1));
            }
            else
            {
                DateTime lResult = new DateTime(pYear, pMonth, DateTime.DaysInMonth(pYear, pMonth));
                while (lResult.DayOfWeek != pDay)
                {
                    lResult = lResult.AddDays(-1);
                }

                return lResult.AddDays(-7 * (pWeekdayOccurence - 1));
            }
        }

		public static WeekOfMonth GetWeekOfMonth(this DateTime pDateTime, DayOfWeek WeekStartingOn)
	    {
			var x = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(pDateTime, CalendarWeekRule.FirstFourDayWeek, WeekStartingOn);
			var y = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(pDateTime.AddDays(1 - pDateTime.Day), CalendarWeekRule.FirstFourDayWeek, WeekStartingOn);
		    var r = x - y + 1;

		    switch (r)
		    {
				case 1:
				    return WeekOfMonth.First;
				case 2:
					return WeekOfMonth.Second;
				case 3:
					return WeekOfMonth.Third;
				case 4:
					return WeekOfMonth.Fourth;
				default:
					return WeekOfMonth.None;
		    }
	    }

		public static DateTime GetXthLastOccurenceInMonth(this DateTime pDateTime, DayOfWeek pDayOfWeek, int x)
		{
			var initialDate = new DateTime(pDateTime.Year, pDateTime.Month, DateTime.DaysInMonth(pDateTime.Year, pDateTime.Month));
			var lastOccurence = initialDate;

			for (var currentDate = initialDate; currentDate >= new DateTime(pDateTime.Year, pDateTime.Month, 1); currentDate = currentDate.AddDays(-1))
			{
				if (!currentDate.DayOfWeek.Equals(pDayOfWeek)) continue;

				lastOccurence = currentDate;
				break;
			}

			return lastOccurence.AddDays(-1*7*x);
		}

		public static int GetOccurenceNumber(this DateTime pDateTime)
		{
			var initialDate = new DateTime(pDateTime.Year, pDateTime.Month, DateTime.DaysInMonth(pDateTime.Year, pDateTime.Month));
			var lastOccurence = initialDate;
			var dayOfWeek = pDateTime.DayOfWeek;

			for (var currentDate = initialDate; currentDate >= new DateTime(pDateTime.Year, pDateTime.Month, 1); currentDate = currentDate.AddDays(-1))
			{
				if (!currentDate.DayOfWeek.Equals(dayOfWeek)) continue;

				lastOccurence = currentDate;
				break;
			}

			return Convert.ToInt32((lastOccurence - pDateTime).TotalDays / 7);
		}

		public static int GetIso8601WeekOfYear(this DateTime time)
		{
			var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
			if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
			{
				time = time.AddDays(3);
			}

			return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
		}

		public static DateTime FirstDateOfWeek(this DateTime date, int weekOfYear)
		{
			var jan1 = new DateTime(date.Year, 1, 1);
			var daysOffset = DayOfWeek.Thursday - jan1.DayOfWeek;

			var firstThursday = jan1.AddDays(daysOffset);
			var cal = CultureInfo.CurrentCulture.Calendar;
			var firstWeek = cal.GetWeekOfYear(firstThursday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

			var weekNum = weekOfYear;
			if (firstWeek <= 1)
			{
				weekNum -= 1;
			}
			var result = firstThursday.AddDays(weekNum * 7);
			return result.AddDays(-3);
		}

	    public static bool CompareShortDates(this DateTime date, DateTime pDate)
	    {
		    return date.Year == pDate.Year && date.Month == pDate.Month && date.Day == pDate.Day;
	    }

		public static bool InBetweenOrAround(this DateTime start1, DateTime end1, DateTime start2, DateTime end2)
		{
			var gleich = start1 == start2 && end1 == end2;
			var vorne = start1 < start2 && end1 > start2 && end1 < end2;
			var hinten = start1 > start2 && start1 < end2;
			var drumherum = start1 < start2 && end1 > end2;

			return gleich || vorne || hinten || drumherum;
		}

		public static double GetIntersectingMinutes(this DateTime start1, DateTime end1, DateTime start2, DateTime end2)
		{
			var e = end1 <= end2 ? end1 : end2;
			var s = start1 >= start2 ? start1 : start2;

			return s > e ? 0 : (e - s).TotalMinutes;
		}

		public static DateTime GetNextDayOfWeek(this DateTime start, DayOfWeek WeekDay)
		{
			var result = start;

			while (!result.DayOfWeek.Equals(WeekDay))
			{
				result = result.AddDays(1);
			}

			return result;
		}

	    public static DateTime GetShortDateTime(this DateTime date)
	    {
		    return new DateTime(date.Year, date.Month, date.Day);
	    }

		public static int GetDayNumberBetweenTwoDates(this DateTime anteriorDate, DateTime posteriorDate)
		{
			var value = 0;

			var shortAnteriorDate = new DateTime(anteriorDate.Year, anteriorDate.Month, anteriorDate.Day);
			var shortPosteriorDate = new DateTime(posteriorDate.Year, posteriorDate.Month, posteriorDate.Day);
			var tmpDate = shortAnteriorDate;

			while (tmpDate < shortPosteriorDate)
			{
				tmpDate = tmpDate.AddDays(1);
				value++;
			}

			return value;
		}

        /// <summary>
        /// Erzeugt aus zwei Datumsobjekten ein neues mit den Datumsangaben des ersten und den Zeitangaben des zweiten Parameters.
        /// </summary>
        /// <param name="first">Datum dessen Datewerte übernommen.</param>
        /// <param name="second">Datum dessen Zeitwerte übernommen werden.</param>
        /// <returns>Gibt ein neues Objekt der Klasse Datum mit dem Datum des ersten Parameters und der Zeit des zweiten Parameters zurück.</returns>
        public static DateTime MergeDatesByDate(this DateTime first, DateTime second)
        {
            return new DateTime(first.Year, first.Month, first.Day, second.Hour, second.Minute, second.Second);
        }
    }
}