package lz4

import (
	

	
	
	
)

var writerStates = []aState{
	noState:     newState,
	newState:    writeState,
	writeState:  closedState,
	closedState: newState,
	errorState:  newState,
}

// NewWriter returns a new LZ4 frame encoder.
func ( io.Writer) *Writer {
	 := &Writer{frame: lz4stream.NewFrame()}
	.state.init(writerStates)
	_ = .Apply(DefaultBlockSizeOption, DefaultChecksumOption, DefaultConcurrency, defaultOnBlockDone)
	.Reset()
	return 
}

// Writer allows writing an LZ4 stream.
type Writer struct {
	state   _State
	src     io.Writer                 // destination writer
	level   lz4block.CompressionLevel // how hard to try
	num     int                       // concurrency level
	frame   *lz4stream.Frame          // frame being built
	data    []byte                    // pending data
	idx     int                       // size of pending data
	handler func(int)
	legacy  bool
}

func (*Writer) () {}

func ( *Writer) ( ...Option) ( error) {
	defer .state.check(&)
	switch .state.state {
	case newState:
	case errorState:
		return .state.err
	default:
		return lz4errors.ErrOptionClosedOrError
	}
	.Reset(.src)
	for ,  := range  {
		if  = ();  != nil {
			return
		}
	}
	return
}

func ( *Writer) () bool {
	return .num == 1
}

// init sets up the Writer when in newState. It does not change the Writer state.
func ( *Writer) () error {
	.frame.InitW(.src, .num, .legacy)
	 := .frame.Descriptor.Flags.BlockSizeIndex()
	.data = .Get()
	.idx = 0
	return .frame.Descriptor.Write(.frame, .src)
}

func ( *Writer) ( []byte) ( int,  error) {
	defer .state.check(&)
	switch .state.state {
	case writeState:
	case closedState, errorState:
		return 0, .state.err
	case newState:
		if  = .init(); .state.next() {
			return
		}
	default:
		return 0, .state.fail()
	}

	 := len(.data)
	for len() > 0 {
		if .isNotConcurrent() && .idx == 0 && len() >=  {
			// Avoid a copy as there is enough data for a block.
			if  = .write([:], false);  != nil {
				return
			}
			 += 
			 = [:]
			continue
		}
		// Accumulate the data to be compressed.
		 := copy(.data[.idx:], )
		 += 
		.idx += 
		 = [:]

		if .idx < len(.data) {
			// Buffer not filled.
			return
		}

		// Buffer full.
		if  = .write(.data, true);  != nil {
			return
		}
		if !.isNotConcurrent() {
			 := .frame.Descriptor.Flags.BlockSizeIndex()
			.data = .Get()
		}
		.idx = 0
	}
	return
}

func ( *Writer) ( []byte,  bool) error {
	if .isNotConcurrent() {
		 := .frame.Blocks.Block
		 := .Compress(.frame, , .level).Write(.frame, .src)
		.handler(len(.Data))
		return 
	}
	 := make(chan *lz4stream.FrameDataBlock)
	.frame.Blocks.Blocks <- 
	go func( chan *lz4stream.FrameDataBlock,  []byte,  bool) {
		 := lz4stream.NewFrameDataBlock(.frame)
		 <- .Compress(.frame, , .level)
		<-
		.handler(len(.Data))
		.Close(.frame)
		if  {
			// safe to put it back as the last usage of it was FrameDataBlock.Write() called before c is closed
			lz4block.Put()
		}
	}(, , )

	return nil
}

// Flush any buffered data to the underlying writer immediately.
func ( *Writer) () ( error) {
	switch .state.state {
	case writeState:
	case errorState:
		return .state.err
	case newState:
		if  = .init(); .state.next() {
			return
		}
	default:
		return nil
	}

	if .idx > 0 {
		// Flush pending data, disable w.data freeing as it is done later on.
		if  = .write(.data[:.idx], false);  != nil {
			return 
		}
		.idx = 0
	}
	return nil
}

// Close closes the Writer, flushing any unwritten data to the underlying writer
// without closing it.
func ( *Writer) () error {
	if  := .Flush();  != nil {
		return 
	}
	 := .frame.CloseW(.src, .num)
	// It is now safe to free the buffer.
	if .data != nil {
		lz4block.Put(.data)
		.data = nil
	}
	return 
}

// Reset clears the state of the Writer w such that it is equivalent to its
// initial state from NewWriter, but instead writing to writer.
// Reset keeps the previous options unless overwritten by the supplied ones.
// No access to writer is performed.
//
// w.Close must be called before Reset or pending data may be dropped.
func ( *Writer) ( io.Writer) {
	.frame.Reset(.num)
	.state.reset()
	.src = 
}

// ReadFrom efficiently reads from r and compressed into the Writer destination.
func ( *Writer) ( io.Reader) ( int64,  error) {
	switch .state.state {
	case closedState, errorState:
		return 0, .state.err
	case newState:
		if  = .init(); .state.next() {
			return
		}
	default:
		return 0, .state.fail()
	}
	defer .state.check(&)

	 := .frame.Descriptor.Flags.BlockSizeIndex()
	var  bool
	var  int
	 := .Get()
	if .isNotConcurrent() {
		// Keep the same buffer for the whole process.
		defer lz4block.Put()
	}
	for ! {
		,  = io.ReadFull(, )
		switch  {
		case nil:
		case io.EOF, io.ErrUnexpectedEOF: // read may be partial
			 = true
		default:
			return
		}
		 += int64()
		 = .write([:], true)
		if  != nil {
			return
		}
		.handler()
		if ! && !.isNotConcurrent() {
			// The buffer will be returned automatically by go routines (safe=true)
			// so get a new one fo the next round.
			 = .Get()
		}
	}
	return
}