using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using BeWo.Report;
using BeWo.Data.Entities;
using BeWo.Data.Access;
using BeWo.Service.Configuration;
using BeWo.Service.ServiceImplementations;
using BS.Shared.Core;
using BS.Shared.Extensions;
using DevExpress.XtraCharts.Native;


namespace BeWo.Report.ReportObjects
{
	public class MitarbeiterstundenkontoBerechnung
	{
		private decimal maxWochenstunden = 40;
		private AppSettings appSettings;

		public MitarbeiterstundenkontoBerechnung()
		{
			appSettings = AppSettings.CreateSettings();
		}

		public MitarbeiterstundenkontoBerechnung(decimal maxWochenstunden) : this()
		{
			this.maxWochenstunden = maxWochenstunden;
		}

		public virtual void CreateReport(MitarbeiterstundenkontoRO ro)
		{
			CreateEmployeeDetails(ro);

			CreateTeamDetails(ro);

			CreateTotals(ro);
			
			
		}

		public virtual void CreateTotals(MitarbeiterstundenkontoRO ro)
		{
			foreach (var empDetail in ro.EmployeeDetailList)
			{
				
				ro.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit;
				ro.FlsSollGesamt += empDetail.FlsSoll;
				ro.FlsIstGesamt += empDetail.FlsIst;
				ro.MittelbarIst += empDetail.MittelbarIst;
				ro.FlsDiffGesamt += empDetail.FlsDiff;
				ro.StundenGesamt += empDetail.StundenGesamtIst;
			}

			if (ro.FlsSollGesamt != 0)
				ro.RelationIstSollGesamt = (ro.FlsIstGesamt / ro.FlsSollGesamt) * 100m;

			if (ro.FlsIstGesamt + ro.MittelbarIst != 0)
				ro.RelationFlsZuMittelbar = (ro.FlsIstGesamt / (ro.FlsIstGesamt + ro.MittelbarIst)) * 100;
		}

		public virtual void CreateTeamDetails(MitarbeiterstundenkontoRO ro)
		{
			foreach (var teamDetail in ro.TeamDetailList)
			{
				foreach (var empDetail in teamDetail.EmployeeDetailList)
				{
					teamDetail.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit;
					teamDetail.FlsSollGesamt += empDetail.FlsSoll;
					teamDetail.FlsIstGesamt += empDetail.FlsIst;
					teamDetail.MittelbarIst += empDetail.MittelbarIst;
					teamDetail.FlsDiffGesamt += empDetail.FlsDiff;
					teamDetail.StundenGesamt += empDetail.StundenGesamtIst;
				}
				if (teamDetail.FlsSollGesamt != 0)
					teamDetail.RelationIstSollGesamt = (teamDetail.FlsIstGesamt / teamDetail.FlsSollGesamt) * 100m;

				if (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst != 0)
					teamDetail.RelationFlsZuMittelbar = (teamDetail.FlsIstGesamt / (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst)) * 100;
			}

			
		}

		public virtual void CreateEmployeeDetails(MitarbeiterstundenkontoRO ro)
		{
			foreach (var empDetail in ro.EmployeeDetailList)
			{
				DateTime monatsAnfang = ro.ReportStartDate;
				DateTime monatsEnde = monatsAnfang.AddMonths(1);
				DateTime berichtsAnfang = monatsAnfang;
				DateTime berichtsEnde = monatsEnde;

				Employee emp = empDetail.Employee;
				
				decimal bu = 0;

				Contract c = emp.GetValidContractForDate(ro.ReportStartDate);

				if (c == null) // Kein Vertrag vom 1. gefunden, gucken, ob es einen gibt, der mitten im Monat beginnt.
				{
					var c2 = GetNextValidContractForDate(ro.ReportStartDate, emp);
					if (c2 != null && c2.EntryDate.HasValue && c2.EntryDate.Value < ro.ReportEndDate)
						c = c2;
				}
				if (c != null)
				{
					if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang)
					{
						berichtsAnfang = c.EntryDate.Value;
					}
					if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
					{
						berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1);
					}
					empDetail.HasContract = true;
				}

				decimal flsIst = 0;
				decimal mittelbarIst = 0;
				decimal weeklyTotalHours = 0;
				decimal flsSollGesamt = 0;
				decimal? fehlZeitenGesamt = null;
				decimal? tageUrlaubGesamt = null;
				decimal? tageKrankheitGesamt = null;
				decimal? abwesenheitTageGesamt = null;
				decimal workdaysTotal = 0;
				decimal stundenSollGesamt = 0;
				decimal tatsaechlicheSollStdGesamt = 0;

				IList<ServiceRecord> recordsAll = DAOFactory.SearchDAO.FindServiceRecords(emp.Oid, null);
				recordsAll = AnalysisServiceImp.FilterActiveServiceRecords(recordsAll);

