package gaffer.bitmap.serialisation.utils;

import uk.gov.gchq.gaffer.exception.SerialisationException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Contains a method for converting version 0.1.5 serialised RoaringBitmaps into
 * version 0.4.0-0.6.35 compatible forms.
 */
public class RoaringBitmapUtils {
    private static final int BITMAP_CONTAINER_SIZE = (1 << 16) / 8;
    private static final int MAX_ARRAY_CONTAINER_SIZE = 4096;
    private static final short VERSION_ZERO_ONE_FIVE_TO_ZERO_THREE_SEVEN_SERIAL_COOKIE = 12345;
    private static final short VERSION_ZERO_FOUR_ZERO_TO_SIX_THRIRTY_FIVE_NO_RUNCONTAINER_COOKIE = 12346;
    private static final short VERSION_ZERO_FIVE_ZERO_TO_SIX_THIRTY_FIVE_COOKIE = 12347;
    private static final byte[] VERSION_ZERO_FOUR_ZERO_TO_SIX_THRIRTY_FIVE_NO_RUNCONTAINER_COOKIE_BYTES = ByteBuffer.allocate(4).putInt(Integer.reverseBytes(VERSION_ZERO_FOUR_ZERO_TO_SIX_THRIRTY_FIVE_NO_RUNCONTAINER_COOKIE)).array();

    public static byte[] upConvertSerialisedForm(byte[] serialisedBitmap) throws SerialisationException {
        DataInputStream input = new DataInputStream(new ByteArrayInputStream(serialisedBitmap));
        int cookie;
        try {
            cookie = Integer.reverseBytes(input.readInt());
        } catch (IOException e) {
            throw new SerialisationException("I failed to read the bitmap version cookie");
        }

        if (cookie == VERSION_ZERO_ONE_FIVE_TO_ZERO_THREE_SEVEN_SERIAL_COOKIE) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
                DataOutputStream out = new DataOutputStream(baos);
                out.write(VERSION_ZERO_FOUR_ZERO_TO_SIX_THRIRTY_FIVE_NO_RUNCONTAINER_COOKIE_BYTES);
                int sizeInt = input.readInt();
                int size = Integer.reverseBytes(sizeInt);

                //Doesn't need to be reversed (already read as reversed)
                out.writeInt(sizeInt);
                int startOffSet = 4 + 4 + 4 * size + 4 * size;

                //Need to extract the cardinalities to calculate the offsets
                //Bitmap containers are a fixed sixe (BITMAP_CONTAINER_SIZE) and are used when the cardinality is greater than 4096
                //Array containers are variable size (cardinality * 2)
                int[] cardinalities = new int[size];
                for (int i = 0; i < size; ++i) {

                    //Read and write the key (Don't need to use this just copy it across)
                    out.writeShort(input.readShort());

                    //Get the cardinality and store it for calculating offsets
                    short cardShort = input.readShort();
                    cardinalities[i] = 1 + (0xFFFF & Short.reverseBytes(cardShort));

                    //Doesn't need reversing (already read as reversed)
                    out.writeShort(cardShort);
                }
                int currentOffSet = startOffSet;
                for (int i = 0; i < size; ++i) {
                    out.writeInt(Integer.reverseBytes(currentOffSet));
                    if (cardinalities[i] > MAX_ARRAY_CONTAINER_SIZE) {

                        //It's a bitmap container
                        currentOffSet += BITMAP_CONTAINER_SIZE;
                    } else {

                        //It's an array container
                        currentOffSet += (cardinalities[i] * 2);
                    }
                }
                int expectedNumContainerBytes = currentOffSet - startOffSet;

                //Write out all the container data
                int numContainerBytes = 0;
                int b;
                while ((b = input.read()) != -1) {
                    out.write(b);
                    ++numContainerBytes;
                }
                out.flush();
                if (numContainerBytes != expectedNumContainerBytes) {
                    throw new SerialisationException("I failed to convert roaring bitmap from pre 0.4.0 version");
                }
                return baos.toByteArray();
            } catch (SerialisationException e) {
                throw (e);
            } catch (IOException e) {
                throw new SerialisationException("IOException: I failed to convert roaring bitmap from pre 0.4.0 version");
            }
        } else if (cookie == VERSION_ZERO_FOUR_ZERO_TO_SIX_THRIRTY_FIVE_NO_RUNCONTAINER_COOKIE || (cookie & 0xFFFF) == VERSION_ZERO_FIVE_ZERO_TO_SIX_THIRTY_FIVE_COOKIE) {
            return serialisedBitmap;
        } else {
            throw new SerialisationException("I faled to find a known roaring bitmap cookie (cookie = " + cookie + ")");
        }
    }
}

@@ -937,33 +937,32 @@
 R_ZERO_TO_SIX_TH
-R
 IRTY_FIVE_NO_RUN
@@ -1842,33 +1842,32 @@
 R_ZERO_TO_SIX_TH
-R
 IRTY_FIVE_NO_RUN
@@ -2300,17 +2300,17 @@
 fixed si
-x
+z
 e (BITMA
@@ -4742,16 +4742,17 @@
 on(%22I fa
+i
 led to f

gh-674 Fix typos