// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package zstd

import (
	"io"
)

// debug can be set in the source to print debug info using println.
const debug = false

// compressedBlock decompresses a compressed block, storing the decompressed
// data in r.buffer. The blockSize argument is the compressed size.
// RFC 3.1.1.3.
func (r *Reader) compressedBlock(blockSize int) error {
	if len(r.compressedBuf) >= blockSize {
		r.compressedBuf = r.compressedBuf[:blockSize]
	} else {
		// We know that blockSize <= 128K,
		// so this won't allocate an enormous amount.
		need := blockSize - len(r.compressedBuf)
		r.compressedBuf = append(r.compressedBuf, make([]byte, need)...)
	}

	if _, err := io.ReadFull(r.r, r.compressedBuf); err != nil {
		return r.wrapNonEOFError(0, err)
	}

	data := block(r.compressedBuf)
	off := 0
	r.buffer = r.buffer[:0]

	litoff, litbuf, err := r.readLiterals(data, off, r.literals[:0])
	if err != nil {
		return err
	}
	r.literals = litbuf

	off = litoff

	seqCount, off, err := r.initSeqs(data, off)
	if err != nil {
		return err
	}

	if seqCount == 0 {
		// No sequences, just literals.
		if off < len(data) {
			return r.makeError(off, "extraneous data after no sequences")
		}

		r.buffer = append(r.buffer, litbuf...)

		return nil
	}

	return r.execSeqs(data, off, litbuf, seqCount)
}

// seqCode is the kind of sequence codes we have to handle.
type seqCode int

const (
	seqLiteral seqCode = iota
	seqOffset
	seqMatch
)

// seqCodeInfoData is the information needed to set up seqTables and
// seqTableBits for a particular kind of sequence code.
type seqCodeInfoData struct {
	predefTable     []fseBaselineEntry // predefined FSE
	predefTableBits int                // number of bits in predefTable
	maxSym          int                // max symbol value in FSE
	maxBits         int                // max bits for FSE

	// toBaseline converts from an FSE table to an FSE baseline table.
	toBaseline func(*Reader, int, []fseEntry, []fseBaselineEntry) error
}

// seqCodeInfo is the seqCodeInfoData for each kind of sequence code.
var seqCodeInfo = [3]seqCodeInfoData{
	seqLiteral: {
		predefTable:     predefinedLiteralTable[:],
		predefTableBits: 6,
		maxSym:          35,
		maxBits:         9,
		toBaseline:      (*Reader).makeLiteralBaselineFSE,
	},
	seqOffset: {
		predefTable:     predefinedOffsetTable[:],
		predefTableBits: 5,
		maxSym:          31,
		maxBits:         8,
		toBaseline:      (*Reader).makeOffsetBaselineFSE,
	},
	seqMatch: {
		predefTable:     predefinedMatchTable[:],
		predefTableBits: 6,
		maxSym:          52,
		maxBits:         9,
		toBaseline:      (*Reader).makeMatchBaselineFSE,
	},
}

// initSeqs reads the Sequences_Section_Header and sets up the FSE
// tables used to read the sequence codes. It returns the number of
// sequences and the new offset. RFC 3.1.1.3.2.1.
func (r *Reader) initSeqs(data block, off int) (int, int, error) {
	if off >= len(data) {
		return 0, 0, r.makeEOFError(off)
	}

	seqHdr := data[off]
	off++
	if seqHdr == 0 {
		return 0, off, nil
	}

	var seqCount int
	if seqHdr < 128 {
		seqCount = int(seqHdr)
	} else if seqHdr < 255 {
		if off >= len(data) {
			return 0, 0, r.makeEOFError(off)
		}
		seqCount = ((int(seqHdr) - 128) << 8) + int(data[off])
		off++
	} else {
		if off+1 >= len(data) {
			return 0, 0, r.makeEOFError(off)
		}
		seqCount = int(data[off]) + (int(data[off+1]) << 8) + 0x7f00
		off += 2
	}

	// Read the Symbol_Compression_Modes byte.

	if off >= len(data) {
		return 0, 0, r.makeEOFError(off)
	}
	symMode := data[off]
	if symMode&3 != 0 {
		return 0, 0, r.makeError(off, "invalid symbol compression mode")
	}
	off++

	// Set up the FSE tables used to decode the sequence codes.

	var err error
	off, err = r.setSeqTable(data, off, seqLiteral, (symMode>>6)&3)
	if err != nil {
		return 0, 0, err
	}

	off, err = r.setSeqTable(data, off, seqOffset, (symMode>>4)&3)
	if err != nil {
		return 0, 0, err
	}

	off, err = r.setSeqTable(data, off, seqMatch, (symMode>>2)&3)
	if err != nil {
		return 0, 0, err
	}

	return seqCount, off, nil
}