				IList<ServiceRecord> recordsThisMonth = new List<ServiceRecord>();
				IList<ServiceRecord> groupFilteredRecords = new List<ServiceRecord>();
				Dictionary<long, bool> groupBookingDict = new Dictionary<long, bool>();
				foreach (var item in recordsAll)
				{

					if (!item.GroupOid.HasValue || !groupBookingDict.ContainsKey(item.GroupOid.Value))
					{
						groupFilteredRecords.Add(item);
						if (item.GroupOid.HasValue)
						{
							groupBookingDict.Add(item.GroupOid.Value, true);
						}
					}
				}

				while (berichtsAnfang < berichtsEnde)
				{
					var arbeitstage = GetArbeitstageImBereich(berichtsAnfang, berichtsEnde, emp);
					workdaysTotal += arbeitstage;
					weeklyTotalHours = 0;
					if (c != null)
					{
						if (c.WeeklyTotalHours.HasValue)
						{
							weeklyTotalHours = c.WeeklyTotalHours.Value;
							bu = c.WeeklyTotalHours.Value/this.maxWochenstunden;
						}

						//if (c.EntryDate > ro.ReportStartDate)
						//	bu = 0;

					}
					var stundenSoll = arbeitstage * (weeklyTotalHours / 5m);
					stundenSollGesamt += stundenSoll;

					tatsaechlicheSollStdGesamt += stundenSoll;

					foreach (var item in groupFilteredRecords)
					{
						if (!IsServiceRecordFehlzeit(item))
						{
							if (item.Start.Value >= berichtsAnfang && item.Start.Value < berichtsEnde)
							{

								if (AddServiceRecordToDuration(item))
								{
									decimal duration = 0;

									if (item.GroupRoundedDuration.HasValue)
									{
										duration = item.GroupRoundedDuration.Value;
									}
									else
									{
										if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
										{
											duration = item.RoundedDuration;
										}
										else
										{
											duration = (decimal) item.End.Value.Subtract(item.Start.Value).TotalMinutes;
										}
									}

									if (item.ServiceDescription != null &&
									    item.ServiceDescription.ServiceCategory != null &&
									    item.ServiceDescription.ServiceCategory.IsBillable)
										flsIst += duration;
									else
										mittelbarIst += duration;
								}
								recordsThisMonth.Add(item);
							}
						}
					}

					decimal? fehlZeiten = GetFehlzeiten(groupFilteredRecords, berichtsAnfang, berichtsEnde);
					if (fehlZeiten.HasValue)
					{
						if (!fehlZeitenGesamt.HasValue)
							fehlZeitenGesamt = 0;
						fehlZeitenGesamt += fehlZeiten;

						tatsaechlicheSollStdGesamt -= fehlZeiten.Value;
					}

					var ath = GetAbsenceTimeDays(emp.AbsenceTimes, berichtsAnfang, berichtsEnde);

					if (ath.TageUrlaub.HasValue)
					{
						if (!tageUrlaubGesamt.HasValue)
							tageUrlaubGesamt = 0;
						tageUrlaubGesamt += ath.TageUrlaub;
					}
					if (ath.TageKrankheit.HasValue)
					{
						if (!tageKrankheitGesamt.HasValue)
							tageKrankheitGesamt = 0;
						tageKrankheitGesamt += ath.TageKrankheit;
					}
					if (ath.TageGesamt.HasValue)
					{
						if (!abwesenheitTageGesamt.HasValue)
							abwesenheitTageGesamt = 0;
						abwesenheitTageGesamt += ath.TageGesamt;

						tatsaechlicheSollStdGesamt -= (ath.TageGesamt.Value * (weeklyTotalHours / 5m));
					}

					flsSollGesamt = tatsaechlicheSollStdGesamt;

					if (c != null && c.WeeklyFLS.HasValue && c.WeeklyTotalHours.HasValue && c.WeeklyTotalHours.Value != 0)
						flsSollGesamt *= c.WeeklyFLS.Value / c.WeeklyTotalHours.Value;
					
					berichtsAnfang = berichtsEnde;
					berichtsEnde = monatsEnde;
					c = GetNextValidContractForDate(berichtsAnfang, emp);

					if (c != null)
					{
						if (c.EntryDate.HasValue && c.EntryDate >= berichtsAnfang)
							berichtsAnfang = c.EntryDate.Value;
						if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
						{
							berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1);
						}
					}
				}

				if (AddRateFactorToIst() != 1)
				{
					flsIst *= AddRateFactorToIst();
				}
				flsIst /= 60m;
				mittelbarIst /= 60m;

				empDetail.WorkDaysThisMonth = workdaysTotal;
				//decimal stundenSollMax = arbeitstage * (maxWochenstunden / 5) * bu;
				
