// <copyright file="Base64UrlEncoder.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>

using System;

namespace OpenQA.Selenium.Internal;

/*
 * Based on: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6.19.0/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs
 *
 * Now it is a part of .NET 9+ as System.Buffers.Text.Base64Url
 * https://github.com/SeleniumHQ/selenium/issues/14813
 */

/// <summary>
/// Encodes and Decodes strings as Base64Url encoding.
/// </summary>
public static class Base64UrlEncoder
{
    private const char base64PadCharacter = '=';
    private const string doubleBase64PadCharacter = "==";
    private const char base64Character62 = '+';
    private const char base64Character63 = '/';
    private const char base64UrlCharacter62 = '-';
    private const char base64UrlCharacter63 = '_';

    /// <summary>
    /// Encoding table
    /// </summary>
    internal static readonly char[] s_base64Table =
    {
        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
        '0','1','2','3','4','5','6','7','8','9',
        base64UrlCharacter62,
        base64UrlCharacter63
    };

    /// <summary>
    /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation which is encoded with base-64-url digits.
    /// </summary>
    /// <param name="inArray">An array of 8-bit unsigned integers.</param>
    /// <returns>The string representation in base 64 url encoding of length elements of inArray, starting at position offset.</returns>
    /// <exception cref="ArgumentNullException">'inArray' is null.</exception>
    public static string Encode(byte[] inArray)
    {
        _ = inArray ?? throw new ArgumentNullException(nameof(inArray));

        if (inArray.Length == 0)
            return string.Empty;

        var length = inArray.Length;

        int lengthmod3 = length % 3;
        int limit = length - lengthmod3;
        char[] output = new char[(length + 2) / 3 * 4];
        char[] table = s_base64Table;
        int i, j = 0;

        // takes 3 bytes from inArray and insert 4 bytes into output
        for (i = 0; i < limit; i += 3)
        {
            byte d0 = inArray[i];
            byte d1 = inArray[i + 1];
            byte d2 = inArray[i + 2];

            output[j + 0] = table[d0 >> 2];
            output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];
            output[j + 2] = table[((d1 & 0x0f) << 2) | (d2 >> 6)];
            output[j + 3] = table[d2 & 0x3f];
            j += 4;
        }

        //Where we left off before
        i = limit;

        switch (lengthmod3)
        {
            case 2:
                {
                    byte d0 = inArray[i];
                    byte d1 = inArray[i + 1];

                    output[j + 0] = table[d0 >> 2];
                    output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];
                    output[j + 2] = table[(d1 & 0x0f) << 2];
                    j += 3;
                }
                break;

            case 1:
                {
                    byte d0 = inArray[i];

                    output[j + 0] = table[d0 >> 2];
                    output[j + 1] = table[(d0 & 0x03) << 4];
                    j += 2;
                }
                break;

                //default or case 0: no further operations are needed.
        }

        return new string(output, 0, j);
    }

    /// <summary>
    ///  Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array.</summary>
    /// <param name="str">base64Url encoded string.</param>
    /// <returns>UTF8 bytes.</returns>
    public static byte[] DecodeBytes(string str)
    {
        _ = str ?? throw new ArgumentNullException(nameof(str));

        // 62nd char of encoding
        str = str.Replace(base64UrlCharacter62, base64Character62);

        // 63rd char of encoding
        str = str.Replace(base64UrlCharacter63, base64Character63);

        // check for padding
        switch (str.Length % 4)
        {
            case 0:
                // No pad chars in this case
                break;
            case 2:
                // Two pad chars
                str += doubleBase64PadCharacter;
                break;
            case 3:
                // One pad char
                str += base64PadCharacter;
                break;
            default:
                throw new FormatException(str);
        }

        return Convert.FromBase64String(str);
    }
}
