using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;

using BeWo.Controls.Controls.Calendar;

using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts;
using BS.Shared.DataContracts.Compact;
using BS.Shared.Extensions;

namespace BeWo.ViewModel
{
    public class BookingSequenceVM : AbstractDCMapperVM<BookingSequenceDC>
    {
        public static string PropertyName_ChangedElements = "ChangedElements";

        public static string PropertyName_Employee = "Employee";

        public static string PropertyName_Frequency = "Frequency";

        public static string PropertyName_Notice = "Notice";

        public static string PropertyName_RepeatInterval = "RepeatInterval";

        public static string PropertyName_Resource = "Resource";

        public static string PropertyName_RootAppointment = "RootAppointment";

        public static string PropertyName_SequenceEnd = "SequenceEnd";

        private readonly Dictionary<int, Appointment> _ChangedElements = new Dictionary<int, Appointment>();

        private readonly List<FrequencyTranslation> _PossibleFrequencies = new List<FrequencyTranslation>
        {
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.Once,
                Translation = "Einmalig"
            },
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.Daily,
                Translation = "jeden Tag"
            },
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.Weekly
            },
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.Monthly
            },
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.MonthlyOnFirstDay
            },
            new FrequencyTranslation
            {
                Frequency = ResourceBookingFrequencyType.MonthlyOnLastDay
            },
        };

        private Dictionary<int, Appointment> _AppointmentsInSpan = new Dictionary<int, Appointment>();

        private CompactEmployeeDC _Employee;

        private ResourceBookingFrequencyType _Frequency;

        private bool _NeedsRecalculation = true;

        private string _Notice;

        private int _RepeatInterval = 1;

        private ResourceDC _Resource;

        private Appointment _RootAppointment;

        private DateTime _SequenceEnd;

        private DateTimeSpan _SpanToConsider;

        public BookingSequenceVM(BookingSequenceDC pDC) : base(pDC, pDC.SequenceOid == null)
        {
        }

        public List<Appointment> AppointmentsInSpan
        {
            get
            {
                if (_NeedsRecalculation && _SpanToConsider != null)
                {
                    Dictionary<int, Appointment> lBak = _AppointmentsInSpan;

                    _AppointmentsInSpan = CalculateAppointmentsFromSequence(_SpanToConsider);

                    if (lBak.Count > 0)
                    {
                        foreach (int lPos in lBak.Keys)
                        {
                            if (_AppointmentsInSpan.ContainsKey(lPos) && _AppointmentsInSpan[lPos] != null)
                            {
                                lBak[lPos].Duration = _AppointmentsInSpan[lPos].Duration;
                                lBak[lPos].Notice = _AppointmentsInSpan[lPos].Notice;
                                lBak[lPos].Text = _AppointmentsInSpan[lPos].Text;
                                _AppointmentsInSpan[lPos] = lBak[lPos];
                            }
                        }
                    }

                    _NeedsRecalculation = false;
                }

                return _AppointmentsInSpan.Values.ToList();
            }
        }

        public Dictionary<int, Appointment> ChangedElements
        {
            get { return _ChangedElements; }
        }

        public CompactEmployeeDC Employee
        {
            get { return _Employee; }

            set
            {
                if (AreDifferent(_Employee, value))
                {
                    _Employee = value;

                    // StoreDirtyInformation(AreDifferent(DataContract.Employee, value), PropertyName_Employee);
                    FirePropertyChanged(PropertyName_Employee);
                }
            }
        }

        public ResourceBookingFrequencyType Frequency
        {
            get { return _Frequency; }

            set
            {
                if (AreDifferent(_Frequency, value))
                {
                    _NeedsRecalculation = true;
                    _Frequency = value;
                    StoreDirtyInformation(AreDifferent(DataContract.Frequency, value), PropertyName_Frequency);
                    FirePropertyChanged(PropertyName_Frequency);
                }
            }
        }

        public string Notice
        {
            get { return _Notice; }

            set
            {
                if (AreDifferent(_Notice, value))
                {
                    _Notice = value;
                    StoreDirtyInformation(AreDifferent(DataContract.Notice, value), PropertyName_Notice);
                    FirePropertyChanged(PropertyName_Notice);
                }
            }
        }

        public List<FrequencyTranslation> PossibleFrequencies
        {
            get { return _PossibleFrequencies; }
        }

        public int RepeatInterval
        {
            get { return _RepeatInterval; }

            set
            {
                if (AreDifferent(_RepeatInterval, value))
                {
                    _NeedsRecalculation = true;
                    _RepeatInterval = value;
                    StoreDirtyInformation(AreDifferent(DataContract.RepeatInterval, value), PropertyName_RepeatInterval);
                    FirePropertyChanged(PropertyName_RepeatInterval);
                }
            }
        }

        public ResourceDC Resource
        {
            get { return _Resource; }

            set
            {
                _Resource = value;
                RootAppointment.Index = (int) value.ResourceOid.Value;
                RootAppointment.Brush = value.GetBrush();
                foreach (var iChanged in _ChangedElements)
                {
                    iChanged.Value.Index = (int) value.ResourceOid.Value;
                    iChanged.Value.Brush = value.GetBrush();
                }

                FirePropertyChanged(PropertyName_Resource);
            }
        }

        public Appointment RootAppointment
        {
            get { return _RootAppointment; }

            set
            {
                if (AreDifferent(_RootAppointment, value))
                {
                    _RootAppointment = value;
                    StoreDirtyInformation(!AreEqual(DataContract.RootBooking, _RootAppointment),
                        PropertyName_RootAppointment);
                    FirePropertyChanged(PropertyName_RootAppointment);
                    _RootAppointment.PropertyChanged += _RootAppointment_PropertyChanged;
                    _RootAppointment.Duration.PropertyChanged += Duration_PropertyChanged;
                    _RootAppointment.Duration.SynchEndDate = true;
                }
            }
        }

        public DateTime SequenceEnd
        {
            get { return _SequenceEnd; }

            set
            {
                if (AreDifferent(_SequenceEnd, value))
                {
                    value = value.Date.AddHours(23).AddMinutes(59);
                    _SequenceEnd = value;
                    StoreDirtyInformation(AreDifferent(DataContract.SequenceEnd, value), PropertyName_SequenceEnd);
                    FirePropertyChanged(PropertyName_SequenceEnd);
                }
            }
        }

        public DateTimeSpan SpanToConsider
        {
            get { return _SpanToConsider; }

            set
            {
                if (AreDifferent(_SpanToConsider, value))
                {
                    _SpanToConsider = value;
                    _NeedsRecalculation = true;
                }
            }
        }

        private bool IsChangedElementsDirty
        {
            get
            {
                if (DataContract.ChangedElements.Count != _ChangedElements.Count)
                {
                    return true;
                }

                if (
                    DataContract.ChangedElements.Count(
                        dc =>
                            !_ChangedElements.ContainsKey(dc.SequencePosition) ||
                            !AreEqual(dc, _ChangedElements[dc.SequencePosition])) > 0)
                {
                    return true;
                }

                return false;
            }
        }

        public Dictionary<int, Appointment> CalculateAppointmentsFromSequence(DateTimeSpan pSpanToConsider)
        {
            var lResult = new Dictionary<int, Appointment>();

            DateTime lCurrentStart = RootAppointment.Duration.StartDateTime;
            DateTime lCurrentEnd = RootAppointment.Duration.EndDateTime;
            int lCurrentPostion = 0;
            int lCurrentSequencePosition = 0;

            if (RootAppointment.Duration.Overlaps(pSpanToConsider))
            {
                lResult.Add(0, _RootAppointment);
            }

            lCurrentStart = Next(lCurrentStart, _Frequency);
            lCurrentEnd = Next(lCurrentEnd, _Frequency);

            if (_Frequency != ResourceBookingFrequencyType.Once)
            {
                while (lCurrentStart <= pSpanToConsider.EndDateTime && lCurrentStart <= _SequenceEnd)
                {
                    lCurrentPostion++;

                    if (lCurrentPostion%_RepeatInterval == 0)
                    {
                        lCurrentSequencePosition++;
                        if (_ChangedElements.ContainsKey(lCurrentSequencePosition))
                        {
                            if (_ChangedElements[lCurrentSequencePosition] != null &&
                                _ChangedElements[lCurrentSequencePosition].Duration.Overlaps(pSpanToConsider))
                            {
                                lResult.Add(lCurrentSequencePosition, _ChangedElements[lCurrentSequencePosition]);
                            }
                        }
                        else if (new DateTimeSpan
                        {
                            StartDateTime = lCurrentStart,
                            EndDateTime = lCurrentEnd
                        }.Overlaps(pSpanToConsider))
                        {
                            var app = new Appointment();
                            var dts = new DateTimeSpan();
                            dts.StartDateTime = lCurrentStart;
                            dts.EndDateTime = lCurrentEnd;

                            app.Duration = dts;
                            app.Notice = RootAppointment.Notice;

                            app.Index = RootAppointment.Index;
                            app.Brush = _Resource.GetBrush();
                            app.Tag = RootAppointment.Tag;

                            string text = RootAppointment.Text;
                            if (text.LastIndexOf("\n") > 0)
                            {
                                text = text.Substring(0, text.LastIndexOf("\n") + 1);
                                text += dts.ToString();
                            }

                            app.Text = text;

                            lResult.Add(lCurrentSequencePosition, app);
                        }
                    }

                    lCurrentStart = Next(lCurrentStart, _Frequency);
                    lCurrentEnd = Next(lCurrentEnd, _Frequency);
                }
            }

            return lResult;
        }

        public void ChangeElement(int pSequencePosition, Appointment pAppointment)
        {
            _ChangedElements[pSequencePosition] = pAppointment;

            StoreDirtyInformation(IsChangedElementsDirty, PropertyName_ChangedElements);
        }

        public void ClearChanged()
        {
            _NeedsRecalculation = true;
            _ChangedElements.Clear();

            StoreDirtyInformation(IsChangedElementsDirty, PropertyName_ChangedElements);
        }

        public void DeleteElement(int pSequencePosition)
        {
            _ChangedElements[pSequencePosition] = null;
            if (_AppointmentsInSpan.ContainsKey(pSequencePosition))
            {
                _AppointmentsInSpan.Remove(pSequencePosition);
            }

            StoreDirtyInformation(IsChangedElementsDirty, PropertyName_ChangedElements);
        }

        public Appointment GetAppointment(int pSequencePostion)
        {
            if (_AppointmentsInSpan.ContainsKey(pSequencePostion))
            {
                return _AppointmentsInSpan[pSequencePostion];
            }

            return null;
        }

        public int GetSequencePosition(Appointment pAppointment)
        {
            foreach (var iPair in _AppointmentsInSpan)
            {
                if (iPair.Value == pAppointment)
                {
                    return iPair.Key;
                }
            }

            return -1;
        }

        public DateTimeSpan GetSequenceSpan()
        {
            return new DateTimeSpan
            {
                StartDateTime = RootAppointment.Duration.StartDateTime,
                EndDateTime = SequenceEnd
            };
        }

        public void UpdatePossibleFrequencies(DateTime pStartDate)
        {
            DateTimeFormatInfo lDateInfo = DateTimeFormatInfo.CurrentInfo;

            _PossibleFrequencies.Single(f => f.Frequency == ResourceBookingFrequencyType.Weekly).Translation =
                "jeden " + lDateInfo.GetDayName(pStartDate.DayOfWeek);

            _PossibleFrequencies.Single(f => f.Frequency == ResourceBookingFrequencyType.Monthly).Translation =
                "jeden " + pStartDate.Day + "ten des Monats";

            _PossibleFrequencies.Single(f => f.Frequency == ResourceBookingFrequencyType.MonthlyOnFirstDay).Translation
                = "jeden " + pStartDate.GetWeekdayOccurence(true) + ". " +
                  lDateInfo.GetShortestDayName(pStartDate.DayOfWeek) + ". des Monats";

            _PossibleFrequencies.Single(f => f.Frequency == ResourceBookingFrequencyType.MonthlyOnLastDay).Translation =
                "jeden " + pStartDate.GetWeekdayOccurence(false) + ". letzten " +
                lDateInfo.GetShortestDayName(pStartDate.DayOfWeek) + ". des Monats";
        }

        protected override void InitByDataContract(BookingSequenceDC pDataContract)
        {
            string text = string.Empty;

            if (pDataContract.RootBooking.Employee != null)
            {
                text = pDataContract.RootBooking.Employee.FirstName + " " + pDataContract.RootBooking.Employee.LastName +
                       ":\n";
            }

            if (pDataContract.Resource != null)
            {
                text += pDataContract.Resource.Name + "\n";
            }

            if (!String.IsNullOrEmpty(pDataContract.RootBooking.Notice))
            {
                text += "\n";
                text += pDataContract.RootBooking.Notice;
                text += "\n";
            }

            text += "\n";

            if (pDataContract.RootBooking.Duration.StartDate.Date == pDataContract.RootBooking.Duration.EndDate.Date)
            {
                // return StartDateTime.ToMyDateTimeString() + " " + StartDateTime.ToShortTimeString() + " - " + EndDateTime.ToMyDateTimeString() + " " + EndDateTime.ToShortTimeString();
                text += String.Format("{0:ddd, dd.MM.yy HH:mm} - {1:HH:mm}",
                    pDataContract.RootBooking.Duration.StartDateTime, pDataContract.RootBooking.Duration.EndDateTime);

                // text += DataContract.RootBooking.Duration.ToString();
            }
            else
            {
                text += pDataContract.RootBooking.Duration.ToString();
            }

            RootAppointment = new Appointment
            {
                Duration = pDataContract.RootBooking.Duration,
                Notice = pDataContract.RootBooking.Notice,
                Text = text,
                Tag = pDataContract.RootBooking.Employee,
            };

            if (pDataContract.Resource != null)
            {
                RootAppointment.Index = (int) pDataContract.Resource.ResourceOid.Value;
                RootAppointment.Brush = pDataContract.Resource.GetBrush();
            }

            if (pDataContract.ChangedElements == null)
            {
                pDataContract.ChangedElements = new List<ResourceBookingDC>();
            }

            foreach (ResourceBookingDC iBooking in pDataContract.ChangedElements)
            {
	            if (!_ChangedElements.ContainsKey(iBooking.SequencePosition))
	            {
		            _ChangedElements.Add(
			            iBooking.SequencePosition,
			            iBooking.IsDeleted
				            ? null
				            : new Appointment
				            {
					            Duration = iBooking.Duration.Clone(),
					            Notice = iBooking.Notice,
					            Index = RootAppointment.Index,
					            Brush = RootAppointment.Brush,
					            Tag = iBooking.Employee
				            });
	            }
            }

            _Frequency = pDataContract.Frequency;
            _RepeatInterval = pDataContract.RepeatInterval;
            _SequenceEnd = pDataContract.SequenceEnd;
            _Resource = pDataContract.Resource;
            _Notice = pDataContract.Notice;
            _Employee = pDataContract.Employee;

            UpdatePossibleFrequencies(_RootAppointment.Duration.StartDateTime);
        }

        protected override BookingSequenceDC MapToDataContract(BookingSequenceDC pDataContract, bool doCommit)
        {
            pDataContract.Frequency = _Frequency;
            pDataContract.RepeatInterval = _RepeatInterval;
            pDataContract.Notice = _Notice;
            pDataContract.Resource = _Resource;
            pDataContract.SequenceEnd = _SequenceEnd;
            pDataContract.Employee = RootAppointment.Tag as CompactEmployeeDC;

            pDataContract.RootBooking.Duration = RootAppointment.Duration.Clone();
            pDataContract.RootBooking.Notice = RootAppointment.Notice;
            pDataContract.RootBooking.Employee = RootAppointment.Tag as CompactEmployeeDC;

            if (_Frequency == ResourceBookingFrequencyType.Once)
            {
                pDataContract.SequenceEnd = pDataContract.RootBooking.Duration.EndDateTime;
            }

            pDataContract.ChangedElements.RemoveRange(dc => !_ChangedElements.ContainsKey(dc.SequencePosition));

            foreach (ResourceBookingDC iDC in pDataContract.ChangedElements)
            {
                if (_ChangedElements[iDC.SequencePosition] != null)
                {
                    iDC.Duration = _ChangedElements[iDC.SequencePosition].Duration.Clone();
                    iDC.Notice = _ChangedElements[iDC.SequencePosition].Notice;
                    iDC.Employee = _ChangedElements[iDC.SequencePosition].Tag as CompactEmployeeDC;
                }
                else
                {
                    iDC.IsDeleted = true;
                }
            }

            foreach (
                var iEntry in
                    _ChangedElements.Where(
                        entry =>
                            pDataContract.ChangedElements.SingleOrDefault(dc => dc.SequencePosition == entry.Key) ==
                            null))
            {
                if (iEntry.Value == null)
                {
                    pDataContract.ChangedElements.Add(
                        new ResourceBookingDC
                        {
                            SequencePosition = iEntry.Key,
                            IsDeleted = true,
                            Duration = null
                        });
                }
                else
                {
                    pDataContract.ChangedElements.Add(
                        new ResourceBookingDC
                        {
                            SequencePosition = iEntry.Key,
                            IsDeleted = false,
                            Duration = iEntry.Value.Duration.Clone(),
                            Notice = iEntry.Value.Notice,
                            Employee = iEntry.Value.Tag as CompactEmployeeDC
                        });
                }
            }

            return pDataContract;
        }

        private bool AreEqual(ResourceBookingDC x, Appointment y)
        {
            NullCompareResult lResult = Utils.NullCompare(x, y);

            return lResult.BothNotNull
                ? x.Duration.StartDateTime == y.Duration.StartDateTime &&
                  x.Duration.EndDateTime == y.Duration.EndDateTime && x.Notice == y.Notice
                : lResult.Different;
        }

        private void Duration_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == DateTimeSpan.PropertyName_StartDate)
            {
                UpdatePossibleFrequencies(_RootAppointment.Duration.StartDateTime);
            }
        }

        private DateTime Next(DateTime pDate, ResourceBookingFrequencyType pFrequency)
        {
            switch (pFrequency)
            {
                case ResourceBookingFrequencyType.Daily:
                    return pDate.AddDays(1);
                case ResourceBookingFrequencyType.Weekly:
                    return pDate.AddDays(7);
                case ResourceBookingFrequencyType.Monthly:
                    return pDate.AddMonths(1);
                case ResourceBookingFrequencyType.MonthlyOnFirstDay:
                    return pDate.AddMonthsDayDependent(1, true);
                case ResourceBookingFrequencyType.MonthlyOnLastDay:
                    return pDate.AddMonthsDayDependent(1, false);
                default:
                    return pDate;
            }
        }

        private void _RootAppointment_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == Appointment.PropertyName_Duration)
            {
                UpdatePossibleFrequencies(_RootAppointment.Duration.StartDateTime);
            }

            StoreDirtyInformation(!AreEqual(DataContract.RootBooking, _RootAppointment), PropertyName_RootAppointment);
        }
    }
}