#!/usr/bin/ruby

# Author: Trizen
# Date: 23 March 2023
# https://github.com/trizen

# Encode and decode a random list of integers into a binary string.

func encode_integers (Array integers) {

    var counts = []
    var count = 0
    var bits_per_symbol = 2
    var processed_len = 0

    for k in (integers) {
        while (k >= bits_per_symbol) {

            if (count > 0) {
                counts << [bits_per_symbol.len(2)-1, count]
                processed_len += count
            }

            count = 0
            bits_per_symbol *= 2
        }
        ++count
    }

    counts << [[bits_per_symbol.len(2)-1, integers.len - processed_len]].grep { .tail > 0 }...

    var clen = counts.len
    var header = clen.chr

    for b,len in (counts) {
        header += chr(b)
        header += pack('N', len)
    }

    var bits = []

    for b,len in (counts) {
        bits << integers.splice(0, len).map{ sprintf('%0*s', b, .as_bin) }...
    }

    header + pack('B*', bits.join)
}

func decode_integers (String str) {

    var str_fh = str.open_r(:raw)
    var count_len = str_fh.getc.ord
    var counts = []

    count_len.times {
        var b = str_fh.getc.ord
        var l = Num(unpack('N', 4.of { str_fh.getc }.join))
        counts << [b, l] if (l > 0)
    }

    var chunks = []
    var bits_fh = str_fh.slurp.ascii2bin.open_r(:raw)

    for b,len in (counts) {
        bits_fh.read(\var chunk, len*b)
        chunks << chunk.slices(b)...
    }

    chunks.map { .bin }
}

var integers = 1000.of { .irand }
var str      = encode_integers(integers.clone)

say ("Encoded length: ", str.len)
say ("Rawdata length: ", integers.join(' ').len)

var decoded = decode_integers(str)

assert_eq(integers, decoded)

__END__
Encoded length: 1158
Rawdata length: 3621