package com.splicemachine.encoding;

import com.google.common.collect.Lists;
import com.splicemachine.testutil.RandomDerbyDecimalBuilder;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static org.junit.Assert.assertTrue;

/*
 * Test BigDecimalEncoding with random values.
 */
@RunWith(Parameterized.class)
public class BigDecimalEncoding_RandomizedTest {

    private static final int NUM_TESTS =50;
    private static final int NUM_VALUES_PER_TEST =1000;
    private static final RandomDerbyDecimalBuilder DERBY_DECIMAL_BUILDER = new RandomDerbyDecimalBuilder().withNegatives(true);

    @Parameterized.Parameters
    public static Collection<Object[]> getParameters() {
        Collection<Object[]> data = Lists.newArrayListWithCapacity(NUM_TESTS);
        for (int i = 0; i < NUM_TESTS; i++) {
            data.add(new Object[]{DERBY_DECIMAL_BUILDER.buildArray(NUM_VALUES_PER_TEST)});
        }
        return data;
    }

    private final BigDecimal[] data;

    public BigDecimalEncoding_RandomizedTest(BigDecimal[] data) {
        this.data = data;
    }

    @Test
    public void testCanSerializeAndDeserializeCorrectly() throws Exception {
        for(BigDecimal decimal:data){
            byte[] data = BigDecimalEncoding.toBytes(decimal, false);
            BigDecimal ret = BigDecimalEncoding.toBigDecimal(data, false);

            assertTrue(decimal.compareTo(ret) == 0);

            //decode the negative version too, just to make sure
            byte[] negData = BigDecimalEncoding.toBytes(decimal.negate(), false);
            ret  = BigDecimalEncoding.toBigDecimal(negData, false);

            assertTrue(decimal.negate().compareTo(ret) == 0);
        }
    }

    @Test
    public void testSortsBytesCorrectly() throws Exception {
        List<byte[]> serializedDecimals = Lists.newArrayList();
        for (BigDecimal aData : data) {
            serializedDecimals.add(BigDecimalEncoding.toBytes(aData, false));
        }

        Collections.sort(serializedDecimals, Bytes.BYTES_COMPARATOR);

        //deserialize
        BigDecimal last = null;
        BigDecimal current;
        for (byte[] serializedBytes : serializedDecimals) {
            current = BigDecimalEncoding.toBigDecimal(serializedBytes, false);
            assertTrue(String.format("last='%s', current='%s'", last, current), last == null || current.compareTo(last) >= 0);
            last = current;
        }
    }

    @Test
    public void testCanDecodeWhenManuallyConverted() throws Exception {
        /*
         * Some bits of code will manually convert from ascending to descending and back, this
         * makes sure that BigDecimals work correctly in that situation
         */

        for(BigDecimal testNum:data){
            byte[] bigDecBytes = Encoding.encode(testNum, false);

            BigDecimal result = Encoding.decodeBigDecimal(convertToDescending(bigDecBytes), true);

            assertTrue(result.compareTo(testNum) == 0);

            //check the negation as well
            BigDecimal t = testNum.negate();
            bigDecBytes = Encoding.encode(t, false);

            result = Encoding.decodeBigDecimal(convertToDescending(bigDecBytes), true);

            assertTrue(result.compareTo(t) == 0);
        }

    }

    private byte[] convertToDescending(byte[] bytes){
        byte[] retBytes = new byte[bytes.length];
        System.arraycopy(bytes,0,retBytes,0,bytes.length);
        for(int i=0;i<retBytes.length;i++){
            retBytes[i] ^=0xff;
        }

        return retBytes;
    }
}

@@ -601,16 +601,17 @@
 _TESTS =
+ 
 50;%0A    
@@ -656,16 +656,17 @@
 R_TEST =
+ 
 1000;%0A  
@@ -1361,45 +1361,171 @@
 n %7B%0A
-        for(BigDecimal decimal:data)%7B
+%0A        final boolean DESCENDING = true;%0A        final boolean ASCENDING = false;%0A%0A        for(BigDecimal decimal:data)%7B%0A%0A            // encode/decode, descending
 %0A   
@@ -1540,20 +1540,18 @@
  byte%5B%5D 
-data
+b1
  = BigDe
@@ -1581,21 +1581,26 @@
 ecimal, 
-false
+DESCENDING
 );%0A     
@@ -1617,19 +1617,18 @@
 Decimal 
-ret
+d1
  = BigDe
@@ -1658,23 +1658,25 @@
 mal(
-data, false
+b1, DESCENDING
 );%0A
-%0A
     
@@ -1704,35 +1704,34 @@
 cimal.compareTo(
-ret
+d1
 ) == 0);%0A%0A      
@@ -1742,85 +1742,579 @@
   //
-decode the negative version too, just to make sure%0A            byte%5B%5D negData
+ encode/decode, descending, NEGATIVE%0A            byte%5B%5D b2 = BigDecimalEncoding.toBytes(decimal.negate(), DESCENDING);%0A            BigDecimal d2  = BigDecimalEncoding.toBigDecimal(b2, DESCENDING);%0A            assertTrue(decimal.negate().compareTo(d2) == 0);%0A%0A            // encode/decode, ascending%0A            byte%5B%5D b3 = BigDecimalEncoding.toBytes(decimal, ASCENDING);%0A            BigDecimal d3 = BigDecimalEncoding.toBigDecimal(b3, ASCENDING);%0A            assertTrue(decimal.compareTo(d3) == 0);%0A%0A            // encode/decode, ascending, NEGATIVE%0A            byte%5B%5D b4
  = B
@@ -2357,21 +2357,25 @@
 gate(), 
-false
+ASCENDING
 );%0A     
@@ -2385,12 +2385,21 @@
     
-ret 
+BigDecimal d4
  = B
@@ -2433,26 +2433,24 @@
 mal(
-negData, false
+b4, ASCENDING
 );%0A
-%0A
     
@@ -2495,19 +2495,18 @@
 mpareTo(
-ret
+d4
 ) == 0);
@@ -2498,32 +2498,33 @@
 reTo(d4) == 0);%0A
+%0A
         %7D%0A    %7D%0A

Improve existing test: actually test all values of boolean parameter for method under test.