// setSeqTable uses the Compression_Mode in mode to set up r.seqTables and
// r.seqTableBits for kind. We store these in the Reader because one of
// the modes simply reuses the value from the last block in the frame.
func (r *Reader) setSeqTable(data block, off int, kind seqCode, mode byte) (int, error) {
	info := &seqCodeInfo[kind]
	switch mode {
	case 0:
		// Predefined_Mode
		r.seqTables[kind] = info.predefTable
		r.seqTableBits[kind] = uint8(info.predefTableBits)
		return off, nil

	case 1:
		// RLE_Mode
		if off >= len(data) {
			return 0, r.makeEOFError(off)
		}
		rle := data[off]
		off++

		// Build a simple baseline table that always returns rle.

		entry := []fseEntry{
			{
				sym:  rle,
				bits: 0,
				base: 0,
			},
		}
		if cap(r.seqTableBuffers[kind]) == 0 {
			r.seqTableBuffers[kind] = make([]fseBaselineEntry, 1<<info.maxBits)
		}
		r.seqTableBuffers[kind] = r.seqTableBuffers[kind][:1]
		if err := info.toBaseline(r, off, entry, r.seqTableBuffers[kind]); err != nil {
			return 0, err
		}

		r.seqTables[kind] = r.seqTableBuffers[kind]
		r.seqTableBits[kind] = 0
		return off, nil

	case 2:
		// FSE_Compressed_Mode
		if cap(r.fseScratch) < 1<<info.maxBits {
			r.fseScratch = make([]fseEntry, 1<<info.maxBits)
		}
		r.fseScratch = r.fseScratch[:1<<info.maxBits]

		tableBits, roff, err := r.readFSE(data, off, info.maxSym, info.maxBits, r.fseScratch)
		if err != nil {
			return 0, err
		}
		r.fseScratch = r.fseScratch[:1<<tableBits]

		if cap(r.seqTableBuffers[kind]) == 0 {
			r.seqTableBuffers[kind] = make([]fseBaselineEntry, 1<<info.maxBits)
		}
		r.seqTableBuffers[kind] = r.seqTableBuffers[kind][:1<<tableBits]

		if err := info.toBaseline(r, roff, r.fseScratch, r.seqTableBuffers[kind]); err != nil {
			return 0, err
		}

		r.seqTables[kind] = r.seqTableBuffers[kind]
		r.seqTableBits[kind] = uint8(tableBits)
		return roff, nil

	case 3:
		// Repeat_Mode
		if len(r.seqTables[kind]) == 0 {
			return 0, r.makeError(off, "missing repeat sequence FSE table")
		}
		return off, nil
	}
	panic("unreachable")
}

