/// <summary>
        /// If a intervalReading to the tariff change timestamp is needed, this method will be called.
        /// </summary>
        /// <param name="meterReading">The raw data.</param>
        /// <param name="end">The date for the meterReading</param>
        /// <param name="index">the index of the parent loop</param>
        /// <returns>An intervalReading or null</returns>
        public (IntervalReading reading, DateTime end) SetIntervalReading(MeterReading meterReading, DateTime end, int index, int dayTimeProfilesCount)
        {
            var date    = LocalSetLastReading(end, index, dayTimeProfilesCount);
            var reading = meterReading.GetIntervalReadingFromDate(date);

            // If there is a gap at 0:00 o'clock the next day
            if (date > end && (reading == null || !IsStatusValid(reading)))
            {
                date    = end;
                reading = meterReading.GetIntervalReadingFromDate(date);
            }

            return(reading, date);
        }
        /// <summary>
        /// Is called from FindLastValidTime when no matching vaule was found.
        /// </summary>
        /// <param name="end">The current endpoint of the tariff stage range.</param>
        /// <param name="profile">The used SpecialDayProfile instance.</param>
        /// <param name="dayTimeProfiles">The used DayTime profile.</param>
        /// <param name="meterReading">The raw data.</param>
        /// <param name="index">For marking the parent loop index.</param>
        /// <returns>The matching DateTime and the new index for the parent loop.</returns>
        public (DateTime end, int index) FindNextValidTime(DateTime end, SpecialDayProfile profile,
                                                           List <DayTimeProfile> dayTimeProfiles, MeterReading meterReading, int index)
        {
            DateTime result    = end;
            var      match     = false;
            var      helpindex = index;

            while (!match)
            {
                if (helpindex >= dayTimeProfiles.Count)
                {
                    helpindex = helpindex - 1;
                    break;
                }

                result = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[helpindex]);
                var reading = meterReading.GetIntervalReadingFromDate(result);

                if (reading != null && IsStatusValid(reading))
                {
                    match = true;
                }
                else
                {
                    helpindex++;
                }
            }
            return(result, helpindex);
        }
        /// <summary>
        /// If no coresonding value to the tarffif change time is found this method will be called.
        /// </summary>
        /// <param name="start">The beginning of the tariff stage range.</param>
        /// <param name="end">The current endpoint of the tariff stage range.</param>
        /// <param name="profile">The used SpecialDayProfile instance.</param>
        /// <param name="dayTimeProfiles">The used DayTime profile.</param>
        /// <param name="meterReading">The raw data.</param>
        /// <param name="index">Start index for iterating over the measurement list.</param>
        /// <returns>The matching DateTime and the new index for the parent loop.</returns>
        public (DateTime end, int index) FindLastValidTime(DateTime start, DateTime end, SpecialDayProfile profile,
                                                           List <DayTimeProfile> dayTimeProfiles, MeterReading meterReading, int index)
        {
            var result    = end;
            var match     = false;
            var helpindex = index;

            while (!match)
            {
                // Get the next lower value
                // Example: If there is no value at 5:00 then it is set to 4:45 and so on until a value
                //          was found or start is reached. If start is reached the next upper value is searched (FindNextValidTime)
                result = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[helpindex]);

                if (result == start)
                {
                    (result, helpindex) = this.FindNextValidTime(end, profile, dayTimeProfiles, meterReading, index);

                    if (helpindex + 1 == dayTimeProfiles.Count)
                    {
                        break;
                    }
                }

                var reading = meterReading.GetIntervalReadingFromDate(result);

                if (reading != null && IsStatusValid(reading))
                {
                    match = true;
                }
                else
                {
                    helpindex--;
                    if (helpindex < 0)
                    {
                        throw new InvalidOperationException(
                                  "Die PTB oder FNN Stati aller Messwerte sind kritisch oder fatal.");
                    }
                }
            }

            return(result, helpindex);
        }
        /// <summary>
        /// Searches the next valid startReading (In case the first value or values of the day are gaps)
        /// </summary>
        /// <param name="start">the current timestamp.</param>
        /// <param name="dayTimeProfiles">The dayTimeProfiles for the current day.</param>
        /// <param name="profile">The specialDayProfile of the current day.</param>
        /// <param name="meterReading">The meterReading object of the current original value list.</param>
        /// <param name="currentDay">The current AccountingDay object.</param>
        /// <param name="dtpIndex">The current dayTimeProfiles index.</param>
        /// <param name="latestReading">The current meterReading value.</param>
        /// <param name="lastTariffId">The last valid tariff id.</param>
        /// <returns>The next valid start value.</returns>
        public (IntervalReading startReading, int index, long latestReading, ushort tariffId) GetStartReading(DateTime start, List <DayTimeProfile> dayTimeProfiles,
                                                                                                              SpecialDayProfile profile, MeterReading meterReading, AccountingDay currentDay, int dtpIndex, long latestReading, ushort lastTariffId)
        {
            var dayStart     = start;
            var startReading = new IntervalReading();
            var index        = dtpIndex;

            for (int i = 1; i < dayTimeProfiles.Count; i++)
            {
                start        = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[i]);
                startReading = meterReading.GetIntervalReadingFromDate(start);

                // If the gap reaches the next tariff change
                if (dayTimeProfiles[i].TariffNumber != lastTariffId && lastTariffId != 63)
                {
                    lastTariffId = 63;
                }

                // Checks if a startReading is found
                if (startReading != null && IsStatusValid(startReading))
                {
                    currentDay.Add(new MeasuringRange()
                    {
                        Start    = dayStart,
                        End      = start,
                        Amount   = (startReading.Value.Value - latestReading),
                        TariffId = lastTariffId
                    }, new ObisId(meterReading.ReadingType.ObisCode));

                    index         = i;
                    latestReading = GetLatestReading(latestReading, startReading);
                    lastTariffId  = (ushort)dayTimeProfiles[i].TariffNumber;
                    break;
                }
                else
                {
                    index = i;
                }
            }

            // if no startReading for the whole day was found, set index to 96
            if ((startReading == null || !IsStatusValid(startReading)) && index == dayTimeProfiles.Count - 1)
            {
                var nextDay0OClock = start.AddSeconds(900);
                startReading = meterReading.GetIntervalReadingFromDate(nextDay0OClock);

                // Check if a value for the next day at 0:00 o Clock exists
                if (startReading != null && IsStatusValid(startReading))
                {
                    currentDay.Add(new MeasuringRange()
                    {
                        Start    = dayStart,
                        End      = nextDay0OClock,
                        Amount   = startReading.Value.Value - latestReading,
                        TariffId = lastTariffId
                    }, new ObisId(meterReading.ReadingType.ObisCode));
                }
                index = dayTimeProfiles.Count;
            }

            return(startReading, index, latestReading, lastTariffId);
        }
        /// <summary>
        /// The main calculation method for every day in the billing period.
        /// </summary>
        /// <param name="profile">The current SpecialDayProfile</param>
        /// <param name="dayProfiles">A List of all DayProfiles</param>
        /// <param name="meterReading">The MeterReading instance with the raw data.</param>
        /// <param name="supplier">Contains the calculation data.</param>
        /// <param name="latestReading">The last valid value of an IntervalReading.</param>
        /// <param name="latestTariffId">The last vaild tariff.</param>
        /// <returns>The calculated AccountingSection</returns>
        public (AccountingDay day, long latestReading, ushort tariffId) GetDayData(SpecialDayProfile profile, List <DayProfile> dayProfiles,
                                                                                   MeterReading meterReading, UsagePointLieferant supplier, long latestReading, ushort latestTariffId)
        {
            var registers = supplier.GetRegister();

            this.UpdateReadingTypeFromOriginalValueList(registers);

            var currentDay = new AccountingDay(registers);

            // Every SpecialDayProfile is linked to a dayProfile which contains dayTimeProfiles.
            // This dayTimeProfiles are needed because they contain the tariff change information of the day.
            var dayTimeProfiles = GetValidDayTimeProfiles(dayProfiles, profile);

            var start        = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[0]);
            var index        = 1;
            var startReading = meterReading.GetIntervalReadingFromDate(start);

            // If the current day has a gap at 00:00
            if (startReading == null || !IsStatusValid(startReading))
            {
                var startData = this.GetStartReading(start, dayTimeProfiles, profile,
                                                     meterReading, currentDay, index, latestReading, latestTariffId);

                index          = startData.index;
                startReading   = startData.startReading;
                start          = startData.startReading != null && IsStatusValid(startData.startReading) ? startData.startReading.TargetTime.Value : start;
                latestTariffId = startData.tariffId;
                latestReading  = startData.latestReading;
            }

            // Check whether dayTimeProfiles is null or empty
            this.CheckInitSettings(dayTimeProfiles);

            if (startReading != null && IsStatusValid(startReading))
            {
                currentDay.Reading = new Reading()
                {
                    Amount = startReading.Value, ObisCode = new ObisId(meterReading.ReadingType.ObisCode)
                };
                currentDay.Start = profile.SpecialDayDate.GetDate();
            }

            var endReading = SetConcreteIntervalReading(null, DateTime.MinValue);

            for (var i = index; i < dayTimeProfiles.Count; i++)
            {
                DateTime end;
                // Check if the tariff number changes
                if (dayTimeProfiles[i - 1].TariffNumber != dayTimeProfiles[i].TariffNumber)
                {
                    // Set the end of the current range
                    end        = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[i]);
                    endReading = SetIntervalReading(meterReading, end, i, dayTimeProfiles.Count);

                    var rangeData = GetNextRange(endReading.reading, endReading.end,
                                                 startReading, start, end,
                                                 dayTimeProfiles, meterReading, profile, currentDay, i, latestReading, latestTariffId);


                    latestReading  = rangeData.latestReading;
                    start          = rangeData.reading.TargetTime.Value;
                    startReading   = rangeData.reading;
                    i              = rangeData.index;
                    latestTariffId = rangeData.range.TariffId;
                    latestReading  = this.GetLatestReading(latestReading, rangeData.reading);

                    if (!this.IsRangeEmpty(rangeData.range))
                    {
                        currentDay.Add(rangeData.range, new ObisId(meterReading.ReadingType.ObisCode));
                    }
                }
                // If there is no tariff change at the current timestamp
                else
                {
                    //  Check if it is the last value of the current day
                    if (i == dayTimeProfiles.Count - 1)
                    {
                        end        = ModelExtensions.GetDateTimeFromSpecialDayProfile(profile, dayTimeProfiles[i]);
                        endReading = SetIntervalReading(meterReading, end, i, dayTimeProfiles.Count);

                        var rangeData = GetNextRange(endReading.reading, endReading.end,
                                                     startReading, start, end,
                                                     dayTimeProfiles, meterReading, profile, currentDay, i, latestReading, latestTariffId);


                        latestReading  = rangeData.latestReading;
                        start          = end;
                        startReading   = rangeData.reading;
                        i              = rangeData.index;
                        latestTariffId = rangeData.range.TariffId;
                        latestReading  = this.GetLatestReading(latestReading, rangeData.reading);

                        if (!this.IsRangeEmpty(rangeData.range))
                        {
                            currentDay.Add(rangeData.range, new ObisId(meterReading.ReadingType.ObisCode));
                        }
                    }
                }
            }

            return(currentDay, latestReading, latestTariffId);
        }