// Copyright 2020+ Klaus Post. All rights reserved.
// License information can be found in the LICENSE file.

package zstd

import (
	
	
	
)

// HeaderMaxSize is the maximum size of a Frame and Block Header.
// If less is sent to Header.Decode it *may* still contain enough information.
const HeaderMaxSize = 14 + 3

// Header contains information about the first frame and block within that.
type Header struct {
	// SingleSegment specifies whether the data is to be decompressed into a
	// single contiguous memory segment.
	// It implies that WindowSize is invalid and that FrameContentSize is valid.
	SingleSegment bool

	// WindowSize is the window of data to keep while decoding.
	// Will only be set if SingleSegment is false.
	WindowSize uint64

	// Dictionary ID.
	// If 0, no dictionary.
	DictionaryID uint32

	// HasFCS specifies whether FrameContentSize has a valid value.
	HasFCS bool

	// FrameContentSize is the expected uncompressed size of the entire frame.
	FrameContentSize uint64

	// Skippable will be true if the frame is meant to be skipped.
	// This implies that FirstBlock.OK is false.
	Skippable bool

	// SkippableID is the user-specific ID for the skippable frame.
	// Valid values are between 0 to 15, inclusive.
	SkippableID int

	// SkippableSize is the length of the user data to skip following
	// the header.
	SkippableSize uint32

	// HeaderSize is the raw size of the frame header.
	//
	// For normal frames, it includes the size of the magic number and
	// the size of the header (per section 3.1.1.1).
	// It does not include the size for any data blocks (section 3.1.1.2) nor
	// the size for the trailing content checksum.
	//
	// For skippable frames, this counts the size of the magic number
	// along with the size of the size field of the payload.
	// It does not include the size of the skippable payload itself.
	// The total frame size is the HeaderSize plus the SkippableSize.
	HeaderSize int

	// First block information.
	FirstBlock struct {
		// OK will be set if first block could be decoded.
		OK bool

		// Is this the last block of a frame?
		Last bool

		// Is the data compressed?
		// If true CompressedSize will be populated.
		// Unfortunately DecompressedSize cannot be determined
		// without decoding the blocks.
		Compressed bool

		// DecompressedSize is the expected decompressed size of the block.
		// Will be 0 if it cannot be determined.
		DecompressedSize int

		// CompressedSize of the data in the block.
		// Does not include the block header.
		// Will be equal to DecompressedSize if not Compressed.
		CompressedSize int
	}

	// If set there is a checksum present for the block content.
	// The checksum field at the end is always 4 bytes long.
	HasCheckSum bool
}

// Decode the header from the beginning of the stream.
// This will decode the frame header and the first block header if enough bytes are provided.
// It is recommended to provide at least HeaderMaxSize bytes.
// If the frame header cannot be read an error will be returned.
// If there isn't enough input, io.ErrUnexpectedEOF is returned.
// The FirstBlock.OK will indicate if enough information was available to decode the first block header.
func ( *Header) ( []byte) error {
	,  := .DecodeAndStrip()
	return 
}

