/*
 * Copyright 2012-2017 Brian Campbell
 *
 * 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.jose4j.keys;

import org.junit.Test;

import java.math.BigInteger;
import java.util.Arrays;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

/**
 */
public class BigEndianBigIntegerTest
{
    @Test
    public void testExampleStuff()
    {
        basicConversionTest(BigEndianBigInteger.toBase64Url(ExampleRsaKeyFromJws.PUBLIC_KEY.getPublicExponent()));
        basicConversionTest(BigEndianBigInteger.toBase64Url(ExampleRsaKeyFromJws.PUBLIC_KEY.getModulus()));
        basicConversionTest(BigEndianBigInteger.toBase64Url(ExampleRsaKeyFromJws.PRIVATE_KEY.getPrivateExponent()));
    }

    @Test
    public void testBasicConversions()
    {
        for (int i = 0; i < 500; i++)
        {
            basicConversionTest(i);
        }
    }

    @Test
    public void testBasicConversions2()
    {
        for (long l = 200; l < Long.MAX_VALUE && l > 0; l=l*2)
        {
            for (int i = -100; i <= 100; i++)
            {
                basicConversionTest(l+i);
            }
        }
    }

    @Test
    public void testBasicConversionSub0()
    {
        try
        {
            basicConversionTest(-1);
            fail("negitive numbers shouldn't work");
        }
        catch (IllegalArgumentException e)
        {
        }
    }

    @Test
    public void testBasicConversionSub0MinLong()
    {
        try
        {
            basicConversionTest(Long.MIN_VALUE);
            fail("negitive numbers shouldn't work");
        }
        catch (IllegalArgumentException e)
        {
        }
    }

    @Test
    public void testBasicConversion0()
    {
        basicConversionTest(0);
    }

    @Test
    public void testBasicConversion1()
    {
        basicConversionTest(129);
    }

    @Test
    public void testBasicConversion2()
    {
        basicConversionTest(8388608);
    }

    @Test
    public void testBasicConversion3()
    {
        basicConversionTest(8388609);
    }

    @Test
    public void testBasicConversion4()
    {
        basicConversionTest(8388811);
    }

    @Test
    public void testBasicConversion5()
    {
        basicConversionTest(16777215);
    }

    @Test
    public void testBasicConversion6()
    {
        basicConversionTest(16777217);
    }

    @Test
    public void testBasicConversionMaxLong()
    {
        basicConversionTest(Long.MAX_VALUE);
    }

    private void basicConversionTest(long i)
    {
        BigInteger bigInt1 = BigInteger.valueOf(i);
        String b64 = BigEndianBigInteger.toBase64Url(bigInt1);
        BigInteger bigInt2= BigEndianBigInteger.fromBase64Url(b64);
        assertThat(bigInt1, is(equalTo(bigInt2)));

        byte[] bytes = BigEndianBigInteger.toByteArray(bigInt1);
        byte[] bytes2 = toByteArrayViaHex(bigInt1);
        assertArrayEquals("array comp on " + i + " " + Arrays.toString(bytes) + " " + Arrays.toString(bytes2), bytes, bytes2);
    }

    @Test
    public void testConversion1()
    {
        basicConversionTest("AQAB");
    }

    @Test
    public void testConversion2()
    {
        basicConversionTest("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4");
    }

    @Test
    public void testConversion3()
    {
        basicConversionTest("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM");
    }

    @Test
    public void testConversion4()
    {
        String s = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx" +
                "4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs" +
                "tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2" +
                "QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI" +
                "SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb" +
                "w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw";
        basicConversionTest(s);
    }

    private void basicConversionTest(String urlEncodedBytes)
    {
        BigInteger bigInt = BigEndianBigInteger.fromBase64Url(urlEncodedBytes);
        String b64 = BigEndianBigInteger.toBase64Url(bigInt);
        assertEquals(urlEncodedBytes, b64);
        BigInteger bigInt2 = BigEndianBigInteger.fromBase64Url(b64);
        assertEquals(bigInt, bigInt2);

        byte[] bytes = BigEndianBigInteger.toByteArray(bigInt);
        byte[] bytes2 = toByteArrayViaHex(bigInt);
        assertArrayEquals("array comp on " + urlEncodedBytes, bytes, bytes2);
    }

    private byte[] toByteArrayViaHex(BigInteger bigInteger)
    {
        // ugly but a sanity check
        int hexRadix = 16;
        String hexString = bigInteger.toString(hexRadix);
        hexString = (hexString.length() % 2 != 0) ? "0" + hexString : hexString;

        byte[] bytes = new byte[hexString.length() / 2];
        for (int idx = 0; idx < hexString.length(); idx+=2)
        {
            String hexPart = hexString.substring(idx, idx+2);
            bytes[idx/2] = (byte) Short.parseShort(hexPart, hexRadix);
        }

        return bytes;
    }

    @Test
    public void minArrayLengthOneByteNumbers()
    {
        BigInteger[] oneByteBigs = new BigInteger[]
        {
            BigInteger.ZERO, BigInteger.ONE, BigInteger.TEN, BigInteger.valueOf(127), BigInteger.valueOf(255)
        };

        for (BigInteger bi : oneByteBigs)
        {
            byte[] conciseBytes = BigEndianBigInteger.toByteArray(bi);
            assertThat(1, is(equalTo(conciseBytes.length)));
            BigInteger fromConciseByteArray = BigEndianBigInteger.fromBytes(conciseBytes);

            assertThat(bi, is(equalTo(fromConciseByteArray)));

            int[] minArrayLengths = new int[] {1, 2, 3, 5, 66};

            for (int minArrayLength : minArrayLengths)
            {
                byte[] zeroPaddedBytes = BigEndianBigInteger.toByteArray(bi, minArrayLength);
                assertThat(minArrayLength, is(equalTo(zeroPaddedBytes.length)));

                BigInteger fromZeroPaddedBytes = BigEndianBigInteger.fromBytes(zeroPaddedBytes);
                assertThat(bi, is(equalTo(fromZeroPaddedBytes)));
            }
        }
    }
    @Test
    public void minLengthEncoded()
    {
        String testNumber = "3411573884280259127265394545583489556845492233706098942622874385873783026581606817" +
                "805506341607692318868814372414764859287098904949502022867291016696377213417";

        String notPadded = "_nJhyQ20ca7Nn0Zvyiq54FfCAblGK7kuduFBTPkxv9eOjiaeGp7V_f3qV1kxS_Il2LY7Tc5l2GSlW_-SzYKxgek";
        String lftPadded = "AP5yYckNtHGuzZ9Gb8oqueBXwgG5Riu5LnbhQUz5Mb_Xjo4mnhqe1f396ldZMUvyJdi2O03OZdhkpVv_ks2CsYHp";

        BigInteger bigInteger1 = BigEndianBigInteger.fromBase64Url(notPadded);
        BigInteger bigInteger2 = BigEndianBigInteger.fromBase64Url(lftPadded);
        assertThat(bigInteger1, is(equalTo(bigInteger2)));

        BigInteger fromBase10 = new BigInteger(testNumber);
        assertThat(fromBase10, is(equalTo(bigInteger1)));

        String toBase64 = BigEndianBigInteger.toBase64Url(fromBase10);
        assertThat(toBase64, is(equalTo(notPadded)));

        String toBase64IncludingLeftPad = BigEndianBigInteger.toBase64Url(fromBase10, 66);
        assertThat(toBase64IncludingLeftPad, is(equalTo(lftPadded)));
    }

}