// execSeqs reads and executes the sequences. RFC 3.1.1.3.2.1.2.
func (r *Reader) execSeqs(data block, off int, litbuf []byte, seqCount int) error {
	// Set up the initial states for the sequence code readers.

	rbr, err := r.makeReverseBitReader(data, len(data)-1, off)
	if err != nil {
		return err
	}

	literalState, err := rbr.val(r.seqTableBits[seqLiteral])
	if err != nil {
		return err
	}

	offsetState, err := rbr.val(r.seqTableBits[seqOffset])
	if err != nil {
		return err
	}

	matchState, err := rbr.val(r.seqTableBits[seqMatch])
	if err != nil {
		return err
	}

	// Read and perform all the sequences. RFC 3.1.1.4.

	seq := 0
	for seq < seqCount {
		if len(r.buffer)+len(litbuf) > 128<<10 {
			return rbr.makeError("uncompressed size too big")
		}

		ptoffset := &r.seqTables[seqOffset][offsetState]
		ptmatch := &r.seqTables[seqMatch][matchState]
		ptliteral := &r.seqTables[seqLiteral][literalState]

		add, err := rbr.val(ptoffset.basebits)
		if err != nil {
			return err
		}
		offset := ptoffset.baseline + add

		add, err = rbr.val(ptmatch.basebits)
		if err != nil {
			return err
		}
		match := ptmatch.baseline + add

		add, err = rbr.val(ptliteral.basebits)
		if err != nil {
			return err
		}
		literal := ptliteral.baseline + add

		// Handle repeat offsets. RFC 3.1.1.5.
		// See the comment in makeOffsetBaselineFSE.
		if ptoffset.basebits > 1 {
			r.repeatedOffset3 = r.repeatedOffset2
			r.repeatedOffset2 = r.repeatedOffset1
			r.repeatedOffset1 = offset
		} else {
			if literal == 0 {
				offset++
			}
			switch offset {
			case 1:
				offset = r.repeatedOffset1
			case 2:
				offset = r.repeatedOffset2
				r.repeatedOffset2 = r.repeatedOffset1
				r.repeatedOffset1 = offset
			case 3:
				offset = r.repeatedOffset3
				r.repeatedOffset3 = r.repeatedOffset2
				r.repeatedOffset2 = r.repeatedOffset1
				r.repeatedOffset1 = offset
			case 4:
				offset = r.repeatedOffset1 - 1
				r.repeatedOffset3 = r.repeatedOffset2
				r.repeatedOffset2 = r.repeatedOffset1
				r.repeatedOffset1 = offset
			}
		}

		seq++
		if seq < seqCount {
			// Update the states.
			add, err = rbr.val(ptliteral.bits)
			if err != nil {
				return err
			}
			literalState = uint32(ptliteral.base) + add

			add, err = rbr.val(ptmatch.bits)
			if err != nil {
				return err
			}
			matchState = uint32(ptmatch.base) + add

			add, err = rbr.val(ptoffset.bits)
			if err != nil {
				return err
			}
			offsetState = uint32(ptoffset.base) + add
		}

		// The next sequence is now in literal, offset, match.

		if debug {
			println("literal", literal, "offset", offset, "match", match)
		}

		// Copy literal bytes from litbuf.
		if literal > uint32(len(litbuf)) {
			return rbr.makeError("literal byte overflow")
		}
		if literal > 0 {
			r.buffer = append(r.buffer, litbuf[:literal]...)
			litbuf = litbuf[literal:]
		}

		if match > 0 {
			if err := r.copyFromWindow(&rbr, offset, match); err != nil {
				return err
			}
		}
	}

	r.buffer = append(r.buffer, litbuf...)

	if rbr.cnt != 0 {
		return r.makeError(off, "extraneous data after sequences")
	}

	return nil
}

// Copy match bytes from the decoded output, or the window, at offset.
func (r *Reader) copyFromWindow(rbr *reverseBitReader, offset, match uint32) error {
	if offset == 0 {
		return rbr.makeError("invalid zero offset")
	}

	// Offset may point into the buffer or the window and
	// match may extend past the end of the initial buffer.
	// |--r.window--|--r.buffer--|
	//        |<-----offset------|
	//        |------match----------->|
	bufferOffset := uint32(0)
	lenBlock := uint32(len(r.buffer))
	if lenBlock < offset {
		lenWindow := r.window.len()
		copy := offset - lenBlock
		if copy > lenWindow {
			return rbr.makeError("offset past window")
		}
		windowOffset := lenWindow - copy
		if copy > match {
			copy = match
		}
		r.buffer = r.window.appendTo(r.buffer, windowOffset, windowOffset+copy)
		match -= copy
	} else {
		bufferOffset = lenBlock - offset
	}

	// We are being asked to copy data that we are adding to the
	// buffer in the same copy.
	for match > 0 {
		copy := uint32(len(r.buffer)) - bufferOffset
		if copy > match {
			copy = match
		}
		r.buffer = append(r.buffer, r.buffer[bufferOffset:bufferOffset+copy]...)
		match -= copy
	}
	return nil
}
