package extraio

import (
	
)

// UnpadReader is an io.Reader that unpads padding from PadReader. It validates
// the padding on EOF and returns an error if it is invalid.
type UnpadReader struct {
	reader    TailReader
	blockSize uint8

	incomplete int    // mutable
	lastBlock  []byte // mutable
}

// NewUnpadReader returns a new reader that unpads r using the given block size.
// If blockSize is zero, UnpadReader is a no-op, i.e. it does not attempt to
// remove padding from the underlying reader.
func ( io.Reader,  uint8) *UnpadReader {
	var  []byte
	if  != 0 {
		 = make([]byte, 0, )
	}
	return &UnpadReader{
		reader: TailReader{
			reader: ,
			buf:    ,
		},
		blockSize: ,
	}
}

// Reset resets the reader’s state.
func ( *UnpadReader) () {
	.incomplete = 0
	.lastBlock = nil
	.reader.Reset()
}

// Read implements the io.Reader interface. It reads from the underlying
// io.Reader until EOF and then validates and removes padding from the last
// block.
func ( *UnpadReader) ( []byte) (int, error) {
	if .lastBlock != nil {
		return .unpad()
	}

	,  := .reader.Read()
	if .blockSize == 0 {
		return , 
	}
	if  > 0 {
		 := int(.blockSize)
		.incomplete +=  % 
		.incomplete %= 
	}
	if  != io.EOF {
		return , 
	}

	// Check that all read bytes are divisible into blocks (i.e. we are not
	// at the incomplete block).
	if .incomplete != 0 {
		return , io.ErrUnexpectedEOF
	}

	// We call Tail after checking for incomplete blocks in previously read
	// bytes since it may linearize the underlying bufffer before returning
	// it. This saves us a few CPU cycles needed to rotate the ring buffer
	// that would otherwise be unused.
	//
	// Note that 0 <= len(lastBlock) <= r.blockSize <= math.MaxUint8.
	 := .reader.Tail()

	// Check that the last block is also complete. If not, we have an
	// unexpected EOF.
	if uint8(len()) != .blockSize {
		return , io.ErrUnexpectedEOF
	}

	// Check that padding fill byte is within the block size.
	 := [len()-1]
	if  > .blockSize ||  == 0 {
		return , io.ErrUnexpectedEOF
	}

	// Check that padding is filled with same bytes.
	,  := unpadPayload(, )
	if ! {
		return , io.ErrUnexpectedEOF
	}

	.lastBlock = 
	,  := .unpad([:])
	return  + , 
}

// unpad writes remaining payload to p.
func ( *UnpadReader) ( []byte) (int, error) {
	 := copy(, .lastBlock)
	.lastBlock = .lastBlock[:]
	if len(.lastBlock) == 0 {
		.Reset()
		return , io.EOF
	}
	return , nil
}

// unpadPayload validates that buf is padded with fillByte bytes and returns the
// unpadded payload.
func ( []byte,  byte) ([]byte, bool) {
	 := int()
	if len() <  {
		return nil, false
	}
	 := len() - 

	 := [:]
	for ,  := range  {
		if  ==  {
			continue
		}
		return nil, false
	}

	return [:], true
}