module bf

/*
bf (BitField) is a module (shared library for V programming language) for
manipulating arrays of bits, i.e. series of zeroes and ones spread across an
array of storage units (unsigned 32-bit integers).

BitField structure
------------------

Bit arrays are stored in data structures called 'BitField'. The structure is
'opaque', i.e. its internals are not available to the end user. This module
provides API (functions and methods) for accessing and modifying bit arrays.
*/

struct BitField {
mut:
	size int
	//field *u32
	field []u32
}

// helper functions  
const (
	SLOT_SIZE = 32
)

fn bitmask(bitnr int) u32 {
	return u32(u32(1) << u32(bitnr % SLOT_SIZE))
}

fn bitslot(size int) int {
	return size / SLOT_SIZE
}

fn bitget(instance BitField, bitnr int) int {
	return (instance.field[bitslot(bitnr)] >> u32(bitnr % SLOT_SIZE)) & 1
}

fn bitset(instance mut BitField, bitnr int) {
	instance.field[bitslot(bitnr)] = instance.field[bitslot(bitnr)] | bitmask(bitnr)
}

fn bitclear(instance mut BitField, bitnr int) {
	instance.field[bitslot(bitnr)] = instance.field[bitslot(bitnr)] & ~bitmask(bitnr)
}

fn bittoggle(instance mut BitField, bitnr int) {
	instance.field[bitslot(bitnr)] = instance.field[bitslot(bitnr)] ^ bitmask(bitnr)
}
/*
#define BITTEST(a, b) ((a)->field[BITSLOT(b)] & BITMASK(b))
*/

fn min(input1 int, input2 int) int {
	if input1 < input2 {
		return input1
	}
	else {
		return input2
	}
}

fn bitnslots(length int) int {
	return (length - 1) / SLOT_SIZE + 1
} 

fn cleartail(instance mut BitField) {
	tail := instance.size % SLOT_SIZE
	if tail != 0 {
		// create a mask for the tail 
		mask := u32((1 << tail) - 1)
		// clear the extra bits 
		instance.field[bitnslots(instance.size) - 1] = instance.field[bitnslots(instance.size) - 1] & mask
	}
}

// public functions 

// str2bf() converts a string of characters ('0' and '1') to a bit
// array. Any character different from '0' is treated as '1'.

pub fn str2bf(input string) BitField {
	mut output := new(input.len)
	for i := 0; i < input.len; i++ {
		if input[i] != 48 {
			output.setbit(i)
		}
	}
	return output
}

// string() converts the bit array to a string of characters ('0' and '1') and
// return the string

pub fn (input BitField) string() string {
	mut output := ''
	for i := 0; i < input.size; i++ {
		if input.getbit(i) == 1 {
			output = output + '1'
		}
		else {
			output = output + '0'
		}
	}
	return output
}

//new() creates an empty bit array of capable of storing 'size' bits.

pub fn new(size int) BitField {
	output := BitField{
		size: size 
		//field: *u32(calloc(bitnslots(size) * SLOT_SIZE / 8))
		field: [u32(0); bitnslots(size)]
	}
	return output
}
/*
pub fn del(instance *BitField) {
	free(instance.field)
	free(instance)
}
*/

// getbit() returns the value (0 or 1) of bit number 'bit_nr' (count from
// 0)

pub fn (instance BitField) getbit(bitnr int) int {
	if bitnr >= instance.size {return 0}
	return bitget(instance, bitnr)
}

// setbit() set bit number 'bit_nr' to 1 (count from 0)

pub fn (instance mut BitField) setbit(bitnr int) {
	if bitnr >= instance.size {return}
	bitset(mut instance, bitnr)
}

// clearbit() clears (sets to zero) bit number 'bit_nr' (count from 0)

pub fn (instance mut BitField) clearbit(bitnr int) {
	if bitnr >= instance.size {return}
	bitclear(mut instance, bitnr)
}

// setall() sets all bits in the array to 1

