package lz4

import (
	
	

	
	
	
)

type crState int

const (
	crStateInitial crState = iota
	crStateReading 
	crStateFlushing
	crStateDone
)

type CompressingReader struct {
	state crState
	src io.ReadCloser // source reader
	level lz4block.CompressionLevel // how hard to try
	frame *lz4stream.Frame // frame being built
	in []byte
	out ovWriter
	handler func(int)
}

// NewCompressingReader creates a reader which reads compressed data from
// raw stream. This makes it a logical opposite of a normal lz4.Reader.
// We require an io.ReadCloser as an underlying source for compatibility
// with Go's http.Request.
func ( io.ReadCloser) *CompressingReader {
	 := &CompressingReader {
		frame: lz4stream.NewFrame(),
	}

	_ = .Apply(DefaultBlockSizeOption, DefaultChecksumOption, defaultOnBlockDone)
	.Reset()

	return 
}

// Source exposes the underlying source stream for introspection and control.
func ( *CompressingReader) () io.ReadCloser {
	return .src
}

// Close simply invokes the underlying stream Close method. This method is
// provided for the benefit of Go http client/server, which relies on Close
// for goroutine termination.
func ( *CompressingReader) () error {
	return .src.Close()
}

// Apply applies useful options to the lz4 encoder.
func ( *CompressingReader) ( ...Option) ( error) {
	if .state != crStateInitial {
		return lz4errors.ErrOptionClosedOrError
	}

	.Reset(.src)

	for ,  := range  {
		if  = ();  != nil {
			return
		}
	}
	return
}

func (*CompressingReader) () {}

func ( *CompressingReader) () error {
	.frame.InitW(&.out, 1, false)
	 := .frame.Descriptor.Flags.BlockSizeIndex()
	.in = .Get()
	return .frame.Descriptor.Write(.frame, &.out)
}

// Read allows reading of lz4 compressed data
func ( *CompressingReader) ( []byte) ( int,  error) {
	defer func() {
		if  != nil {
			.state = crStateDone
		}
	}()

	if !.out.reset() {
		return len(), nil
	}

	switch .state {
	case crStateInitial:
		 = .init()
		if  != nil {
			return
		}
		.state = crStateReading
	case crStateDone:
		return 0, errors.New("This reader is done")
	case crStateFlushing:
		if .out.dataPos > 0 {
			 = .out.dataPos
			.out.data = nil
			.out.dataPos = 0
			return
		} else {
			.state = crStateDone
			return 0, io.EOF
		}
	}

	for .state == crStateReading {
		 := .frame.Blocks.Block

		var  int
		,  = io.ReadFull(.src, .in)
		switch  {
		case nil:
			 = .Compress(
				.frame, .in[ : ], .level,
			).Write(.frame, &.out)
			.handler(len(.Data))
			if  != nil {
				return
			}

			if .out.dataPos == len(.out.data) {
				 = .out.dataPos
				.out.dataPos = 0
				.out.data = nil
				return
			}
		case io.EOF, io.ErrUnexpectedEOF: // read may be partial
			if  > 0 {
				 = .Compress(
					.frame, .in[ : ], .level,
				).Write(.frame, &.out)
				.handler(len(.Data))
				if  != nil {
					return
				}
			}

			 = .frame.CloseW(&.out, 1)
			if  != nil {
				return
			}
			.state = crStateFlushing

			 = .out.dataPos
			.out.dataPos = 0
			.out.data = nil
			return
		default:
			return
		}
	}

	 = lz4errors.ErrInternalUnhandledState
	return
}

// Reset makes the stream usable again; mostly handy to reuse lz4 encoder
// instances.
func ( *CompressingReader) ( io.ReadCloser) {
	.frame.Reset(1)
	.state = crStateInitial
	.src = 
	.out.clear()
}

type ovWriter struct {
	data []byte
	ov []byte
	dataPos int
	ovPos int
}

func ( *ovWriter) ( []byte) ( int,  error) {
	 := copy(.data[.dataPos : ], )
	.dataPos += 

	if  < len() {
		.ov = append(.ov, [ : ]...)
	}

	return len(), nil
}

func ( *ovWriter) ( []byte) bool {
	 := len(.ov) - .ovPos

	if  >= len() {
		.ovPos += copy(, .ov[.ovPos : ])
		return false
	}

	if  > 0 {
		copy(, .ov[.ovPos : ])
		.ov = .ov[ : 0]
		.ovPos = 0
		.dataPos = 
	} else if .ovPos > 0 {
		.ov = .ov[ : 0]
		.ovPos = 0
		.dataPos = 0
	}

	.data = 
	return true
}

func ( *ovWriter) () {
	.data = nil
	.dataPos = 0
	.ov = .ov[ : 0]
	.ovPos = 0
}