package leb128

import (
	
	
	
)

const (
	maxVarintLen32 = 5
	maxVarintLen33 = maxVarintLen32
	maxVarintLen64 = 10

	int33Mask  int64 = 1 << 7
	int33Mask2       = ^int33Mask
	int33Mask3       = 1 << 6
	int33Mask4       = 8589934591 // 2^33-1
	int33Mask5       = 1 << 32
	int33Mask6       = int33Mask4 + 1 // 2^33

	int64Mask3 = 1 << 6
	int64Mask4 = ^0
)

var (
	errOverflow32 = errors.New("overflows a 32-bit integer")
	errOverflow33 = errors.New("overflows a 33-bit integer")
	errOverflow64 = errors.New("overflows a 64-bit integer")
)

// EncodeInt32 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func ( int32) []byte {
	return EncodeInt64(int64())
}

// EncodeInt64 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func ( int64) ( []byte) {
	for {
		// Take 7 remaining low-order bits from the value into b.
		 := uint8( & 0x7f)
		// Extract the sign bit.
		 := uint8( & 0x40)
		 >>= 7

		// The encoding unsigned numbers is simpler as it only needs to check if the value is non-zero to tell if there
		// are more bits to encode. Signed is a little more complicated as you have to double-check the sign bit.
		// If either case, set the high-order bit to tell the reader there are more bytes in this int.
		if ( != -1 ||  == 0) && ( != 0 ||  != 0) {
			 |= 0x80
		}

		// Append b into the buffer
		 = append(, )
		if &0x80 == 0 {
			break
		}
	}
	return 
}

// EncodeUint32 encodes the value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer
func ( uint32) []byte {
	return EncodeUint64(uint64())
}

// EncodeUint64 encodes the value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer
func ( uint64) ( []byte) {
	// This is effectively a do/while loop where we take 7 bits of the value and encode them until it is zero.
	for {
		// Take 7 remaining low-order bits from the value into b.
		 := uint8( & 0x7f)
		 =  >> 7

		// If there are remaining bits, the value won't be zero: Set the high-
		// order bit to tell the reader there are more bytes in this uint.
		if  != 0 {
			 |= 0x80
		}

		// Append b into the buffer
		 = append(, )
		if &0x80 == 0 {
			return 
		}
	}
}

type nextByte func(i int) (byte, error)

func ( io.ByteReader) ( uint32,  uint64,  error) {
	return decodeUint32(func( int) (byte, error) { return .ReadByte() })
}

func ( []byte) ( uint32,  uint64,  error) {
	return decodeUint32(func( int) (byte, error) {
		if  >= len() {
			return 0, io.EOF
		}
		return [], nil
	})
}

func decodeUint32( nextByte) ( uint32,  uint64,  error) {
	// Derived from https://github.com/golang/go/blob/go1.24.0/src/encoding/binary/varint.go
	// with the modification on the overflow handling tailored for 32-bits.
	var  uint32
	for  := 0;  < maxVarintLen32; ++ {
		,  := ()
		if  != nil {
			return 0, 0, 
		}
		if  < 0x80 {
			// Unused bits must be all zero.
			if  == maxVarintLen32-1 && (&0xf0) > 0 {
				return 0, 0, errOverflow32
			}
			return  | uint32()<<, uint64() + 1, nil
		}
		 |= uint32(&0x7f) << 
		 += 7
	}
	return 0, 0, errOverflow32
}

func ( []byte) ( uint64,  uint64,  error) {
	 := len()
	if  == 0 {
		return 0, 0, io.EOF
	}

	// Derived from https://github.com/golang/go/blob/go1.24.0/src/encoding/binary/varint.go
	var  uint64
	for  := 0;  < maxVarintLen64; ++ {
		if  >=  {
			return 0, 0, io.EOF
		}
		 := []
		if  < 0x80 {
			// Unused bits (non first bit) must all be zero.
			if  == maxVarintLen64-1 &&  > 1 {
				return 0, 0, errOverflow64
			}
			return  | uint64()<<, uint64() + 1, nil
		}
		 |= uint64(&0x7f) << 
		 += 7
	}
	return 0, 0, errOverflow64
}