pub fn (instance mut BitField) setall() {
	for i := 0; i < bitnslots(instance.size); i++ {
		instance.field[i] = u32(-1)
	}
	cleartail(mut instance)
}

// clearall() clears (sets to zero) all bits in the array

pub fn (instance mut BitField) clearall() {
	for i := 0; i < bitnslots(instance.size); i++ {
		instance.field[i] = u32(0)
	}
}

// togglebit() change the value (from 0 to 1 or from 1 to 0) of bit
// number 'bit_nr'

pub fn (instance mut BitField) togglebit(bitnr int) {
	if bitnr >= instance.size {return}
	bittoggle(mut instance, bitnr)
}

// bfand() perform logical AND operation on every pair of bits from 'input1'
// and 'input2' and return the result as a new array. If inputs differ in size,
// the tail of the longer one is ignored.

pub fn bfand(input1 BitField, input2 BitField) BitField {
	size := min(input1.size, input2.size)
	bitnslots := bitnslots(size)
	mut output := new(size)
	mut i := 0
	for i < bitnslots {
		output.field[i] = input1.field[i] & input2.field[i]
		i++
	}
	cleartail(mut output)
	return output
}

// bfnot() toggle all bits in a bit array and return the result as a new array

pub fn bfnot(input BitField) BitField {
	size := input.size
	bitnslots := bitnslots(size)
	mut output := new(size)
	mut i := 0
	for i < bitnslots {
		output.field[i] = ~input.field[i]
		i++
	}
	cleartail(mut output)
	return output
}

// bfor() perform logical OR operation on every pair of bits from 'input1' and
// 'input2' and return the result as a new array. If inputs differ in size, the
// tail of the longer one is ignored.

pub fn bfor(input1 BitField, input2 BitField) BitField {
	size := min(input1.size, input2.size)
	bitnslots := bitnslots(size)
	mut output := new(size)
	mut i := 0
	for i < bitnslots {
		output.field[i] = input1.field[i] | input2.field[i]
		i++
	}
	cleartail(mut output)
	return output
}

// bfxor(input1 BitField, input2 BitField) perform logical XOR operation on
// every pair of bits from 'input1' and 'input2' and return the result as a new
// array. If inputs differ in size, the tail of the longer one is ignored.

pub fn bfxor(input1 BitField, input2 BitField) BitField {
	size := min(input1.size, input2.size)
	bitnslots := bitnslots(size)
	mut output := new(size)
	mut i := 0
	for i < bitnslots {
		output.field[i] = input1.field[i] ^ input2.field[i]
		i++
	}
	cleartail(mut output)
	return output
}

// join() concatenates two bit arrays and return the result as a new array.

pub fn join(input1 BitField, input2 BitField) BitField {
	output_size := input1.size + input2.size
	mut output := new(output_size)
	// copy the first input to output as is
	for i := 0; i < bitnslots(input1.size); i++ {
		output.field[i] = input1.field[i]
	}

	// find offset bit and offset slot
	offset_bit := input1.size % SLOT_SIZE
	offset_slot := input1.size / SLOT_SIZE

	for i := 0; i < bitnslots(input2.size); i++ {
		output.field[i + offset_slot] =
		    output.field[i + offset_slot] |
		    u32(input2.field[i] << u32(offset_bit))
	}

	/*
	 * If offset_bit is not zero, additional operations are needed.
	 * Number of iterations depends on the nr of slots in output. Two
	 * options:
	 * (a) nr of slots in output is the sum of inputs' slots. In this
	 * case, the nr of bits in the last slot of output is less than the
	 * nr of bits in second input (i.e. ), OR
	 * (b) nr of slots of output is the sum of inputs' slots less one
	 * (i.e. less iterations needed). In this case, the nr of bits in
	 * the last slot of output is greater than the nr of bits in second
	 * input.
	 * If offset_bit is zero, no additional copies needed.
	 */
	if (output_size - 1) % SLOT_SIZE < (input2.size - 1) % SLOT_SIZE {
		for i := 0; i < bitnslots(input2.size); i++ {
			output.field[i + offset_slot + 1] =
			    output.field[i + offset_slot + 1] |
			    u32(input2.field[i] >> u32(SLOT_SIZE - offset_bit))
		}
	} else if (output_size - 1) % SLOT_SIZE > (input2.size - 1) % SLOT_SIZE {
		for i := 0; i < bitnslots(input2.size) - 1; i++ {
			output.field[i + offset_slot + 1] =
			    output.field[i + offset_slot + 1] |
			    u32(input2.field[i] >> u32(SLOT_SIZE - offset_bit))
		}
	}
	return output
}

