// Copyright (c) 2022 Klaus Post. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package s2

import (
	
	
	
)

// LZ4Converter provides conversion from LZ4 blocks as defined here:
// https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
type LZ4Converter struct {
}

// ErrDstTooSmall is returned when provided destination is too small.
var ErrDstTooSmall = errors.New("s2: destination too small")

// ConvertBlock will convert an LZ4 block and append it as an S2
// block without block length to dst.
// The uncompressed size is returned as well.
// dst must have capacity to contain the entire compressed block.
func ( *LZ4Converter) (,  []byte) ([]byte, int, error) {
	if len() == 0 {
		return , 0, nil
	}
	const  = false
	const  = true
	const  = 4

	,  := 0, len()
	 = [:cap()]
	if ! && hasAmd64Asm {
		,  := cvtLZ4BlockAsm([:], )
		if  < 0 {
			const (
				     = -1
				 = -2
			)
			switch  {
			case :
				return nil, 0, ErrCorrupt
			case :
				return nil, 0, ErrDstTooSmall
			default:
				return nil, 0, fmt.Errorf("unexpected result: %d", )
			}
		}
		if + > len() {
			return nil, 0, ErrDstTooSmall
		}
		return [:+], , nil
	}

	 := len() - 10
	var  uint16
	var  int
	if  {
		fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(), len())
	}

	for {
		if  >= len() {
			return [:], 0, ErrCorrupt
		}
		// Read literal info
		 := []
		 := int( >> 4)
		 := int( + ( & 0xf))

		// If upper nibble is 15, literal length is extended
		if  >= 0xf0 {
			for {
				++
				if  >= len() {
					if  {
						fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", , len())
					}
					return [:], 0, ErrCorrupt
				}
				 := []
				 += int()
				if  != 255 {
					break
				}
			}
		}
		// Skip past token
		if + >= len() {
			if  {
				fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", , , len())
			}
			return nil, 0, ErrCorrupt
		}
		++
		if  > 0 {
			if + >  {
				return nil, 0, ErrDstTooSmall
			}
			if  {
				fmt.Printf("emit %d literals\n", )
			}
			 += emitLiteralGo([:], [:+])
			 += 
			 += 
		}

		// Check if we are done...
		if  == len() &&  ==  {
			break
		}
		// 2 byte offset
		if  >= len()-2 {
			if  {
				fmt.Printf("s (%d) >= len(src)-2 (%d)", , len()-2)
			}
			return nil, 0, ErrCorrupt
		}
		 := binary.LittleEndian.Uint16([:])
		 += 2
		if  == 0 {
			if  {
				fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", , len()-)
			}
			return nil, 0, ErrCorrupt
		}
		if int() >  {
			if  {
				fmt.Printf("error: offset (%d)> uncompressed (%d)\n", , )
			}
			return nil, 0, ErrCorrupt
		}

		if  == +15 {
			for {
				if  >= len() {
					if  {
						fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", , len())
					}
					return nil, 0, ErrCorrupt
				}
				 := []
				++
				 += int()
				if  != 255 {
					if  >= len() {
						if  {
							fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", , len())
						}
						return nil, 0, ErrCorrupt
					}
					break
				}
			}
		}
		if  ==  {
			if  {
				fmt.Printf("emit repeat, length: %d, offset: %d\n", , )
			}
			if ! {
				 += emitRepeat16([:], , )
			} else {
				 := 
				 := [:]
				for len() > 5 {
					// Repeat offset, make length cheaper
					 -= 4
					if  <= 4 {
						[0] = uint8()<<2 | tagCopy1
						[1] = 0
						 += 2
						break
					}
					if  < 8 &&  < 2048 {
						// Encode WITH offset
						[1] = uint8()
						[0] = uint8(>>8)<<5 | uint8()<<2 | tagCopy1
						 += 2
						break
					}
					if  < (1<<8)+4 {
						 -= 4
						[2] = uint8()
						[1] = 0
						[0] = 5<<2 | tagCopy1
						 += 3
						break
					}
					if  < (1<<16)+(1<<8) {
						 -= 1 << 8
						[3] = uint8( >> 8)
						[2] = uint8( >> 0)
						[1] = 0
						[0] = 6<<2 | tagCopy1
						 += 4
						break
					}
					const  = (1 << 24) - 1
					 -= 1 << 16
					 := 0
					if  >  {
						 =  -  + 4
						 =  - 4
					}
					[4] = uint8( >> 16)
					[3] = uint8( >> 8)
					[2] = uint8( >> 0)
					[1] = 0
					[0] = 7<<2 | tagCopy1
					if  > 0 {
						 += 5 + emitRepeat16([5:], , )
						break
					}
					 += 5
					break
				}
			}
		} else {
			if  {
				fmt.Printf("emit copy, length: %d, offset: %d\n", , )
			}
			if ! {
				 += emitCopy16([:], , )
			} else {
				 := 
				 := [:]
				for len() > 5 {
					// Offset no more than 2 bytes.
					if  > 64 {
						 := 3
						if  < 2048 {
							// emit 8 bytes as tagCopy1, rest as repeats.
							[1] = uint8()
							[0] = uint8(>>8)<<5 | uint8(8-4)<<2 | tagCopy1
							 -= 8
							 = 2
						} else {
							// Emit a length 60 copy, encoded as 3 bytes.
							// Emit remaining as repeat value (minimum 4 bytes).
							[2] = uint8( >> 8)
							[1] = uint8()
							[0] = 59<<2 | tagCopy2
							 -= 60
						}
						// Emit remaining as repeats, at least 4 bytes remain.
						 +=  + emitRepeat16([:], , )
						break
					}
					if  >= 12 ||  >= 2048 {
						// Emit the remaining copy, encoded as 3 bytes.
						[2] = uint8( >> 8)
						[1] = uint8()
						[0] = uint8(-1)<<2 | tagCopy2
						 += 3
						break
					}
					// Emit the remaining copy, encoded as 2 bytes.
					[1] = uint8()
					[0] = uint8(>>8)<<5 | uint8(-4)<<2 | tagCopy1
					 += 2
					break
				}
			}
			 = 
		}
		 += 
		if  >  {
			return nil, 0, ErrDstTooSmall
		}
	}

	return [:], , nil
}

