package d2common

import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"

var _ d2interface.BitMuncher = &BitMuncher{} // Static check to confirm struct conforms to interface

// BitMuncher is used for parsing files that are not byte-aligned such as the DCC files.
type BitMuncher struct {
	data     []byte
	offset   int
	bitsRead int
}

const (
	twosComplimentNegativeOne = 4294967295
	byteLen                   = 8
	oneBit                    = 0x01
	fourBytes                 = byteLen * 4
)

// CreateBitMuncher Creates a BitMuncher
func CreateBitMuncher(data []byte, offset int) d2interface.BitMuncher {
	return (&BitMuncher{}).Init(data, offset)
}

// CopyBitMuncher Creates a copy of the source BitMuncher
func CopyBitMuncher(source d2interface.BitMuncher) d2interface.BitMuncher {
	return source.Copy()
}

// Init initializes the BitMuncher with data and an offset
func (v *BitMuncher) Init(data []byte, offset int) d2interface.BitMuncher {
	v.data = data
	v.offset = offset
	v.bitsRead = 0

	return v
}

// Copy returns a copy of a BitMuncher
func (v BitMuncher) Copy() d2interface.BitMuncher {
	v.bitsRead = 0
	return &v
}

// Offset returns the offset of the BitMuncher
func (v *BitMuncher) Offset() int {
	return v.offset
}

// SetOffset sets the offset of the BitMuncher
func (v *BitMuncher) SetOffset(n int) {
	v.offset = n
}

// BitsRead returns the number of bits the BitMuncher has read
func (v *BitMuncher) BitsRead() int {
	return v.bitsRead
}

// SetBitsRead sets the number of bits the BitMuncher has read
func (v *BitMuncher) SetBitsRead(n int) {
	v.bitsRead = n
}

// GetBit reads a bit and returns it as uint32
func (v *BitMuncher) GetBit() uint32 {
	result := uint32(v.data[v.offset/byteLen]>>uint(v.offset%byteLen)) & oneBit
	v.offset++
	v.bitsRead++

	return result
}

// SkipBits skips bits, incrementing the offset and bits read
func (v *BitMuncher) SkipBits(bits int) {
	v.offset += bits
	v.bitsRead += bits
}

// GetByte reads a byte from data
func (v *BitMuncher) GetByte() byte {
	return byte(v.GetBits(byteLen))
}

// GetInt32 reads an int32 from data
func (v *BitMuncher) GetInt32() int32 {
	return v.MakeSigned(v.GetBits(fourBytes), fourBytes)
}

// GetUInt32 reads an unsigned uint32 from data
func (v *BitMuncher) GetUInt32() uint32 {
	return v.GetBits(fourBytes)
}

// GetBits given a number of bits to read, reads that number of
// bits and retruns as a uint32
func (v *BitMuncher) GetBits(bits int) uint32 {
	if bits == 0 {
		return 0
	}

	result := uint32(0)
	for i := 0; i < bits; i++ {
		result |= v.GetBit() << uint(i)
	}

	return result
}

// GetSignedBits Given a number of bits, reads that many bits and returns as int
func (v *BitMuncher) GetSignedBits(bits int) int {
	return int(v.MakeSigned(v.GetBits(bits), bits))
}

// MakeSigned converts a uint32 value into an int32
func (v *BitMuncher) MakeSigned(value uint32, bits int) int32 {
	if bits == 0 {
		return 0
	}
	// If its a single bit, a value of 1 is -1 automagically
	if bits == 1 {
		return -int32(value)
	}
	// If there is no sign bit, return the value as is
	if (value & (1 << uint(bits-1))) == 0 {
		return int32(value)
	}

	// We need to extend the signed bit out so that the negative value
	// representation still works with the 2s compliment rule.
	result := uint32(twosComplimentNegativeOne)

	for i := byte(0); i < byte(bits); i++ {
		if ((value >> uint(i)) & 1) == 0 {
			result -= uint32(1 << uint(i))
		}
	}

	// Force casting to a signed value
	return int32(result)
}
