// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Runtime.InteropServices;

namespace MessagePack.Internal
{
    [StructLayout(LayoutKind.Explicit, Pack = 1)]
    internal struct GuidBits
    {
        [FieldOffset(0)]
        public readonly Guid Value;

        [FieldOffset(0)]
        public readonly byte Byte0;
        [FieldOffset(1)]
        public readonly byte Byte1;
        [FieldOffset(2)]
        public readonly byte Byte2;
        [FieldOffset(3)]
        public readonly byte Byte3;
        [FieldOffset(4)]
        public readonly byte Byte4;
        [FieldOffset(5)]
        public readonly byte Byte5;
        [FieldOffset(6)]
        public readonly byte Byte6;
        [FieldOffset(7)]
        public readonly byte Byte7;
        [FieldOffset(8)]
        public readonly byte Byte8;
        [FieldOffset(9)]
        public readonly byte Byte9;
        [FieldOffset(10)]
        public readonly byte Byte10;
        [FieldOffset(11)]
        public readonly byte Byte11;
        [FieldOffset(12)]
        public readonly byte Byte12;
        [FieldOffset(13)]
        public readonly byte Byte13;
        [FieldOffset(14)]
        public readonly byte Byte14;
        [FieldOffset(15)]
        public readonly byte Byte15;

        // string.Join(", ", Enumerable.Range(0, 256).Select(x => (int)BitConverter.ToString(new byte[] { (byte)x }).ToLower()[0]))
        private static ReadOnlySpan<byte> GetByteToHexStringHigh() => new byte[256] { 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102 };

        // string.Join(", ", Enumerable.Range(0, 256).Select(x => (int)BitConverter.ToString(new byte[] { (byte)x }).ToLower()[1]))
        private static ReadOnlySpan<byte> GetByteToHexStringLow() => new byte[256] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102 };

        public GuidBits(ref Guid value)
        {
            this = default(GuidBits);
            this.Value = value;
        }

        // 4-pattern, lower/upper and '-' or no
        public GuidBits(ReadOnlySpan<byte> utf8string)
        {
            this = default(GuidBits);

            // 32
            if (utf8string.Length == 32)
            {
                if (BitConverter.IsLittleEndian)
                {
                    this.Byte0 = Parse(utf8string, 6);
                    this.Byte1 = Parse(utf8string, 4);
                    this.Byte2 = Parse(utf8string, 2);
                    this.Byte3 = Parse(utf8string, 0);

                    this.Byte4 = Parse(utf8string, 10);
                    this.Byte5 = Parse(utf8string, 8);

                    this.Byte6 = Parse(utf8string, 14);
                    this.Byte7 = Parse(utf8string, 12);
                }
                else
                {
                    this.Byte0 = Parse(utf8string, 0);
                    this.Byte1 = Parse(utf8string, 2);
                    this.Byte2 = Parse(utf8string, 4);
                    this.Byte3 = Parse(utf8string, 6);

                    this.Byte4 = Parse(utf8string, 8);
                    this.Byte5 = Parse(utf8string, 10);

                    this.Byte6 = Parse(utf8string, 12);
                    this.Byte7 = Parse(utf8string, 14);
                }

                this.Byte8 = Parse(utf8string, 16);
                this.Byte9 = Parse(utf8string, 18);

                this.Byte10 = Parse(utf8string, 20);
                this.Byte11 = Parse(utf8string, 22);
                this.Byte12 = Parse(utf8string, 24);
                this.Byte13 = Parse(utf8string, 26);
                this.Byte14 = Parse(utf8string, 28);
                this.Byte15 = Parse(utf8string, 30);
                return;
            }
            else if (utf8string.Length == 36)
            {
                // '-' => 45
                if (BitConverter.IsLittleEndian)
                {
                    this.Byte0 = Parse(utf8string, 6);
                    this.Byte1 = Parse(utf8string, 4);
                    this.Byte2 = Parse(utf8string, 2);
                    this.Byte3 = Parse(utf8string, 0);

                    if (utf8string[8] != '-')
                    {
                        goto ERROR;
                    }

                    this.Byte4 = Parse(utf8string, 11);
                    this.Byte5 = Parse(utf8string, 9);

                    if (utf8string[13] != '-')
                    {
                        goto ERROR;
                    }

                    this.Byte6 = Parse(utf8string, 16);
                    this.Byte7 = Parse(utf8string, 14);
                }
                else
                {
                    this.Byte0 = Parse(utf8string, 0);
                    this.Byte1 = Parse(utf8string, 2);
                    this.Byte2 = Parse(utf8string, 4);
                    this.Byte3 = Parse(utf8string, 6);

                    if (utf8string[8] != '-')
                    {
                        goto ERROR;
                    }

                    this.Byte4 = Parse(utf8string, 9);
                    this.Byte5 = Parse(utf8string, 11);

                    if (utf8string[13] != '-')
                    {
                        goto ERROR;
                    }

                    this.Byte6 = Parse(utf8string, 14);
                    this.Byte7 = Parse(utf8string, 16);
                }

                if (utf8string[18] != '-')
                {
                    goto ERROR;
                }

                this.Byte8 = Parse(utf8string, 19);
                this.Byte9 = Parse(utf8string, 21);

                if (utf8string[23] != '-')
                {
                    goto ERROR;
                }

                this.Byte10 = Parse(utf8string, 24);
                this.Byte11 = Parse(utf8string, 26);
                this.Byte12 = Parse(utf8string, 28);
                this.Byte13 = Parse(utf8string, 30);
                this.Byte14 = Parse(utf8string, 32);
                this.Byte15 = Parse(utf8string, 34);
                return;
            }

ERROR:
            throw new MessagePackSerializationException("Invalid Guid Pattern.");
        }

#if !UNITY_2018_3_OR_NEWER
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
#endif
        private static byte Parse(ReadOnlySpan<byte> bytes, int highOffset)
        {
            return unchecked((byte)((SwitchParse(bytes[highOffset]) * 16) + SwitchParse(bytes[highOffset + 1])));
        }

#if !UNITY_2018_3_OR_NEWER
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
#endif
        private static byte SwitchParse(byte b)
        {
            // '0'(48) ~ '9'(57) => -48
            // 'A'(65) ~ 'F'(70) => -55
            // 'a'(97) ~ 'f'(102) => -87
            switch (b)
            {
                case 48:
                case 49:
                case 50:
                case 51:
                case 52:
                case 53:
                case 54:
                case 55:
                case 56:
                case 57:
                    return unchecked((byte)(b - 48));
                case 65:
                case 66:
                case 67:
                case 68:
                case 69:
                case 70:
                    return unchecked((byte)(b - 55));
                case 97:
                case 98:
                case 99:
                case 100:
                case 101:
                case 102:
                    return unchecked((byte)(b - 87));
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                case 10:
                case 11:
                case 12:
                case 13:
                case 14:
                case 15:
                case 16:
                case 17:
                case 18:
                case 19:
                case 20:
                case 21:
                case 22:
                case 23:
                case 24:
                case 25:
                case 26:
                case 27:
                case 28:
                case 29:
                case 30:
                case 31:
                case 32:
                case 33:
                case 34:
                case 35:
                case 36:
                case 37:
                case 38:
                case 39:
                case 40:
                case 41:
                case 42:
                case 43:
                case 44:
                case 45:
                case 46:
                case 47:
                case 58:
                case 59:
                case 60:
                case 61:
                case 62:
                case 63:
                case 64:
                case 71:
                case 72:
                case 73:
                case 74:
                case 75:
                case 76:
                case 77:
                case 78:
                case 79:
                case 80:
                case 81:
                case 82:
                case 83:
                case 84:
                case 85:
                case 86:
                case 87:
                case 88:
                case 89:
                case 90:
                case 91:
                case 92:
                case 93:
                case 94:
                case 95:
                case 96:
                default:
                    throw new MessagePackSerializationException("Invalid Guid Pattern.");
            }
        }