// ConvertBlockSnappy will convert an LZ4 block and append it
// as a Snappy block without block length to dst.
// The uncompressed size is returned as well.
// dst must have capacity to contain the entire compressed block.
func ( *LZ4Converter) (,  []byte) ([]byte, int, error) {
	if len() == 0 {
		return , 0, nil
	}
	const  = false
	const  = 4

	,  := 0, len()
	 = [:cap()]
	// Use assembly when possible
	if ! && hasAmd64Asm {
		,  := cvtLZ4BlockSnappyAsm([:], )
		if  < 0 {
			const (
				     = -1
				 = -2
			)
			switch  {
			case :
				return nil, 0, ErrCorrupt
			case :
				return nil, 0, ErrDstTooSmall
			default:
				return nil, 0, fmt.Errorf("unexpected result: %d", )
			}
		}
		if + > len() {
			return nil, 0, ErrDstTooSmall
		}
		return [:+], , nil
	}

	 := len() - 10
	var  int
	if  {
		fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(), len())
	}

	for {
		if  >= len() {
			return nil, 0, ErrCorrupt
		}
		// Read literal info
		 := []
		 := int( >> 4)
		 := int( + ( & 0xf))

		// If upper nibble is 15, literal length is extended
		if  >= 0xf0 {
			for {
				++
				if  >= len() {
					if  {
						fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", , len())
					}
					return nil, 0, ErrCorrupt
				}
				 := []
				 += int()
				if  != 255 {
					break
				}
			}
		}
		// Skip past token
		if + >= len() {
			if  {
				fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", , , len())
			}
			return nil, 0, ErrCorrupt
		}
		++
		if  > 0 {
			if + >  {
				return nil, 0, ErrDstTooSmall
			}
			if  {
				fmt.Printf("emit %d literals\n", )
			}
			 += emitLiteralGo([:], [:+])
			 += 
			 += 
		}

		// Check if we are done...
		if  == len() &&  ==  {
			break
		}
		// 2 byte offset
		if  >= len()-2 {
			if  {
				fmt.Printf("s (%d) >= len(src)-2 (%d)", , len()-2)
			}
			return nil, 0, ErrCorrupt
		}
		 := binary.LittleEndian.Uint16([:])
		 += 2
		if  == 0 {
			if  {
				fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", , len()-)
			}
			return nil, 0, ErrCorrupt
		}
		if int() >  {
			if  {
				fmt.Printf("error: offset (%d)> uncompressed (%d)\n", , )
			}
			return nil, 0, ErrCorrupt
		}

		if  == +15 {
			for {
				if  >= len() {
					if  {
						fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", , len())
					}
					return nil, 0, ErrCorrupt
				}
				 := []
				++
				 += int()
				if  != 255 {
					if  >= len() {
						if  {
							fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", , len())
						}
						return nil, 0, ErrCorrupt
					}
					break
				}
			}
		}
		if  {
			fmt.Printf("emit copy, length: %d, offset: %d\n", , )
		}
		 := 
		// d += emitCopyNoRepeat(dst[d:], int(offset), ml)
		for  > 0 {
			if  >=  {
				return nil, 0, ErrDstTooSmall
			}

			// Offset no more than 2 bytes.
			if  > 64 {
				// Emit a length 64 copy, encoded as 3 bytes.
				[+2] = uint8( >> 8)
				[+1] = uint8()
				[+0] = 63<<2 | tagCopy2
				 -= 64
				 += 3
				continue
			}
			if  >= 12 ||  >= 2048 ||  < 4 {
				// Emit the remaining copy, encoded as 3 bytes.
				[+2] = uint8( >> 8)
				[+1] = uint8()
				[+0] = uint8(-1)<<2 | tagCopy2
				 += 3
				break
			}
			// Emit the remaining copy, encoded as 2 bytes.
			[+1] = uint8()
			[+0] = uint8(>>8)<<5 | uint8(-4)<<2 | tagCopy1
			 += 2
			break
		}
		 += 
		if  >  {
			return nil, 0, ErrDstTooSmall
		}
	}

	return [:], , nil
}

