/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */
package org.apache.commons.rng.examples.stress;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Tests for {@link Hex}.
 */
class HexTest {
    /** Upper-case hex digits. */
    private static final char[] DIGITS = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };

    /**
     * Test a round-trip hex encode and decode of a byte[] seed.
     *
     * <p>This test ensures that a generated byte[] seed recorded by the stress command
     * in the results file can be used to create the same seed.
     */
    @Test
    void testHexEncodeAndDecode() {
        // Empty bytes
        for (int size = 0; size < 10; size++) {
            assertHexEncodeAndDecode(new byte[size]);
        }
        // Random bytes
        for (int size : new int[] {3, 4, 5, 8, 16, 31}) {
            final byte[] bytes = new byte[size];
            for (int i = 0; i < 5; i++) {
                ThreadLocalRandom.current().nextBytes(bytes);
                assertHexEncodeAndDecode(bytes);
            }
        }
    }

    /**
     * Assert the bytes can be encoded and decoded.
     *
     * @param bytes the bytes
     */
    private static void assertHexEncodeAndDecode(byte[] bytes) {
        // Use a StringBuilder to append the chars as this is the method used in the stress command.
        final StringBuilder sb = new StringBuilder();
        sb.append(Hex.encodeHex(bytes));
        final byte[] decoded = Hex.decodeHex(sb);
        Assertions.assertArrayEquals(bytes, decoded);
    }

    /**
     * Test a round-trip hex decode and encode of a char[] seed.
     *
     * <p>This test ensures that a user input random hex seed passed to the stress command
     * can be converted to bytes and then recorded in the results file.
     */
    @Test
    void testHexDecodeAndEncode() {
        // Note: char[] must be an even length.
        // Empty chars.
        for (int size = 0; size < 10; size++) {
            final char[] chars = new char[size * 2];
            Arrays.fill(chars, '0');
            assertHexDecodeAndEncode(chars);
        }
        // Random bytes
        for (int size : new int[] {3, 4, 5, 8, 16, 31}) {
            final char[] chars = new char[size * 2];
            for (int i = 0; i < 5; i++) {
                // Encode upper case
                for (int j = 0; j < chars.length; j++) {
                    chars[j] = DIGITS[ThreadLocalRandom.current().nextInt(16)];
                }
                assertHexDecodeAndEncode(chars);
            }
        }
    }

    /**
     * Assert the characters can be decoded and encoded.
     *
     * @param chars the chars
     */
    private static void assertHexDecodeAndEncode(char[] chars) {
        final String text = String.valueOf(chars);
        final byte[] decoded = Hex.decodeHex(text);
        // Test the encoding is lower case
        Assertions.assertArrayEquals(text.toLowerCase(Locale.US).toCharArray(), Hex.encodeHex(decoded));
    }

    @Test
    void testHexThrowsWithOddNumberOfCharacters() {
        Assertions.assertThrows(IllegalArgumentException.class, () -> Hex.decodeHex("0"));
    }

    @Test
    void testHexThrowsWithIllegalHexCharacters() {
        Assertions.assertThrows(IllegalArgumentException.class, () -> Hex.decodeHex("0g"));
    }
}
