using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace EasyDevCore.Common
{
    /// <summary>
    /// 
    /// </summary>
    public enum SequentialGuidType
    {
        /// <summary>
        /// The sequential as string (sepecific for MySQL, PostgreSQL ...)
        /// </summary>
        SequentialAsString,
        /// <summary>
        /// The sequential as binary (sepecific for Oracle)
        /// </summary>
        SequentialAsBinary,
        /// <summary>
        /// The sequential at end (sepecific for Microsoft SQL Server)
        /// </summary>
        SequentialAtEnd
    }

    /// <summary>
    /// 
    /// </summary>
    public static class SequentialGuidGenerator
    {
        /// <summary>
        /// The _RNG
        /// </summary>
        private static readonly System.Security.Cryptography.RandomNumberGenerator _rng = System.Security.Cryptography.RandomNumberGenerator.Create();

        /// <summary>
        /// Create  a sequential GUID.
        /// </summary>
        /// <param name="guidType">Type of the GUID.</param>
        /// <returns></returns>
        public static Guid NewSequentialGuid(SequentialGuidType guidType)
        {
            return NewSequentialGuid(guidType, 1)[0];
        }

        /// <summary>
        /// Nexts the sequential unique identifier.
        /// </summary>
        /// <param name="guid">The unique identifier.</param>
        /// <param name="guidType">Type of the unique identifier.</param>
        /// <returns></returns>
        public static Guid NextSequentialGuid(Guid guid, SequentialGuidType guidType)
        {
            byte[] guidBytes = guid.ToByteArray();
            UInt32 nextID = 0;
            switch (guidType)
            {
                case SequentialGuidType.SequentialAsString:
                case SequentialGuidType.SequentialAsBinary:
                    nextID = BitConverter.ToUInt32(guidBytes, 12) + 1;
                    Buffer.BlockCopy(BitConverter.GetBytes(nextID), 0, guidBytes, 12, 4);
                    break;
                case SequentialGuidType.SequentialAtEnd:
                    nextID = BitConverter.ToUInt32(guidBytes, 6) + 1;
                    Buffer.BlockCopy(BitConverter.GetBytes(nextID), 0, guidBytes, 6, 4);
                    break;
                default:
                    throw new NotSupportedException(guidType.ToString());
            }

            return new Guid(guidBytes);
        }

        /// <summary>
        /// Nexts the sequential unique identifier.
        /// </summary>
        /// <param name="guid">The unique identifier.</param>
        /// <returns></returns>
        public static Guid NextSequentialGuid(Guid guid)
        {
            return NextSequentialGuid(guid, SequentialGuidType.SequentialAtEnd);
        }

        /// <summary>
        /// Create a sequential GUID (sepecific for Microsoft SQL Server)
        /// (SequentialGuidType = SequentialGuidType.SequentialAtEnd).
        /// </summary>
        /// <returns></returns>
        public static Guid NewSequentialGuid()
        {
            return NewSequentialGuid(SequentialGuidType.SequentialAtEnd, 1)[0];
        }

        /// <summary>
        /// Create a sery sequential GUIDs (sepecific for Microsoft SQL Server)
        /// (SequentialGuidType = SequentialGuidType.SequentialAtEnd).
        /// </summary>
        /// <param name="count">The count.</param>
        /// <returns></returns>
        public static Guid[] NewSequentialGuid(int count)
        {
            return NewSequentialGuid(SequentialGuidType.SequentialAtEnd, count);
        }

        /// <summary>
        /// Create a sery sequential GUIDs.
        /// </summary>
        /// <param name="guidType">Type of the GUID.</param>
        /// <param name="count">The count.</param>
        /// <returns></returns>
        public static Guid[] NewSequentialGuid(SequentialGuidType guidType, int count)
        {
            byte[] randomBytes = new byte[10];
            _rng.GetBytes(randomBytes);
            UInt32 sequentialID = BitConverter.ToUInt32(randomBytes, 6);
            long timestamp = DateTime.UtcNow.Ticks / 10000L;
            byte[] timestampBytes = null;
            int increment = -1;

            Guid[] listGuids = new Guid[count];
            byte[] guidBytes = new byte[16];
            timestampBytes = BitConverter.GetBytes(timestamp);

            switch (guidType)
            {
                case SequentialGuidType.SequentialAsString:
                case SequentialGuidType.SequentialAsBinary:
                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 6); //Get first 6 byte
                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(timestampBytes);
                    }
                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
                    // If formatting as a string, we have to reverse the order
                    // of the Data1 and Data2 blocks on little-endian systems.
                    if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(guidBytes, 0, 4);
                        Array.Reverse(guidBytes, 4, 2);
                    }
                    while (increment++ < count)
                    {
                        Buffer.BlockCopy(BitConverter.GetBytes(sequentialID++), 0, guidBytes, 12, 4); //Move last rest 4 byte
                        listGuids[increment] = new Guid(guidBytes);
                    }
                    break;
                case SequentialGuidType.SequentialAtEnd:
                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 6); //Get first 6 byte
                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(timestampBytes);
                    }
                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
                    while (++increment < count)
                    {
                        Buffer.BlockCopy(BitConverter.GetBytes(sequentialID++), 0, guidBytes, 6, 4); //Move last rest 4 byte
                        listGuids[increment] = new Guid(guidBytes);
                    }
                    break;
            }

            return listGuids;
        }
        
    }
}
