// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System;

internal readonly partial struct Value
{
    /// <summary>
    ///  Allows packing some <see cref="DateTimeOffset"/> values into a single <see cref="ulong"/>.
    /// </summary>
    private readonly struct PackedDateTimeOffset
    {
        // HHHHHMMT TTT...
        //
        // HHHHH   - hour bits 1-31
        // MM      - minutes flag
        // T       - ticks bit
        //            00 - :00
        //            01 - :15
        //            10 - :30
        //            11 - :45

        // Base local tick time 1800 [DateTime(1800, 1, 1).Ticks]
        private const ulong BaseTicks = 567709344000000000;
        private const ulong MaxTicks = BaseTicks + TickMask;

        // Hours go from -14 to 14. We add 14 to get our number to store.
        private const int HourOffset = 14;

        private const ulong TickMask    = 0b00000001_11111111_11111111_11111111__11111111_11111111_11111111_11111111;
        private const ulong MinuteMask  = 0b00000110_00000000_00000000_00000000__00000000_00000000_00000000_00000000;
        private const ulong HourMask    = 0b11111000_00000000_00000000_00000000__00000000_00000000_00000000_00000000;

        private const int MinuteShift = 57;
        private const int HourShift = 59;

        private readonly ulong _data;

        private PackedDateTimeOffset(ulong data) => _data = data;

        public static bool TryCreate(DateTimeOffset dateTime, TimeSpan offset, out PackedDateTimeOffset packed)
        {
            bool result = false;
            packed = default;

            ulong ticks = (ulong)dateTime.Ticks;
            if (ticks is > BaseTicks and < MaxTicks)
            {
                int minutes = offset.Minutes;

                if (minutes % 15 == 0)
                {
                    ulong data = (ulong)(minutes / 15) << MinuteShift;
                    int hours = offset.Hours + HourOffset;

                    // Only valid offset hours are -14 to 14
                    Debug.Assert(hours is >= 0 and <= 28);
                    data |= (ulong)hours << HourShift;
                    data |= ticks - BaseTicks;
                    packed = new(data);
                    result = true;
                }
            }

            return result;
        }

        public DateTimeOffset Extract()
        {
            TimeSpan offset = new(
                (int)(((_data & HourMask) >> HourShift) - HourOffset),
                (int)((_data & MinuteMask) >> MinuteShift),
                0);
            return new((long)((_data & TickMask) + BaseTicks), offset);
        }
    }
}