// emitRepeat writes a repeat chunk and returns the number of bytes written.
// Length must be at least 4 and < 1<<24
func emitRepeat16( []byte,  uint16,  int) int {
	// Repeat offset, make length cheaper
	 -= 4
	if  <= 4 {
		[0] = uint8()<<2 | tagCopy1
		[1] = 0
		return 2
	}
	if  < 8 &&  < 2048 {
		// Encode WITH offset
		[1] = uint8()
		[0] = uint8(>>8)<<5 | uint8()<<2 | tagCopy1
		return 2
	}
	if  < (1<<8)+4 {
		 -= 4
		[2] = uint8()
		[1] = 0
		[0] = 5<<2 | tagCopy1
		return 3
	}
	if  < (1<<16)+(1<<8) {
		 -= 1 << 8
		[3] = uint8( >> 8)
		[2] = uint8( >> 0)
		[1] = 0
		[0] = 6<<2 | tagCopy1
		return 4
	}
	const  = (1 << 24) - 1
	 -= 1 << 16
	 := 0
	if  >  {
		 =  -  + 4
		 =  - 4
	}
	[4] = uint8( >> 16)
	[3] = uint8( >> 8)
	[2] = uint8( >> 0)
	[1] = 0
	[0] = 7<<2 | tagCopy1
	if  > 0 {
		return 5 + ([5:], , )
	}
	return 5
}

// emitCopy writes a copy chunk and returns the number of bytes written.
//
// It assumes that:
//
//	dst is long enough to hold the encoded bytes
//	1 <= offset && offset <= math.MaxUint16
//	4 <= length && length <= math.MaxUint32
func emitCopy16( []byte,  uint16,  int) int {
	// Offset no more than 2 bytes.
	if  > 64 {
		 := 3
		if  < 2048 {
			// emit 8 bytes as tagCopy1, rest as repeats.
			[1] = uint8()
			[0] = uint8(>>8)<<5 | uint8(8-4)<<2 | tagCopy1
			 -= 8
			 = 2
		} else {
			// Emit a length 60 copy, encoded as 3 bytes.
			// Emit remaining as repeat value (minimum 4 bytes).
			[2] = uint8( >> 8)
			[1] = uint8()
			[0] = 59<<2 | tagCopy2
			 -= 60
		}
		// Emit remaining as repeats, at least 4 bytes remain.
		return  + emitRepeat16([:], , )
	}
	if  >= 12 ||  >= 2048 {
		// Emit the remaining copy, encoded as 3 bytes.
		[2] = uint8( >> 8)
		[1] = uint8()
		[0] = uint8(-1)<<2 | tagCopy2
		return 3
	}
	// Emit the remaining copy, encoded as 2 bytes.
	[1] = uint8()
	[0] = uint8(>>8)<<5 | uint8(-4)<<2 | tagCopy1
	return 2
}

// emitLiteral writes a literal chunk and returns the number of bytes written.
//
// It assumes that:
//
//	dst is long enough to hold the encoded bytes
//	0 <= len(lit) && len(lit) <= math.MaxUint32
func emitLiteralGo(,  []byte) int {
	if len() == 0 {
		return 0
	}
	,  := 0, uint(len()-1)
	switch {
	case  < 60:
		[0] = uint8()<<2 | tagLiteral
		 = 1
	case  < 1<<8:
		[1] = uint8()
		[0] = 60<<2 | tagLiteral
		 = 2
	case  < 1<<16:
		[2] = uint8( >> 8)
		[1] = uint8()
		[0] = 61<<2 | tagLiteral
		 = 3
	case  < 1<<24:
		[3] = uint8( >> 16)
		[2] = uint8( >> 8)
		[1] = uint8()
		[0] = 62<<2 | tagLiteral
		 = 4
	default:
		[4] = uint8( >> 24)
		[3] = uint8( >> 16)
		[2] = uint8( >> 8)
		[1] = uint8()
		[0] = 63<<2 | tagLiteral
		 = 5
	}
	return  + copy([:], )
}