/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.wildfly.common.codec;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Assert;
import org.junit.Test;
import org.wildfly.common.iteration.ByteIterator;
import org.wildfly.common.iteration.CodePointIterator;

/**
 * Tests for Hex encoding and decoding. The expected results for these test cases have been
 * taken from the examples in <a href="https://tools.ietf.org/html/rfc4648">RFC 4648</a>.
 *
 * @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
 */
public class HexTest {

    public HexTest() {
    }

    @Test
    public void testEncodeEmpty() {
        CodePointIterator ci = CodePointIterator.ofString("").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample1() {
        CodePointIterator ci = CodePointIterator.ofString("f").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("66", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample2() {
        CodePointIterator ci = CodePointIterator.ofString("fo").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("666f", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample3() {
        CodePointIterator ci = CodePointIterator.ofString("foo").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("666f6f", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample4() {
        CodePointIterator ci = CodePointIterator.ofString("foob").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("666f6f62", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample5() {
        CodePointIterator ci = CodePointIterator.ofString("fooba").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("666f6f6261", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeRfcExample6() {
        CodePointIterator ci = CodePointIterator.ofString("foobar").asLatin1().hexEncode();
        String encoded = ci.drainToString();
        assertEquals("666f6f626172", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeUpperCase() {
        CodePointIterator ci = ByteIterator.ofBytes((byte) 0x1d, (byte) 0x34, (byte) 0xaf, (byte) 0xbc, (byte) 0x9e, (byte) 0x89).hexEncode(true);
        String encoded = ci.drainToString();
        assertEquals("1D34AFBC9E89", encoded);
        verifyBackwardIterationOfEncodedCharacters(ci, encoded);
    }

    @Test
    public void testEncodeWithFormatting() {
        Assert.assertEquals("66:6F:6F:62:61:72", CodePointIterator.ofString("foobar").asLatin1().hexEncode(true).drainToString(':', 2));
        Assert.assertEquals("0x66 0x6F 0x6F 0x62 0x61 0x72", CodePointIterator.ofString("foobar").asLatin1().hexEncode(true).drainToString("0x", ' ', 2));
        Assert.assertEquals("666F6F6261\n72666F6F62\n6172666F6F\n626172",
                CodePointIterator.ofString("foobarfoobarfoobar").asLatin1().hexEncode(true).drainToString('\n', 10));
    }

    private void verifyBackwardIterationOfEncodedCharacters(CodePointIterator ci, String encoded) {
        int encodedSize = encoded.length();
        for (int i = encodedSize - 1; i >= 0; i--) {
            assertTrue(ci.hasPrevious());
            Assert.assertEquals(encoded.charAt(i), ci.previous());
        }
    }

    @Test
    public void testDecodeEmpty() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample1() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("66").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("f", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample2() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("666f").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("fo", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample3() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("666f6f").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("foo", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample4() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("666f6f62").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("foob", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample5() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("666f6f6261").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("fooba", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeRfcExample6() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("666f6f626172").hexDecode();
        String decoded = bi.asUtf8String().drainToString();
        assertEquals("foobar", decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test
    public void testDecodeUpperCase() throws Exception {
        ByteIterator bi = CodePointIterator.ofString("1D34AFBC9E89").hexDecode();
        byte[] decoded = bi.drain();
        assertArrayEquals(new byte[]{(byte) 0x1d, (byte) 0x34, (byte) 0xaf, (byte) 0xbc, (byte) 0x9e, (byte) 0x89}, decoded);
        verifyBackwardIterationOfDecodedBytes(bi, decoded);
    }

    @Test(expected=DecodeException.class)
    public void testDecodeInvalidCharacter() throws Exception {
        CodePointIterator.ofString("6h666f6f626172g").hexDecode().drain();
    }

    @Test(expected=DecodeException.class)
    public void testDecodeInvalidLength() throws Exception {
        CodePointIterator.ofChars(new char[]{'1', 'F', 'A'}).hexDecode().drain();
    }

    private void verifyBackwardIterationOfDecodedBytes(ByteIterator bi, String decoded) {
        int decodedSize = decoded.length();
        for (int i = decodedSize - 1; i >= 0; i--) {
            assertTrue(bi.hasPrevious());
            Assert.assertEquals(decoded.charAt(i), bi.previous());
        }
    }

    private void verifyBackwardIterationOfDecodedBytes(ByteIterator bi, byte[] decoded) {
        int decodedSize = decoded.length;
        for (int i = decodedSize - 1; i >= 0; i--) {
            assertTrue(bi.hasPrevious());
            assertEquals(decoded[i], (byte) bi.previous());
        }
    }
}
