/// <summary>
 ///     Get TimeRange covered by Energiemenge
 /// </summary>
 /// <param name="menge">Energiemenge</param>
 /// <returns>
 ///     TimeRange ranging from the earliest <see cref="Verbrauch.Startdatum" /> to the latest
 ///     <see cref="Verbrauch.Enddatum" />
 /// </returns>
 /// <returns></returns>
 public static TimeRange GetTimeRange(this BO.Energiemenge menge)
 {
     using (MiniProfiler.Current.Step(nameof(GetTimeRange)))
     {
         return(new TimeRange(menge.GetMinDate().UtcDateTime, menge.GetMaxDate().UtcDateTime));
     }
 }
 /// <summary>
 ///     Test, if the Energiemenge is continuous within its own min/max range.
 ///     <see cref="IsContinuous(BO4E.BO.Energiemenge,Itenso.TimePeriod.TimeRange)" />
 /// </summary>
 /// <param name="em">Energiemenge</param>
 /// <returns>
 ///     true iff Energiemenge has defined value for every point in time t in
 ///     min(energieverbrauch.startdatum) &lt;= t &lt; max(energieverbrauch.enddatum);
 ///     false otherwise
 /// </returns>
 public static bool IsContinuous(this BO.Energiemenge em)
 {
     return(IsContinuous(em, new TimeRange(em.GetMinDate().UtcDateTime, em.GetMaxDate().UtcDateTime)));
 }
        /// <summary>
        ///     Get a list of those time ranges within a reference, where no energieverbrauch entries are defined.
        /// </summary>
        /// <param name="em">Energiemenge</param>
        /// <param name="reference">reference time frame</param>
        /// <param name="wev">Wertermittlungsverfahren</param>
        /// <param name="obis">OBIS-Kennzahl</param>
        /// <param name="me">Mengeneinheit</param>
        /// <returns></returns>
        public static List <TimeRange> GetMissingTimeRanges(this BO.Energiemenge em, ITimeRange reference,
                                                            Wertermittlungsverfahren wev, string obis, Mengeneinheit me)
        {
            using (MiniProfiler.Current.Step(nameof(GetMissingTimeRanges)))
            {
                IDictionary <Tuple <DateTime, DateTime>, Verbrauch> filteredVerbrauch;
                using (MiniProfiler.Current.Step(
                           $"Filtering energieverbrauch on OBIS={obis}, WEV={wev}, Mengeneinheit={me}"))
                {
                    filteredVerbrauch = em.Energieverbrauch
                                        .Where(v => v.Wertermittlungsverfahren == wev && v.Obiskennzahl == obis && v.Einheit == me)
                                        .ToDictionary(v => new Tuple <DateTime, DateTime>(v.Startdatum, v.Enddatum), v => v);
                }

                if (filteredVerbrauch.Count < 2)
                {
                    throw new ArgumentException("Not enough entries in energieverbrauch to determine periodicity.");
                }
                if (!IsEvenlySpaced(em, reference, wev, obis, me, true))
                {
                    throw new ArgumentException(
                              "The provided Energiemenge is not evenly spaced although gaps are allowed.");
                }
                var periodicity = GetTimeSpans(em, wev, obis, me).Min();
                if (
                    Math.Abs((reference.Start - em.GetMinDate()).TotalMilliseconds % periodicity.TotalMilliseconds) !=
                    0)
                {
                    throw new ArgumentException(
                              $"The absolute difference between reference.start ({reference.Start}) and the minimal date time in the Energiemenge ({em.GetMinDate()}) has to be an integer multiple of the periodicity {periodicity.TotalMilliseconds} but was {(reference.Start - em.GetMinDate()).TotalMilliseconds}.");
                }
                // since it's assured, that the energieverbrauch entries are evenly spaced it doesn't matter which entry we use to determine the duration.
                var duration = filteredVerbrauch.Values.Min(v => v.Enddatum) -
                               filteredVerbrauch.Values.Min(v => v.Startdatum);
                var result = new List <TimeRange>();
                using (MiniProfiler.Current.Step("Populating list with time slices in UTC"))
                {
                    for (var dt = reference.Start; dt < reference.End; dt += periodicity)
                    {
                        // use a strict '==' instead of overlap. This is justified because all the other cases are considered beforehand
                        switch (dt.Kind)
                        {
                        case DateTimeKind.Local:
                            throw new ArgumentException("Local DateTime not supported!");

                        case DateTimeKind.Unspecified:
                            dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                            break;

                        case DateTimeKind.Utc:
                            break;
                        }

                        //using (MiniProfiler.Current.Step("linq where on filtered verbrauch"))
                        //{
                        if (!filteredVerbrauch.ContainsKey(
                                new Tuple <DateTime, DateTime>(dt,
                                                               dt + duration))) //   Where<Verbrauch>(v => v.startdatum == dt && v.enddatum == dt + duration).Any())
                        {
                            result.Add(new TimeRange(dt, dt + duration));
                        }
                        //}
                    }
                }

                return(result);
            }
        }
 /// <summary>
 ///     Same as
 ///     <see
 ///         cref="GetTotalConsumption(BO4E.BO.Energiemenge,BO4E.ENUM.Wertermittlungsverfahren,string,BO4E.ENUM.Mengeneinheit)" />
 ///     but without auto-detected parameters.
 ///     By default a the full length of the Energiemenge is taken into account.
 /// </summary>
 /// <param name="em">Energiemenge</param>
 /// <returns>Tuple of consumption value and unit of measurement</returns>
 public static Tuple <decimal, Mengeneinheit> GetTotalConsumption(this BO.Energiemenge em)
 {
     return(GetConsumption(em, new TimeRange(em.GetMinDate().UtcDateTime, em.GetMaxDate().UtcDateTime)));
 }