package gozxing

import (
	"math/bits"

	errors "golang.org/x/xerrors"
)

type BitArray struct {
	bits []uint32
	size int
}

func NewEmptyBitArray() *BitArray {
	return &BitArray{makeArray(1), 0}
}

func NewBitArray(size int) *BitArray {
	return &BitArray{makeArray(size), size}
}

func (b *BitArray) GetSize() int {
	return b.size
}

func (b *BitArray) GetSizeInBytes() int {
	return (b.size + 7) / 8
}

func (b *BitArray) ensureCapacity(size int) {
	if size > len(b.bits)*32 {
		newBits := makeArray(size)
		copy(newBits, b.bits)
		b.bits = newBits
	}
}

func (b *BitArray) Get(i int) bool {
	return (b.bits[i/32] & (1 << uint(i%32))) != 0
}

func (b *BitArray) Set(i int) {
	b.bits[i/32] |= 1 << uint(i%32)
}

func (b *BitArray) Flip(i int) {
	b.bits[i/32] ^= 1 << uint(i%32)
}

func (b *BitArray) GetNextSet(from int) int {
	if from >= b.size {
		return b.size
	}
	bitsOffset := from / 32
	currentBits := b.bits[bitsOffset]
	currentBits &= -(1 << uint(from&0x1F))
	for currentBits == 0 {
		bitsOffset++
		if bitsOffset == len(b.bits) {
			return b.size
		}
		currentBits = b.bits[bitsOffset]
	}
	result := (bitsOffset * 32) + bits.TrailingZeros32(currentBits)
	if result > b.size {
		return b.size
	}
	return result
}

func (b *BitArray) GetNextUnset(from int) int {
	if from >= b.size {
		return b.size
	}
	bitsOffset := from / 32
	currentBits := ^b.bits[bitsOffset]
	currentBits &= -(1 << uint(from&0x1F))
	for currentBits == 0 {
		bitsOffset++
		if bitsOffset == len(b.bits) {
			return b.size
		}
		currentBits = ^b.bits[bitsOffset]
	}
	result := (bitsOffset * 32) + bits.TrailingZeros32(currentBits)
	if result > b.size {
		return b.size
	}
	return result
}

func (b *BitArray) SetBulk(i int, newBits uint32) {
	b.bits[i/32] = newBits
}

func (b *BitArray) SetRange(start, end int) error {
	if end < start || start < 0 || end > b.size {
		return errors.New("IllegalArgumentException")
	}
	if end == start {
		return nil
	}
	end--
	firstInt := start / 32
	lastInt := end / 32
	for i := firstInt; i <= lastInt; i++ {
		firstBit := 0
		lastBit := 31
		if i == firstInt {
			firstBit = start % 32
		}
		if i == lastInt {
			lastBit = end % 32
		}
		mask := (2 << uint(lastBit)) - (1 << uint(firstBit))
		b.bits[i] |= uint32(mask)
	}
	return nil
}

func (b *BitArray) Clear() {
	for i := range b.bits {
		b.bits[i] = 0
	}
}

func (b *BitArray) IsRange(start, end int, value bool) (bool, error) {
	if end < start || start < 0 || end > b.size {
		return false, errors.New("IllegalArgumentException")
	}
	if end == start {
		return true, nil
	}
	end--
	firstInt := start / 32
	lastInt := end / 32
	for i := firstInt; i <= lastInt; i++ {
		firstBit := 0
		lastBit := 31
		if i == firstInt {
			firstBit = start % 32
		}
		if i == lastInt {
			lastBit = end % 32
		}
		mask := uint32((2 << uint(lastBit)) - (1 << uint(firstBit)))
		expect := uint32(0)
		if value {
			expect = mask
		}
		if (b.bits[i] & mask) != expect {
			return false, nil
		}
	}
	return true, nil
}

func (b *BitArray) AppendBit(bit bool) {
	b.ensureCapacity(b.size + 1)
	if bit {
		b.bits[b.size/32] |= 1 << uint(b.size%32)
	}
	b.size++
}

func (b *BitArray) AppendBits(value int, numBits int) error {
	if numBits < 0 || numBits > 32 {
		return errors.New("IllegalArgumentException: Num bits must be between 0 and 32")
	}
	b.ensureCapacity(b.size + numBits)
	for numBitsLeft := numBits; numBitsLeft > 0; numBitsLeft-- {
		b.AppendBit(((value >> uint(numBitsLeft-1)) & 0x01) == 1)
	}
	return nil
}

func (b *BitArray) AppendBitArray(other *BitArray) {
	otherSize := other.size
	b.ensureCapacity(b.size + otherSize)
	for i := 0; i < otherSize; i++ {
		b.AppendBit(other.Get(i))
	}
}

func (b *BitArray) Xor(other *BitArray) error {
	if b.size != other.size {
		return errors.New("IllegalArgumentException: Sizes don't match")
	}
	for i := 0; i < len(b.bits); i++ {
		b.bits[i] ^= other.bits[i]
	}
	return nil
}

func (b *BitArray) ToBytes(bitOffset int, array []byte, offset, numBytes int) {
	for i := 0; i < numBytes; i++ {
		theByte := byte(0)
		for j := 0; j < 8; j++ {
			if b.Get(bitOffset) {
				theByte |= 1 << uint(7-j)
			}
			bitOffset++
		}
		array[offset+i] = theByte
	}
}

func (b *BitArray) GetBitArray() []uint32 {
	return b.bits
}

func (b *BitArray) Reverse() {
	newBits := make([]uint32, len(b.bits))
	len := (b.size - 1) / 32
	oldBitsLen := len + 1
	for i := 0; i < oldBitsLen; i++ {
		newBits[len-i] = bits.Reverse32(b.bits[i])
	}
	if b.size != oldBitsLen*32 {
		leftOffset := uint(oldBitsLen*32 - b.size)
		currentInt := newBits[0] >> leftOffset
		for i := 1; i < oldBitsLen; i++ {
			nextInt := newBits[i]
			currentInt |= nextInt << uint(32-leftOffset)
			newBits[i-1] = currentInt
			currentInt = nextInt >> leftOffset
		}
		newBits[oldBitsLen-1] = currentInt
	}
	b.bits = newBits
}

func makeArray(size int) []uint32 {
	return make([]uint32, (size+31)/32)
}

// equals()
// hasCode()

func (b *BitArray) String() string {
	result := make([]byte, 0, b.size+(b.size/8)+1)
	for i := 0; i < b.size; i++ {
		if (i % 8) == 0 {
			result = append(result, ' ')
		}
		if b.Get(i) {
			result = append(result, 'X')
		} else {
			result = append(result, '.')
		}
	}
	return string(result)
}

// clone()
