package no.nordicsemi.puckcentral.utils;
import java.util.ArrayList;
/* ported from http://bcl.comli.eu/ */
public class LZCompression {
final static int LZ_MAX_OFFSET = 100000;
public static byte[] compress(byte[] input) {
int[] histogram = new int[256];
ArrayList<Byte> output = new ArrayList<>();
/* Create histogram */
for(int i = 0; i < input.length; i++) {
histogram[input[i] & 0xff]++;
}
/* Find the least common byte, and use it as the marker symbol */
byte marker = 0;
for(int i = 0; i < 256; i++) {
if(histogram[i] < histogram[marker]) {
marker = (byte) i;
}
}
/* Remember the marker symbol for the decoder */
output.add(marker);
/* Start of compression */
int inputPosition = 0;
/* Main compression loop */
int bytesLeft = input.length;
do {
/* Determine most distant position */
int maxOffset = Math.min(LZ_MAX_OFFSET, inputPosition);
/* Get pointer to current position */
int currentPosition = inputPosition;
/* Search history window for maximum length string match */
int bestLength = 3;
int bestOffset = 0;
for(int offset = 3; offset <= maxOffset ; offset++) {
/* Get pointer to candidate string */
int candidateStringIndex = currentPosition - offset;
/* Quickly determine if this is a candidate (for speed) */
if((input[candidateStringIndex] == input[currentPosition]) &&
(input[candidateStringIndex + bestLength] == input[currentPosition + bestLength])) {
/* Determine maximum length for this offset */
int maxLength = (bytesLeft <= offset ? bytesLeft - 1: offset);
/* Count maximum length match at this offset */
int length = stringCompare(input, candidateStringIndex, currentPosition, 0, maxLength);
/* Better match than any previous match? */
if(length > bestLength) {
bestLength = length;
bestOffset = offset;
}
}
}
/* Was there a good enough match? */
if((bestLength>= 8) ||
((bestLength == 4) && (bestOffset <= 0x0000007f)) ||
((bestLength == 5) && (bestOffset <= 0x00003fff)) ||
((bestLength == 6) && (bestOffset <= 0x001fffff)) ||
((bestLength == 7) && (bestOffset <= 0x0fffffff)) ) {
output.add(marker);
writeVariableSize(bestLength, output);
writeVariableSize(bestOffset, output);
inputPosition += bestLength;
bytesLeft -= bestLength;
} else {
/* Output single byte (or two bytes if marker byte) */
byte symbol = input[inputPosition++];
output.add(symbol);
if(symbol == marker) {
output.add((byte) 0);
}
bytesLeft--;
}
} while(bytesLeft > 3);
/* Dump remaining bytes, if any */
while(inputPosition < input.length) {
if(input[inputPosition] == marker) {
output.add(marker);
output.add((byte) 0);
} else {
output.add(input[inputPosition]);
}
inputPosition++;
}
byte[] result = new byte[output.size()];
for(int i = 0; i < result.length; i++) {
result[i] = output.get(i);
}
return result;
}
private static int stringCompare(byte[]input, int string1Offset, int string2Offset, int minLength, int maxLength) {
int len = minLength;
while((len < maxLength) && (input[string1Offset + len] == input[string2Offset + len])) {
len++;
}
return len;
}
private static void writeVariableSize(int x, ArrayList<Byte> output) {
/* Determine number of bytes needed to store the number x */
int y = x >> 3;
int num_bytes;
for(num_bytes = 5; num_bytes >= 2; -- num_bytes ) {
if((y & 0xfe000000) != 0) {
break;
}
y <<= 7;
}
/* Write all bytes, seven bits in each, with
* 8:th bit set for all but the last byte. */
for(int i = num_bytes - 1; i >= 0; i--) {
byte b = (byte) ((x >> (i*7)) & 0x0000007f);
if(i > 0) {
b |= 0x00000080;
}
output.add(b);
}
}
}