				empDetail.SollProTag = (weeklyTotalHours/5m);
				empDetail.StundenSollMax = stundenSollGesamt;


				empDetail.Fehlzeiten = fehlZeitenGesamt;
				empDetail.TageUrlaub = tageUrlaubGesamt;
				empDetail.TageKrankheit = tageKrankheitGesamt;
				empDetail.UrlaubUndKrankheit = abwesenheitTageGesamt;

				empDetail.StundenSollOhneUrlaubKrankheit = tatsaechlicheSollStdGesamt;

				empDetail.FlsSoll = flsSollGesamt;

				

				empDetail.MittelbarIst = mittelbarIst;
				empDetail.Bu = bu;
				empDetail.FlsIst = flsIst;
				empDetail.FlsDiff = flsIst - empDetail.FlsSoll;
				empDetail.StundenMittelbarIst = mittelbarIst;
				empDetail.StundenGesamtIst = flsIst + mittelbarIst;
				if (empDetail.FlsSoll != 0)
					empDetail.RelationIstSoll = (empDetail.FlsIst/empDetail.FlsSoll)*100m;


				if (flsIst + mittelbarIst != 0)
					empDetail.RelationFlsZuMittelbar = (flsIst/(flsIst + mittelbarIst))*100;


				decimal? ueberstunden = empDetail.StundenGesamtIst - empDetail.StundenSollOhneUrlaubKrankheit;
				decimal? ueberstundenVormonat;


				decimal alteExtraUeberstunden = 0;
				BerechneUeberstundenBis(emp, groupFilteredRecords, ro.ReportStartDate, out ueberstundenVormonat,
					out alteExtraUeberstunden);


				decimal extraStunden = 0;

				BerechneExtraUeberstunden(emp, monatsAnfang, monatsEnde, out extraStunden);

				if (ShouldCreateDayDetails())
				{
					empDetail.Days = CreateDayDetails(monatsAnfang, monatsEnde, empDetail, recordsThisMonth);
					var ah = GetAdditionalHours(empDetail.Days, monatsAnfang, monatsEnde, empDetail, recordsThisMonth);
					ueberstunden += ah;
					empDetail.StundenGesamtIst += ah;
				}


				empDetail.Ueberstunden = ueberstunden;
				empDetail.UeberstundenVormonat = ueberstundenVormonat;
				if (!empDetail.UeberstundenGesamt.HasValue)
					empDetail.UeberstundenGesamt = ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden;
				else
					empDetail.UeberstundenGesamt += ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden;
				empDetail.UeberstundenAusbezahlt = extraStunden;

