// Ignore Spelling: Linq

using System;

namespace AsbCloudInfrastructure;

public static class DateTimeExtensions
{
    /// <summary>
    /// Приветсти к UTC из времени куста
    /// </summary>
    /// <param name="date"></param>
    /// <param name="remoteTimezoneOffsetHours"></param>
    /// <returns></returns>
    public static DateTimeOffset ToUtcDateTimeOffset(this DateTime date, double remoteTimezoneOffsetHours)
    {
        if (date == default)
            return new DateTimeOffset();

        var dateUtc = date.Kind switch
        {
            DateTimeKind.Local => date.ToUniversalTime(),
            DateTimeKind.Unspecified => DateTime.SpecifyKind(date.AddHours(-remoteTimezoneOffsetHours), DateTimeKind.Utc),
            _ => date,
        };
        return new DateTimeOffset(dateUtc);
    }

    /// <summary>
    /// Привести ко времени куста из utc
    /// </summary>
    /// <param name="date"></param>
    /// <param name="remoteTimezoneOffsetHours"></param>
    /// <returns></returns>
    public static DateTime ToRemoteDateTime(this DateTimeOffset date, double remoteTimezoneOffsetHours)
    {
        if (date == default)
            return new DateTime(0, DateTimeKind.Unspecified);
        var dateTz = date.ToOffset(TimeSpan.FromHours(remoteTimezoneOffsetHours));
        return dateTz.DateTime;
    }

    /// <summary>
    /// Поиск индекса близкого к value значения в span.
    /// Элементы в span должны быть отсортированы по возрастанию по полю propertyGetter.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProp"></typeparam>
    /// <param name="span"></param>
    /// <param name="propertyGetter">метод получения свойства из объекта класса T</param>
    /// <param name="value">искомое значение</param>
    /// <param name="maxIterations">максимальное кол-во итераций. Прим.: 
    /// в span из 4000 записей достаточно 9-ти итераций
    /// 8_000 => 10 итераций;
    /// 16_000 => 11;
    /// ...
    /// 256_000 => 15;
    /// При недостаточном кол-ве итераций индекс может оказаться близко, но это будет не ближайшее значение
    /// </param>
    /// <returns></returns>
    public static int BinarySearch<T, TProp>(this Span<T> span, Func<T, TProp> propertyGetter, TProp value, int maxIterations = 16)
        where TProp : IComparable
    {
        if (span.Length < 2 || --maxIterations < 0)
            return 0;

        var indexOfMiddle = span.Length >> 1;

        var comparison = value.CompareTo(propertyGetter(span[indexOfMiddle]));

        if (comparison > 0)
        {
            var index = indexOfMiddle + 1 + BinarySearch(span[(indexOfMiddle + 1)..], propertyGetter, value, maxIterations);
            return index < span.Length ? index : span.Length - 1;
        }
        else if (comparison < 0)
            return BinarySearch(span[..(indexOfMiddle)], propertyGetter, value, maxIterations);
        else //if (comparison == 0)
            return indexOfMiddle;
    }
}