func ( io.ByteReader) ( int32,  uint64,  error) {
	return decodeInt32(func( int) (byte, error) { return .ReadByte() })
}

func ( []byte) ( int32,  uint64,  error) {
	return decodeInt32(func( int) (byte, error) {
		if  >= len() {
			return 0, io.EOF
		}
		return [], nil
	})
}

func decodeInt32( nextByte) ( int32,  uint64,  error) {
	var  int
	var  byte
	for {
		,  = (int())
		if  != nil {
			return 0, 0, fmt.Errorf("readByte failed: %w", )
		}
		 |= (int32() & 0x7f) << 
		 += 7
		++
		if &0x80 == 0 {
			if  < 32 && (&0x40) != 0 {
				 |= ^0 << 
			}
			// Over flow checks.
			// fixme: can be optimized.
			if  > maxVarintLen32 {
				return 0, 0, errOverflow32
			} else if  :=  & 0b00110000;  == maxVarintLen32 &&  < 0 &&  != 0b00110000 {
				return 0, 0, errOverflow32
			} else if  == maxVarintLen32 &&  >= 0 &&  != 0x00 {
				return 0, 0, errOverflow32
			}
			return
		}
	}
}

// DecodeInt33AsInt64 is a special cased decoder for wasm.BlockType which is encoded as a positive signed integer, yet
// still needs to fit the 32-bit range of allowed indices. Hence, this is 33, not 32-bit!
//
// See https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
func ( io.ByteReader) ( int64,  uint64,  error) {
	var  int
	var  int64
	var  byte
	for  < 35 {
		,  = .ReadByte()
		if  != nil {
			return 0, 0, fmt.Errorf("readByte failed: %w", )
		}
		 = int64()
		 |= ( & int33Mask2) << 
		 += 7
		++
		if &int33Mask == 0 {
			break
		}
	}

	// fixme: can be optimized
	if  < 33 && (&int33Mask3) == int33Mask3 {
		 |= int33Mask4 << 
	}
	 =  & int33Mask4

	// if 33rd bit == 1, we translate it as a corresponding signed-33bit minus value
	if &int33Mask5 > 0 {
		 =  - int33Mask6
	}
	// Over flow checks.
	// fixme: can be optimized.
	if  > maxVarintLen33 {
		return 0, 0, errOverflow33
	} else if  :=  & 0b00100000;  == maxVarintLen33 &&  < 0 &&  != 0b00100000 {
		return 0, 0, errOverflow33
	} else if  == maxVarintLen33 &&  >= 0 &&  != 0x00 {
		return 0, 0, errOverflow33
	}
	return , , nil
}

func ( io.ByteReader) ( int64,  uint64,  error) {
	return decodeInt64(func( int) (byte, error) { return .ReadByte() })
}

func ( []byte) ( int64,  uint64,  error) {
	return decodeInt64(func( int) (byte, error) {
		if  >= len() {
			return 0, io.EOF
		}
		return [], nil
	})
}

func decodeInt64( nextByte) ( int64,  uint64,  error) {
	var  int
	var  byte
	for {
		,  = (int())
		if  != nil {
			return 0, 0, fmt.Errorf("readByte failed: %w", )
		}
		 |= (int64() & 0x7f) << 
		 += 7
		++
		if &0x80 == 0 {
			if  < 64 && (&int64Mask3) == int64Mask3 {
				 |= int64Mask4 << 
			}
			// Over flow checks.
			// fixme: can be optimized.
			if  > maxVarintLen64 {
				return 0, 0, errOverflow64
			} else if  :=  & 0b00111110;  == maxVarintLen64 &&  < 0 &&  != 0b00111110 {
				return 0, 0, errOverflow64
			} else if  == maxVarintLen64 &&  >= 0 &&  != 0x00 {
				return 0, 0, errOverflow64
			}
			return
		}
	}
}