				//Urlaubskonto (todo)
				empDetail.UrlaubGesamtImAktuellenJahr =
					GetAbsenceTimeDays(emp.AbsenceTimes, new DateTime(monatsAnfang.Year, 1, 1), monatsEnde).TageUrlaub;
			}

		}

		public virtual bool ShouldCreateDayDetails()
		{
			return false;
		}

		public virtual IList<MitarbeiterstundenkontoRO.DayDetail> CreateDayDetails(DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList<ServiceRecord> recordsAll)
		{
			
			Dictionary<int, MitarbeiterstundenkontoRO.DayDetail> dayToDetail = new Dictionary<int, MitarbeiterstundenkontoRO.DayDetail>();

			for (int i = berichtStart.Day; i <= berichtEnde.AddDays(-1).Day; i++)
			{
				MitarbeiterstundenkontoRO.DayDetail dd = CreateDayDetail(new DateTime(berichtStart.Year, berichtStart.Month, i), ed);
				dayToDetail.Add(i, dd);
			}

			foreach (var sr in recordsAll)
			{
				if (sr.Start.HasValue && sr.Start >= berichtStart && sr.Start < berichtEnde && sr.End.HasValue)
				{
					var dd = dayToDetail[sr.Start.Value.Day];

					if (!IsServiceRecordFehlzeit(sr) && AddServiceRecordToDuration(sr))
					{
						decimal duration = 0;
						if (sr.GroupRoundedDuration.HasValue)
						{
							duration = sr.GroupRoundedDuration.Value;
						}
						else
						{
							if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
							{
								duration = sr.RoundedDuration;
							}
							else
							{
								duration = (decimal)sr.End.Value.Subtract(sr.Start.Value).TotalMinutes;
							}
						}
						
						dd.MinutesTotal += duration;
						if (dd.ServiceRecords == null)
							dd.ServiceRecords = new List<ServiceRecord>();
						dd.ServiceRecords.Add(sr);

						CalculateSpecialHours(dd, sr, duration);
						
						
					}
				}
			}

			//Berechne von bis Zeiten
			foreach (var ddKeyValue in dayToDetail)
			{
				var dd = ddKeyValue.Value;
				
				if (dd.ServiceRecords != null)
				{
					var orderedList = dd.ServiceRecords.OrderBy(i => i.Start);

					DateTime lastEnd = DateTime.MinValue;
					var gapCount = -1;

					foreach (var sr in orderedList)
					{
						decimal duration = 0;
						if (sr.GroupRoundedDuration.HasValue)
						{
							duration = sr.GroupRoundedDuration.Value;
						}
						else
						{
							if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
							{
								duration = sr.RoundedDuration;
							}
							else
							{
								duration = (decimal)sr.End.Value.Subtract(sr.Start.Value).TotalMinutes;
							}
						}

						DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration);
						if (sr.Start > lastEnd)
						{
							gapCount++;
							
						}
						if (dtEnd > lastEnd)
						{
							lastEnd = dtEnd;
						}
						if (gapCount == 0)
						{
							if (!dd.StartDateTime1.HasValue || sr.Start < dd.StartDateTime1)
							{
								dd.StartDateTime1 = sr.Start;
							}
							if (!dd.EndDateTime1.HasValue || dtEnd > dd.EndDateTime1)
							{
								dd.EndDateTime1 = dtEnd;
							}
						}
						else if (gapCount == 1)
						{
							if (!dd.StartDateTime2.HasValue || sr.Start < dd.StartDateTime2)
							{
								dd.StartDateTime2 = sr.Start;
							}
							if (!dd.EndDateTime2.HasValue || dtEnd > dd.EndDateTime2)
							{
								dd.EndDateTime2 = dtEnd;
							}
						}
						else if (gapCount == 2)
						{
							if (!dd.StartDateTime3.HasValue || sr.Start < dd.StartDateTime3)
							{
								dd.StartDateTime3 = sr.Start;
							}
							if (!dd.EndDateTime3.HasValue || dtEnd > dd.EndDateTime3)
							{
								dd.EndDateTime3 = dtEnd;
							}
						}
						else 
						{
							if (!dd.StartDateTime4.HasValue || sr.Start < dd.StartDateTime4)
							{
								dd.StartDateTime4 = sr.Start;
							}
							if (!dd.EndDateTime4.HasValue || dtEnd > dd.EndDateTime4)
							{
								dd.EndDateTime4 = dtEnd;
							}
						}
					}
				}
			}

			


			return dayToDetail.Values.OrderBy(dd => dd.Date).ToList();
		}

		public virtual void CalculateSpecialHours(MitarbeiterstundenkontoRO.DayDetail dd, ServiceRecord sr, decimal duration)
		{
			if (sr.Start.Value.DayOfWeek == DayOfWeek.Sunday)
			{
				dd.HoursSunday += duration / 60;
			}
			else if (IstFeiertag(sr.Start.Value))
			{
				dd.HoursHoliday += duration / 60;
			}
			else if (dd.NightEnd.HasValue && dd.NightStart.HasValue)
			{
				DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration);

				if (dtEnd.Hour >= dd.NightStart.Value)
				{
					DateTime dtStart = dtEnd.Date.AddHours(dd.NightStart.Value);
					if (sr.Start.Value > dtStart)
						dtStart = sr.Start.Value;

					TimeSpan ts = dtEnd.Subtract(dtStart);
					dd.SpecialHours += (decimal)ts.TotalMinutes / 60;
				}

				if (sr.Start.Value.Hour < dd.NightEnd)
				{
					DateTime dtEnd2 = sr.Start.Value.Date.AddHours(dd.NightEnd.Value);
					if (dtEnd < dtEnd2)
						dtEnd2 = dtEnd;
					TimeSpan ts = dtEnd2.Subtract(sr.Start.Value);
					dd.SpecialHours += (decimal)(Math.Round(ts.TotalMinutes, 0) / 60);
				}
			}
		}

		public virtual MitarbeiterstundenkontoRO.DayDetail CreateDayDetail(DateTime date, MitarbeiterstundenkontoRO.EmployeeDetail ed)
		{
			MitarbeiterstundenkontoRO.DayDetail dd = new MitarbeiterstundenkontoRO.DayDetail();
			dd.Date = date.Date;
			dd.NightStart = 20;
			dd.NightEnd = 6;
			dd.MaxHoursUntilBreak = 6;
			dd.BreakMinutes = 30;

			CalculateAbsenceTimesForDay(dd, ed);
			return dd;
		}

		public virtual void CalculateAbsenceTimesForDay(MitarbeiterstundenkontoRO.DayDetail dd, MitarbeiterstundenkontoRO.EmployeeDetail ed)
		{
			var absenceTimes = ed.Employee.AbsenceTimes;
			
			if (absenceTimes != null)
			{
				foreach (var absenceTime in absenceTimes)
				{
					
					if (IsUrlaubAbsenceTime(absenceTime) || IsKrankheitAbsenceTime(absenceTime))
					{
						DateTimeSpan span = new DateTimeSpan();

						span.StartDate = dd.Date;

						if (absenceTime.Start.HasValue && absenceTime.Start.Value > span.StartDate)
						{
							span.StartDate = absenceTime.Start.Value;
						}
						span.EndDate = dd.Date.AddDays(1);
						if (absenceTime.End.HasValue && absenceTime.End.Value.Date.AddDays(1) < span.EndDate)
						{
							span.EndDate = absenceTime.End.Value.Date.AddDays(1);
						}
						if (span.EndDate > dd.Date && span.StartDate < dd.Date.AddDays(1))
						{
							DateTime day = span.StartDate;
							var faktor = GetFeiertagsFaktor(day);

							if (faktor < 1 && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
							{
								var soll = ed.SollProTag;
								if (faktor != 0)
								{
									soll *= faktor; //halbe Feiertage etc. berücksichtigen
								}
								if (IsKrankheitAbsenceTime(absenceTime))
								{
									dd.HoursSick = soll;
								}
								else if (IsUrlaubAbsenceTime(absenceTime))
								{
									dd.HoursInVacation = soll;
								}
								dd.MinutesTotal += soll * 60;
							}
						}
					}
					
					//}
				}
			}
			
		}

		public virtual bool IsKrankheitAbsenceTime(AbsenceTime absenceTime)
		{
			return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) &&
			       (absenceTime.AbsenceReason.Description.ToLower().IndexOf("arbeitsunfähig") >= 0
			        || absenceTime.AbsenceReason.Description.ToLower().IndexOf("krank") >= 0);
		}

		public virtual bool IsUrlaubAbsenceTime(AbsenceTime absenceTime)
		{
			return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) &&
				   absenceTime.AbsenceReason.Description.ToLower().IndexOf("urlaub") >= 0;
		}

		public virtual bool AddServiceRecordToDuration(ServiceRecord item)
		{
			return true;
		}

		public virtual decimal AddRateFactorToIst()
		{
			return 1;
		}

		private void BerechneUeberstundenBis(Employee emp, IList<ServiceRecord> records, DateTime monat, out decimal? ueberstunden, out decimal alteExtraUeberstunden)
		{
			ueberstunden = null;
			alteExtraUeberstunden = 0;

			Contract c = emp.FirstContract;
			
			if (c != null)
			{
				Dictionary<String, IList<ServiceRecord>> srDict = CreateServiceRecordDictByDate(records);

				DateTime start = c.EntryDate.HasValue ? c.EntryDate.Value : new DateTime(2011, 1, 1);
				while (c != null && start < monat)
				{
					DateTime contractEnd = c.ContractEndDate.HasValue ? c.ContractEndDate.Value : DateTime.MaxValue;
					decimal bu = 0;
					decimal weeklyTotalHours = this.maxWochenstunden;
					
					if (c.WeeklyTotalHours.HasValue)
					{
						weeklyTotalHours = c.WeeklyTotalHours.Value;
						bu = c.WeeklyTotalHours.Value / maxWochenstunden;
					}


					while (start < monat && start < contractEnd)
					{
						decimal flsIst = 0;
						decimal mittelbarIst = 0;
						decimal minutenMonat = 0;
						DateTime ende = new DateTime(start.Year, start.Month, 1).AddMonths(1);
						if (contractEnd < DateTime.MaxValue && contractEnd.AddDays(1) < ende)
							ende = contractEnd.AddDays(1);
						
						//IList<ServiceRecord> recordsThisMonth = new List<ServiceRecord>();
						String key = String.Format("{0:yyyyMM}", start);

						if (srDict.ContainsKey(key))
						{
							foreach (var item in srDict[key])
							{
								if (item.Start.HasValue && item.Start >= start && item.Start < ende)
								{
									if (AddServiceRecordToDuration(item))
									{
										decimal duration = 0;

										if (item.GroupRoundedDuration.HasValue)
										{
											duration = item.GroupRoundedDuration.Value;
										}
										else
										{
											if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
											{
												duration = item.RoundedDuration;
											}
											else
											{
												duration = (decimal)item.End.Value.Subtract(item.Start.Value).TotalMinutes;
											}
										}
										minutenMonat += duration;

										if (AddRateFactorToIst() != 1)
										{
											if (item.ServiceDescription != null &&
											    item.ServiceDescription.ServiceCategory != null &&
											    item.ServiceDescription.ServiceCategory.IsBillable)
												flsIst += duration;
											else
												mittelbarIst += duration;
										}
										//recordsThisMonth.Add(item);
									}
								}
							}
						}

						decimal arbeitstage = GetArbeitstageImBereich(start, ende, emp);

						decimal maxSollMonat = arbeitstage * (maxWochenstunden / 5);
						decimal sollMitarbeiter = maxSollMonat * bu;
						
						var ath = GetAbsenceTimeDays(emp.AbsenceTimes, start, ende);
						decimal? fehlzeit = GetFehlzeiten(records, start, ende);

						if (fehlzeit.HasValue)
						{
							sollMitarbeiter -= fehlzeit.Value;
							maxSollMonat -= fehlzeit.Value;
						}
						if (ath.TageGesamt.HasValue)
						{
							sollMitarbeiter -= (ath.TageGesamt.Value * (maxWochenstunden / 5) * bu);
							maxSollMonat -= (ath.TageGesamt.Value * (maxWochenstunden / 5) * bu);
						}

						decimal stundenMonat = minutenMonat/60m;
						if (AddRateFactorToIst() != 1)
						{
							stundenMonat = ((flsIst*AddRateFactorToIst()) + mittelbarIst)/60m;
						}


						if (!ueberstunden.HasValue)
							ueberstunden = 0;
						ueberstunden += stundenMonat - sollMitarbeiter;

						decimal extraUeberstunden = 0;
						BerechneExtraUeberstunden(emp, start, ende, out extraUeberstunden);
						ueberstunden += extraUeberstunden;

						alteExtraUeberstunden += extraUeberstunden;


						if (ShouldCreateDayDetails())
						{
							MitarbeiterstundenkontoRO.EmployeeDetail ed = new MitarbeiterstundenkontoRO.EmployeeDetail();
							ed.SollProTag = weeklyTotalHours/5;
							ed.Employee = emp;
							var days = CreateDayDetails(start, ende, ed, records);
							var ah = GetAdditionalHours(days, start, ende, ed, records);
							ueberstunden += ah;
						}

						start = ende;
					}

					if (start >= contractEnd)
					{
						var ctemp = GetNextValidContractForDate(start, emp);
						if (ctemp != null && ctemp.Oid != c.Oid)
						{
							c = ctemp;
						}
						else
						{
							c = null;
						}
						
						if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
							start = c.EntryDate.Value;
						else
						{
							start = start.AddMonths(1);
							c = GetNextValidContractForDate(start, emp);
							if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
								start = c.EntryDate.Value;
						}
					}
				}
				
			}
		}

		public virtual Dictionary<string, IList<ServiceRecord>> CreateServiceRecordDictByDate(IList<ServiceRecord> records)
		{
			var dict = new Dictionary<string, IList<ServiceRecord>>();
			foreach (var item in records)
			{
				if (!IsServiceRecordFehlzeit(item))
				{
					if (item.Start.HasValue)
					{
						String key = String.Format("{0:yyyyMM}", item.Start.Value);

						IList<ServiceRecord> list;
						if (!dict.ContainsKey(key))
						{
							list = new List<ServiceRecord>();
							dict.Add(key, list);
						}
						else
						{
							list = dict[key];
						}
						list.Add(item);
					}
				}
			}
			return dict;
		}

		public virtual decimal GetAdditionalHours(IList<MitarbeiterstundenkontoRO.DayDetail> days, DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList<ServiceRecord> recordsAll)
		{
			decimal addHours = 0;

			foreach (var dayDetail in days)
			{
				if (dayDetail.BreakMinutes.HasValue && dayDetail.MaxHoursUntilBreak.HasValue)
				{
					if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue)
					{
						if (dayDetail.EndDateTime1.Value.Subtract(dayDetail.StartDateTime1.Value).TotalHours >
						    dayDetail.MaxHoursUntilBreak.Value)
						{
							addHours -= (dayDetail.BreakMinutes.Value/60m);
							dayDetail.MinutesTotal -= dayDetail.BreakMinutes.Value;
						}
					}
					if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue)
					{
						if (dayDetail.EndDateTime2.Value.Subtract(dayDetail.StartDateTime2.Value).TotalHours >
						    dayDetail.MaxHoursUntilBreak.Value)
						{
							addHours -= (dayDetail.BreakMinutes.Value/60m);
							dayDetail.MinutesTotal -= dayDetail.BreakMinutes.Value;
						}
					}
					if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue)
					{
						if (dayDetail.EndDateTime3.Value.Subtract(dayDetail.StartDateTime3.Value).TotalHours >
						    dayDetail.MaxHoursUntilBreak.Value)
						{
							addHours -= (dayDetail.BreakMinutes.Value/60m);
							dayDetail.MinutesTotal -= dayDetail.BreakMinutes.Value;
						}
					}
					if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue)
					{
						if (dayDetail.EndDateTime4.Value.Subtract(dayDetail.StartDateTime4.Value).TotalHours >
							dayDetail.MaxHoursUntilBreak.Value)
						{
							addHours -= (dayDetail.BreakMinutes.Value / 60m);
							dayDetail.MinutesTotal -= dayDetail.BreakMinutes.Value;
						}
					}
				}
			}

			return addHours;
		}

		public virtual void BerechneExtraUeberstunden(Employee emp, DateTime start, DateTime ende, out decimal stunden)
		{
			stunden = 0;
			
			if (emp.Overtimes != null)
			{
				foreach (var ot in emp.Overtimes)
				{
					if (ot.Date >= start && ot.Date < ende)
					{
						stunden += ot.Betrag;
					}
				}
			}
		}

		public Contract GetNextValidContractForDate(DateTime dt, Employee emp)
		{
			Contract contractNextToStartDate = emp.GetValidContractForDate(dt);

			if (contractNextToStartDate == null)
			{

				foreach (var item in emp.Contracts)
				{
					if (item.EntryDate.HasValue && item.EntryDate.Value >= dt)
					{
						if (contractNextToStartDate == null || item.EntryDate.Value < contractNextToStartDate.EntryDate.Value)
						{
							contractNextToStartDate = item;
						}
					}


				}
			}
			return contractNextToStartDate;
		}

		public virtual AbsenceTimeDays GetAbsenceTimeDays(IList<AbsenceTime> absenceTimes, DateTime start, DateTime end)
		{
			var atd = new AbsenceTimeDays();

			decimal? tage = null;
			if (absenceTimes != null)
			{
				foreach (var item in absenceTimes)
				{
					
					DateTimeSpan span = new DateTimeSpan();

					span.StartDate = start;

					if (item.Start.HasValue && item.Start.Value > span.StartDate)
					{
						span.StartDate = item.Start.Value;
					}
					span.EndDate = end;
					if (item.End.HasValue && item.End.Value.Date.AddDays(1) < span.EndDate)
					{
						span.EndDate = item.End.Value.Date.AddDays(1);
					}
					if (span.EndDate >= start && span.StartDate < end)
					{
						DateTime day = span.StartDate;
						while (day < end && day < span.EndDate)
						{
							var faktor = GetFeiertagsFaktor(day);

							if (faktor < 1 && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
							{
								if (!tage.HasValue)
									tage = 0;

								var tag = 1m;
								var std = 24m;
								if (faktor != 0)
								{
									tag *= faktor;
									std *= faktor;
								}
								
								
								
								tage += std;

								if (IsKrankheitAbsenceTime(item))
								{
									if (!atd.TageKrankheit.HasValue)
										atd.TageKrankheit = 0;

									atd.TageKrankheit += tag;
								}
								if (IsUrlaubAbsenceTime(item))
								{
									if (!atd.TageUrlaub.HasValue)
										atd.TageUrlaub = 0;

									atd.TageUrlaub += tag;
								}
							}
							day = day.AddDays(1);
						}
						
					}
					//}
				}
			}
			if (tage.HasValue)
				tage /= 24;
			atd.TageGesamt = tage;
			return atd;
		}

		public virtual decimal? GetFehlzeiten(IList<ServiceRecord> records, DateTime monat)
		{

			return GetFehlzeiten(records, monat, monat.AddMonths(1));
		}

		public virtual decimal? GetFehlzeiten(IList<ServiceRecord> records, DateTime start, DateTime ende)
		{
			decimal? stunden = null;
			decimal minuten = 0;
			if (records != null)
			{
				foreach (var item in records)
				{
					if (IsServiceRecordFehlzeit(item))
					{
						if (item.Start.Value >= start && item.Start.Value < ende)
						{
							if (item.GroupRoundedDuration.HasValue)
							{
								minuten += item.GroupRoundedDuration.Value;
							}
							else
							{
								if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
								{
									minuten += item.RoundedDuration;
								}
								else
								{
									minuten += (decimal)item.End.Value.Subtract(item.Start.Value).TotalMinutes;
								}
							}
						}
					}
				}
			}
			if (minuten != 0)
				stunden = minuten / 60;
			return stunden;
		}

		
		public virtual decimal GetArbeitstageImMonat(DateTime datum, Employee emp)
		{
			Contract c = GetNextValidContractForDate(datum, emp);
			if (c == null)
				return 0;
			
			DateTime start = new DateTime(datum.Year, datum.Month, 1);

			DateTime end = start.AddMonths(1);

			if (c.EntryDate.HasValue)
			{
				if (c.EntryDate >= end)
					return 0;

				if (c.EntryDate > start)
					start = c.EntryDate.Value;
			}
			if (c.ContractEndDate.HasValue && c.ContractEndDate.Value.AddDays(1) < end)
				end = c.ContractEndDate.Value.AddDays(1);

			return GetArbeitstageImBereich(start, end, emp);

		}

		public virtual decimal GetArbeitstageImBereich(DateTime von, DateTime bis, Employee emp)
		{
			DateTime dt = DateTime.Now;

			DateTime start = von;
			DateTime end = bis;

			TimeSpan ts = end.Subtract(start);

			var maxDays = (decimal)ts.TotalDays;

			while (start < end)
			{
				if (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday)
				{
					maxDays -= 1;
				}
				else
				{
					maxDays -= GetFeiertagsFaktor(start);
					//if (IstFeiertag(start))
					//	maxDays--;
				}
				start = start.AddDays(1);
			}
			return maxDays;

		}

		//gibt 1 zurück wenn ganzer Feiertag und z.B. 0.5 bei halben etc.
		public virtual decimal GetFeiertagsFaktor(DateTime start)
		{
			if (IstFeiertag(start))
			{
				return 1;
			}
			return 0;
		}

		public virtual bool IstFeiertag(DateTime start)
		{
			List<DateTime> feiertage = GetFeiertage(start.Year);

			foreach (var ft in feiertage)
			{
				if (start.Date == ft)
					return true;
			}
			return false;
		}

		public virtual List<DateTime> GetFeiertage(int year)
		{
			List<DateTime> list = new List<DateTime>();
			list.Add(new DateTime(year, 1, 1)); // "Neujahr"));
			//list.Add(new DateTime(year, 1, 6)); // , "Heilige Drei Könige"));
			list.Add(new DateTime(year, 5, 1)); // , "Tag der Arbeit"));
			//list.Add(new DateTime(year, 8, 15)); // , "Mariä Himmelfahrt"));
			list.Add(new DateTime(year, 10, 3)); // , "Tag der dt. Einheit"));
			//list.Add(new DateTime(year, 10, 31)); // , "Reformationstag"));
			list.Add(new DateTime(year, 11, 1)); // , "Allerheiligen "));
			list.Add(new DateTime(year, 12, 24)); // , "Heilig Abend"));
			list.Add(new DateTime(year, 12, 25)); // , "1. Weihnachtstag"));
			list.Add(new DateTime(year, 12, 26)); // , "2. Weihnachtstag"));
			
			DateTime osterSonntag = GetOsterSonntag(year);
			//list.Add(osterSonntag); // , "Ostersonntag"));
			//list.Add(osterSonntag.AddDays(-3)); // , "Gründonnerstag"));	q
			//list.Add(osterSonntag.AddDays(-48)); // , "Rosenmontag"));
			list.Add(osterSonntag.AddDays(-2)); // , "Karfreitag"));
			list.Add(osterSonntag.AddDays(1)); // , "Ostermontag"));
			list.Add(osterSonntag.AddDays(39)); // , "Christi Himmelfahrt"));
			//list.Add(osterSonntag.AddDays(49)); // , "Pfingstsonntag"));
			list.Add(osterSonntag.AddDays(50)); // , "Pfingstmontag"));
			list.Add(osterSonntag.AddDays(60)); // , "Fronleichnam"));


			//01.01. Neujahr
			//01.05. Tag der Arbeit
			//03.10. Tag der Deutschen Einheit
			//01.11. Allerheiligen
			//25.12. 1. Weihnachtstag
			//26.12. 2. Weihnachtstag

			//Karfreitag
			//Ostermontag
			//Christi Himmelfahrt (39 Tage nach Ostersonntag)
			//Pfingstmontag (50 Tage nach Ostersonntag)
			//Fronleichnam (60 Tage nach Ostersonntag)

			//24.12. Heilig Abend

			return list;
		}


		protected DateTime GetOsterSonntag(int year)
		{

			int g, h, c, j, l, i;

			g = year % 19;
			c = year / 100;
			h = ((c - (c / 4)) - (((8 * c) + 13) / 25) + (19 * g) + 15) % 30;
			i = h - (h / 28) * (1 - (29 / (h + 1)) * ((21 - g) / 11));
			j = (year + (year / 4) + i + 2 - c + (c / 4)) % 7;

			l = i - j;
			int month = (int)(3 + ((l + 40) / 44));
			int day = (int)(l + 28 - 31 * (month / 4));

			return new DateTime(year, month, day);

		}

		public virtual bool IsServiceRecordFehlzeit(ServiceRecord sr)
		{
			if (sr.ServiceDescription != null && sr.ServiceDescription.ServiceCategory != null && !String.IsNullOrEmpty(sr.ServiceDescription.ServiceCategory.Name) && sr.ServiceDescription.ServiceCategory.Name.ToLower().IndexOf("fehlzeit") >= 0)
				return true;
			return false;
		}
	}

	public class AbsenceTimeDays
	{
		public decimal? TageUrlaub;
		public decimal? TageKrankheit;
		public decimal? TageGesamt;
	}
}