// print(instance BitField) send the content of a bit array to stdout as a
// string of characters ('0' and '1').

pub fn print(instance BitField) {
	mut i := 0
	for i < instance.size {
		if instance.getbit(i) == 1 {
			print('1')
		}
		else {
			print('0')
		}
		i++
	}
}

// getsize() returns the number of bits the array can hold

pub fn (instance BitField) getsize() int {
	return instance.size
}

// clone() create a copy of a bit array

pub fn clone(input BitField) BitField {
	bitnslots := bitnslots(input.size)
	mut output := new(input.size)
	mut i := 0
	for i < bitnslots {
		output.field[i] = input.field[i]
		i++
	}
	return output
}

// cmp() compare two bit arrays bit by bit and return 'true' if they are
// identical by length and contents and 'false' otherwise.

pub fn cmp(input1 BitField, input2 BitField) bool {
	if input1.size != input2.size {return false}
	for i := 0; i < bitnslots(input1.size); i++ {
		if input1.field[i] != input2.field[i] {return false}
	}
	return true
}

// popcount() returns the number of set bits (ones) in the array

pub fn (instance BitField) popcount() int {
	size := instance.size
	bitnslots := bitnslots(size)
	tail := size % SLOT_SIZE
	mut count := 0
	for i := 0; i < bitnslots - 1; i++ {
		for j := 0; j < SLOT_SIZE; j++ {
			if u32(instance.field[i] >> u32(j)) & u32(1) == u32(1) {
				count++
			}
		}
	}
	for j := 0; j < tail; j++ {
		if u32(instance.field[bitnslots - 1] >> u32(j)) & u32(1) == u32(1) {
			count++
		}
	}
	return count
}

// hamming () compute the Hamming distance between two bit arrays.

pub fn hamming (input1 BitField, input2 BitField) int {
	input_xored := bfxor(input1, input2)
	return input_xored.popcount()
}

// pos() checks if the array contains a sub-array 'needle' and returns its
// position if it does, -1 if it does not, and -2 on error.

pub fn (haystack BitField) pos(needle BitField) int {
	heystack_size := haystack.size
	needle_size := needle.size
	diff := heystack_size - needle_size

	// needle longer than haystack; return error code -2
	if diff < 0 {
		return -2
	}
	for i := 0; i <= diff; i++ {
		needle_candidate := haystack.slice(i, needle_size + i)
		if cmp(needle_candidate, needle) {
			// needle matches a sub-array of haystack; return starting position of the sub-array
			return i
		}
	}
	// nothing matched; return -1
	return -1
}

// slice() return a sub-array of bits between 'start_bit_nr' (included) and 
// 'end_bit_nr' (excluded)

