// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Migrations.Utilities
{
    using System.Diagnostics;
    using System.Globalization;
    using System.Threading;

    // <summary>
    // Used for generating <see cref="DateTime.UtcNow" /> values that are always in sequential
    // order for the calling thread.
    // </summary>
    internal static class UtcNowGenerator
    {
        public const string MigrationIdFormat = "yyyyMMddHHmmssf";

        private static readonly ThreadLocal<DateTime> _lastNow = new ThreadLocal<DateTime>(() => DateTime.UtcNow);

        // <summary>
        // Returns the value of <see cref="DateTime.UtcNow" /> unless this value would be the same as the
        // last value returned by this thread calling this method, in which case the thread pushes the value
        // a little bit into the future. The comparison is in terms of the form used to store migration ID
        // in the database--i.e. to the 1/10 second.
        // </summary>
        // <remarks>
        // There should never be any pushing to the future involved for normal use of migrations, but when
        // this method is called in rapid succession while testing or otherwise calling the DbMigrator APIs
        // there may be occasional sleeping.
        // </remarks>
        [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.DateTime.ToString(System.String)", Justification = "It only objects to the statement in Debug.Assert() which is only for debug mode.")]
        public static DateTime UtcNow()
        {
            var now = DateTime.UtcNow;
            var lastNow = _lastNow.Value;

            // At least on some machines DateTime.UtcNow can return values that are a little bit (< 1 second) less than the
            // last value that it returned.
            if (now <= lastNow
                || now.ToString(MigrationIdFormat, CultureInfo.InvariantCulture)
                      .Equals(lastNow.ToString(MigrationIdFormat, CultureInfo.InvariantCulture), StringComparison.Ordinal))
            {
                now = lastNow.AddMilliseconds(100);

                Debug.Assert(!now.ToString(MigrationIdFormat).Equals(lastNow.ToString(MigrationIdFormat), StringComparison.Ordinal));
            }

            _lastNow.Value = now;

            return now;
        }

        // <summary>
        // Same as UtcNow method bur returns the time in the timestamp format used in migration IDs.
        // </summary>
        public static string UtcNowAsMigrationIdTimestamp()
        {
            return UtcNow().ToString(MigrationIdFormat, CultureInfo.InvariantCulture);
        }
    }
}