        // 4(x2) - 2(x2) - 2(x2) - 2(x2) - 6(x2)
        public void Write(Span<byte> buffer)
        {
            var high = GetByteToHexStringHigh();
            var low = GetByteToHexStringLow();

            if (BitConverter.IsLittleEndian)
            {
                // int(_a)
                buffer[6] = high[this.Byte0];
                buffer[7] = low[this.Byte0];
                buffer[4] = high[this.Byte1];
                buffer[5] = low[this.Byte1];
                buffer[2] = high[this.Byte2];
                buffer[3] = low[this.Byte2];
                buffer[0] = high[this.Byte3];
                buffer[1] = low[this.Byte3];

                buffer[8] = (byte)'-';

                // short(_b)
                buffer[11] = high[this.Byte4];
                buffer[12] = low[this.Byte4];
                buffer[9] = high[this.Byte5];
                buffer[10] = low[this.Byte5];

                buffer[13] = (byte)'-';

                // short(_c)
                buffer[16] = high[this.Byte6];
                buffer[17] = low[this.Byte6];
                buffer[14] = high[this.Byte7];
                buffer[15] = low[this.Byte7];
            }
            else
            {
                buffer[0] = high[this.Byte0];
                buffer[1] = low[this.Byte0];
                buffer[2] = high[this.Byte1];
                buffer[3] = low[this.Byte1];
                buffer[4] = high[this.Byte2];
                buffer[5] = low[this.Byte2];
                buffer[6] = high[this.Byte3];
                buffer[7] = low[this.Byte3];

                buffer[8] = (byte)'-';

                buffer[9] = high[this.Byte4];
                buffer[10] = low[this.Byte4];
                buffer[11] = high[this.Byte5];
                buffer[12] = low[this.Byte5];

                buffer[13] = (byte)'-';

                buffer[14] = high[this.Byte6];
                buffer[15] = low[this.Byte6];
                buffer[16] = high[this.Byte7];
                buffer[17] = low[this.Byte7];
            }

            buffer[18] = (byte)'-';

            buffer[19] = high[this.Byte8];
            buffer[20] = low[this.Byte8];
            buffer[21] = high[this.Byte9];
            buffer[22] = low[this.Byte9];

            buffer[23] = (byte)'-';

            buffer[24] = high[this.Byte10];
            buffer[25] = low[this.Byte10];
            buffer[26] = high[this.Byte11];
            buffer[27] = low[this.Byte11];
            buffer[28] = high[this.Byte12];
            buffer[29] = low[this.Byte12];
            buffer[30] = high[this.Byte13];
            buffer[31] = low[this.Byte13];
            buffer[32] = high[this.Byte14];
            buffer[33] = low[this.Byte14];
            buffer[34] = high[this.Byte15];
            buffer[35] = low[this.Byte15];
        }
    }
}