pub fn (input BitField) slice(_start int, _end int) BitField {
	// boundary checks
	mut start := _start
	mut end := _end
	if end > input.size {
		end = input.size // or panic?
	}
	if start > end {
		start = end // or panic?
	}

	mut output := new(end - start)
	start_offset := start % SLOT_SIZE
	end_offset := (end - 1) % SLOT_SIZE
	start_slot := start / SLOT_SIZE
	end_slot := (end - 1) / SLOT_SIZE
	output_slots := bitnslots(end - start)

	if output_slots > 1 {
		if start_offset != 0 {
			for i := 0; i < output_slots - 1; i++ {
				output.field[i] =
				    u32(input.field[start_slot + i] >> u32(start_offset))
				output.field[i] = output.field[i] |
				    u32(input.field[start_slot + i + 1] <<
				    u32(SLOT_SIZE - start_offset))
			}
		}
		else {
			for i := 0; i < output_slots - 1; i++ {
				output.field[i] =
				    u32(input.field[start_slot + i])
			}
		}
	}

	if start_offset > end_offset {
		output.field[(end - start - 1) / SLOT_SIZE] =
		    u32(input.field[end_slot - 1] >> u32(start_offset))
		mut mask := u32((1 << (end_offset + 1)) - 1)
		mask = input.field[end_slot] & mask
		mask = u32(mask << u32(SLOT_SIZE - start_offset))
		output.field[(end - start - 1) / SLOT_SIZE] =
		    output.field[(end - start - 1) / SLOT_SIZE] | mask
	}
	else if start_offset == 0 {
		mut mask := u32(0)
		if end_offset == SLOT_SIZE - 1 {
			mask = u32(-1)
		}
		else {
			mask = u32(u32(1) << u32(end_offset + 1))
			mask = mask - u32(1)
		}
		output.field[(end - start - 1) / SLOT_SIZE] =
		    (input.field[end_slot] & mask)
	}
	else {
		mut mask := u32(((1 << (end_offset - start_offset + 1)) - 1)  << start_offset)
		mask = input.field[end_slot] & mask
		mask = u32(mask >> u32(start_offset))
		output.field[(end - start - 1) / SLOT_SIZE] =
		    output.field[(end - start - 1) / SLOT_SIZE] | mask
	}
	return output
}

// reverse() reverses the order of bits in the array (swap the first with the
// last, the second with the last but one and so on)

pub fn (instance mut BitField) reverse() BitField {
	size := instance.size
	bitnslots := bitnslots(size)
	mut output := new(size)
	for i:= 0; i < (bitnslots - 1); i++ {
		for j := 0; j < SLOT_SIZE; j++ {
			if u32(instance.field[i] >> u32(j)) & u32(1) == u32(1) {
				bitset(mut output, size - i * SLOT_SIZE - j - 1)
			}
		}
	}
	bits_in_last_input_slot := (size - 1) % SLOT_SIZE + 1
	for j := 0; j < bits_in_last_input_slot; j++ {
		if u32(instance.field[bitnslots - 1] >> u32(j)) & u32(1) == u32(1) {
			bitset(mut output, bits_in_last_input_slot - j - 1)
		}
	}
	return output
}

// resize() changes the size of the bit array to 'new_size'

pub fn (instance mut BitField) resize(size int) {
	new_bitnslots := bitnslots(size)
	old_size := instance.size
	old_bitnslots := bitnslots(old_size)
	mut field := [u32(0); new_bitnslots]
	for i := 0; i < old_bitnslots && i < new_bitnslots; i++ {
		field[i] = instance.field[i]
	}
	instance.field = field
	instance.size = size
	if size < old_size && size % SLOT_SIZE != 0 {
		cleartail(mut instance)
	}
}

// rotate(offset int) circular-shift the bits by 'offset' positions (move
// 'offset' bit to 0, 'offset+1' bit to 1, and so on)

pub fn (instance BitField) rotate(offset int) BitField {
	/**
	 * This function "cuts" the bitfield into two and swaps them.
	 * If the offset is positive, the cutting point is counted from the
	 * beginning of the bit array, otherwise from the end.
	**/
	size := instance.size
	// removing extra rotations

	mut offset_internal := offset % size
	if (offset_internal == 0) {
		// nothing to shift
		return instance
	}
	if offset_internal < 0 {
		offset_internal = offset_internal + size
	}

	first_chunk := instance.slice(0, offset_internal)
	second_chunk := instance.slice(offset_internal, size)
	output := join(second_chunk, first_chunk)
	return output
}