// DecodeAndStrip will decode the header from the beginning of the stream
// and on success return the remaining bytes.
// This will decode the frame header and the first block header if enough bytes are provided.
// It is recommended to provide at least HeaderMaxSize bytes.
// If the frame header cannot be read an error will be returned.
// If there isn't enough input, io.ErrUnexpectedEOF is returned.
// The FirstBlock.OK will indicate if enough information was available to decode the first block header.
func ( *Header) ( []byte) ( []byte,  error) {
	* = Header{}
	if len() < 4 {
		return nil, io.ErrUnexpectedEOF
	}
	.HeaderSize += 4
	,  := [:4], [4:]
	if string() != frameMagic {
		if string([1:4]) != skippableFrameMagic || [0]&0xf0 != 0x50 {
			return nil, ErrMagicMismatch
		}
		if len() < 4 {
			return nil, io.ErrUnexpectedEOF
		}
		.HeaderSize += 4
		.Skippable = true
		.SkippableID = int([0] & 0xf)
		.SkippableSize = binary.LittleEndian.Uint32()
		return [4:], nil
	}

	// Read Window_Descriptor
	// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#window_descriptor
	if len() < 1 {
		return nil, io.ErrUnexpectedEOF
	}
	,  := [0], [1:]
	.HeaderSize++
	.SingleSegment = &(1<<5) != 0
	.HasCheckSum = &(1<<2) != 0
	if &(1<<3) != 0 {
		return nil, errors.New("reserved bit set on frame header")
	}

	if !.SingleSegment {
		if len() < 1 {
			return nil, io.ErrUnexpectedEOF
		}
		var  byte
		,  = [0], [1:]
		.HeaderSize++
		 := 10 + ( >> 3)
		 := uint64(1) << 
		 := ( / 8) * uint64(&0x7)
		.WindowSize =  + 
	}

	// Read Dictionary_ID
	// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary_id
	if  :=  & 3;  != 0 {
		if  == 3 {
			 = 4
		}
		if len() < int() {
			return nil, io.ErrUnexpectedEOF
		}
		,  = [:], [:]
		.HeaderSize += int()
		switch len() {
		case 1:
			.DictionaryID = uint32([0])
		case 2:
			.DictionaryID = uint32([0]) | (uint32([1]) << 8)
		case 4:
			.DictionaryID = uint32([0]) | (uint32([1]) << 8) | (uint32([2]) << 16) | (uint32([3]) << 24)
		}
	}

	// Read Frame_Content_Size
	// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#frame_content_size
	var  int
	 :=  >> 6
	switch  {
	case 0:
		if .SingleSegment {
			 = 1
		}
	default:
		 = 1 << 
	}

	if  > 0 {
		.HasFCS = true
		if len() <  {
			return nil, io.ErrUnexpectedEOF
		}
		,  = [:], [:]
		.HeaderSize += int()
		switch len() {
		case 1:
			.FrameContentSize = uint64([0])
		case 2:
			// When FCS_Field_Size is 2, the offset of 256 is added.
			.FrameContentSize = uint64([0]) | (uint64([1]) << 8) + 256
		case 4:
			.FrameContentSize = uint64([0]) | (uint64([1]) << 8) | (uint64([2]) << 16) | (uint64([3]) << 24)
		case 8:
			 := uint32([0]) | (uint32([1]) << 8) | (uint32([2]) << 16) | (uint32([3]) << 24)
			 := uint32([4]) | (uint32([5]) << 8) | (uint32([6]) << 16) | (uint32([7]) << 24)
			.FrameContentSize = uint64() | (uint64() << 32)
		}
	}

	// Frame Header done, we will not fail from now on.
	if len() < 3 {
		return , nil
	}
	 := [:3]
	 := uint32([0]) | (uint32([1]) << 8) | (uint32([2]) << 16)
	.FirstBlock.Last = &1 != 0
	 := blockType(( >> 1) & 3)
	// find size.
	 := int( >> 3)
	switch  {
	case blockTypeReserved:
		return , nil
	case blockTypeRLE:
		.FirstBlock.Compressed = true
		.FirstBlock.DecompressedSize = 
		.FirstBlock.CompressedSize = 1
	case blockTypeCompressed:
		.FirstBlock.Compressed = true
		.FirstBlock.CompressedSize = 
	case blockTypeRaw:
		.FirstBlock.DecompressedSize = 
		.FirstBlock.CompressedSize = 
	default:
		panic("Invalid block type")
	}

	.FirstBlock.OK = true
	return , nil
}

// AppendTo will append the encoded header to the dst slice.
// There is no error checking performed on the header values.
func ( *Header) ( []byte) ([]byte, error) {
	if .Skippable {
		 := [4]byte{0x50, 0x2a, 0x4d, 0x18}
		[0] |= byte(.SkippableID & 0xf)
		 = append(, [:]...)
		 := .SkippableSize
		return append(, uint8(), uint8(>>8), uint8(>>16), uint8(>>24)), nil
	}
	 := frameHeader{
		ContentSize:   .FrameContentSize,
		WindowSize:    uint32(.WindowSize),
		SingleSegment: .SingleSegment,
		Checksum:      .HasCheckSum,
		DictID:        .DictionaryID,
	}
	return .appendTo(), nil
}