//go:build !js
// +build !js

package websocket

import (
	
	
	
	
	
	
	
	
	

	

	
	
)

// Writer returns a writer bounded by the context that will write
// a WebSocket message of type dataType to the connection.
//
// You must close the writer once you have written the entire message.
//
// Only one writer can be open at a time, multiple calls will block until the previous writer
// is closed.
func ( *Conn) ( context.Context,  MessageType) (io.WriteCloser, error) {
	,  := .writer(, )
	if  != nil {
		return nil, fmt.Errorf("failed to get writer: %w", )
	}
	return , nil
}

// Write writes a message to the connection.
//
// See the Writer method if you want to stream a message.
//
// If compression is disabled or the compression threshold is not met, then it
// will write the message in a single frame.
func ( *Conn) ( context.Context,  MessageType,  []byte) error {
	,  := .write(, , )
	if  != nil {
		return fmt.Errorf("failed to write msg: %w", )
	}
	return nil
}

type msgWriter struct {
	c *Conn

	mu      *mu
	writeMu *mu
	closed  bool

	ctx    context.Context
	opcode opcode
	flate  bool

	trimWriter  *trimLastFourBytesWriter
	flateWriter *flate.Writer
}

func ( *Conn) *msgWriter {
	 := &msgWriter{
		c:       ,
		mu:      newMu(),
		writeMu: newMu(),
	}
	return 
}

func ( *msgWriter) () {
	if .trimWriter == nil {
		.trimWriter = &trimLastFourBytesWriter{
			w: util.WriterFunc(.write),
		}
	}

	if .flateWriter == nil {
		.flateWriter = getFlateWriter(.trimWriter)
	}
	.flate = true
}

func ( *msgWriter) () bool {
	if .c.client {
		return !.c.copts.clientNoContextTakeover
	}
	return !.c.copts.serverNoContextTakeover
}

func ( *Conn) ( context.Context,  MessageType) (io.WriteCloser, error) {
	 := .msgWriter.reset(, )
	if  != nil {
		return nil, 
	}
	return .msgWriter, nil
}

func ( *Conn) ( context.Context,  MessageType,  []byte) (int, error) {
	,  := .writer(, )
	if  != nil {
		return 0, 
	}

	if !.flate() {
		defer .msgWriter.mu.unlock()
		return .writeFrame(, true, false, .msgWriter.opcode, )
	}

	,  := .Write()
	if  != nil {
		return , 
	}

	 = .Close()
	return , 
}

func ( *msgWriter) ( context.Context,  MessageType) error {
	 := .mu.lock()
	if  != nil {
		return 
	}

	.ctx = 
	.opcode = opcode()
	.flate = false
	.closed = false

	.trimWriter.reset()

	return nil
}

func ( *msgWriter) () {
	if .flateWriter != nil {
		putFlateWriter(.flateWriter)
		.flateWriter = nil
	}
}

// Write writes the given bytes to the WebSocket connection.
func ( *msgWriter) ( []byte) ( int,  error) {
	 = .writeMu.lock(.ctx)
	if  != nil {
		return 0, fmt.Errorf("failed to write: %w", )
	}
	defer .writeMu.unlock()

	if .closed {
		return 0, errors.New("cannot use closed writer")
	}

	defer func() {
		if  != nil {
			 = fmt.Errorf("failed to write: %w", )
			.c.close()
		}
	}()

	if .c.flate() {
		// Only enables flate if the length crosses the
		// threshold on the first frame
		if .opcode != opContinuation && len() >= .c.flateThreshold {
			.ensureFlate()
		}
	}

	if .flate {
		return .flateWriter.Write()
	}

	return .write()
}

func ( *msgWriter) ( []byte) (int, error) {
	,  := .c.writeFrame(.ctx, false, .flate, .opcode, )
	if  != nil {
		return , fmt.Errorf("failed to write data frame: %w", )
	}
	.opcode = opContinuation
	return , nil
}

// Close flushes the frame to the connection.
func ( *msgWriter) () ( error) {
	defer errd.Wrap(&, "failed to close writer")

	 = .writeMu.lock(.ctx)
	if  != nil {
		return 
	}
	defer .writeMu.unlock()

	if .closed {
		return errors.New("writer already closed")
	}
	.closed = true

	if .flate {
		 = .flateWriter.Flush()
		if  != nil {
			return fmt.Errorf("failed to flush flate: %w", )
		}
	}

	_,  = .c.writeFrame(.ctx, true, .flate, .opcode, nil)
	if  != nil {
		return fmt.Errorf("failed to write fin frame: %w", )
	}

	if .flate && !.flateContextTakeover() {
		.putFlateWriter()
	}
	.mu.unlock()
	return nil
}

func ( *msgWriter) () {
	if .c.client {
		.c.writeFrameMu.forceLock()
		putBufioWriter(.c.bw)
	}

	.writeMu.forceLock()
	.putFlateWriter()
}

func ( *Conn) ( context.Context,  opcode,  []byte) error {
	,  := context.WithTimeout(, time.Second*5)
	defer ()

	,  := .writeFrame(, true, false, , )
	if  != nil {
		return fmt.Errorf("failed to write control frame %v: %w", , )
	}
	return nil
}

// frame handles all writes to the connection.
func ( *Conn) ( context.Context,  bool,  bool,  opcode,  []byte) ( int,  error) {
	 = .writeFrameMu.lock()
	if  != nil {
		return 0, 
	}

	// If the state says a close has already been written, we wait until
	// the connection is closed and return that error.
	//
	// However, if the frame being written is a close, that means its the close from
	// the state being set so we let it go through.
	.closeMu.Lock()
	 := .wroteClose
	.closeMu.Unlock()
	if  &&  != opClose {
		.writeFrameMu.unlock()
		select {
		case <-.Done():
			return 0, .Err()
		case <-.closed:
			return 0, net.ErrClosed
		}
	}
	defer .writeFrameMu.unlock()

	select {
	case <-.closed:
		return 0, net.ErrClosed
	case .writeTimeout <- :
	}

	defer func() {
		if  != nil {
			select {
			case <-.closed:
				 = net.ErrClosed
			case <-.Done():
				 = .Err()
			default:
			}
			.close()
			 = fmt.Errorf("failed to write frame: %w", )
		}
	}()

	.writeHeader.fin = 
	.writeHeader.opcode = 
	.writeHeader.payloadLength = int64(len())

	if .client {
		.writeHeader.masked = true
		_,  = io.ReadFull(rand.Reader, .writeHeaderBuf[:4])
		if  != nil {
			return 0, fmt.Errorf("failed to generate masking key: %w", )
		}
		.writeHeader.maskKey = binary.LittleEndian.Uint32(.writeHeaderBuf[:])
	}

	.writeHeader.rsv1 = false
	if  && ( == opText ||  == opBinary) {
		.writeHeader.rsv1 = true
	}

	 = writeFrameHeader(.writeHeader, .bw, .writeHeaderBuf[:])
	if  != nil {
		return 0, 
	}

	,  := .writeFramePayload()
	if  != nil {
		return , 
	}

	if .writeHeader.fin {
		 = .bw.Flush()
		if  != nil {
			return , fmt.Errorf("failed to flush: %w", )
		}
	}

	select {
	case <-.closed:
		if  == opClose {
			return , nil
		}
		return , net.ErrClosed
	case .writeTimeout <- context.Background():
	}

	return , nil
}

func ( *Conn) ( []byte) ( int,  error) {
	defer errd.Wrap(&, "failed to write frame payload")

	if !.writeHeader.masked {
		return .bw.Write()
	}

	 := .writeHeader.maskKey
	for len() > 0 {
		// If the buffer is full, we need to flush.
		if .bw.Available() == 0 {
			 = .bw.Flush()
			if  != nil {
				return , 
			}
		}

		// Start of next write in the buffer.
		 := .bw.Buffered()

		 := len()
		if  > .bw.Available() {
			 = .bw.Available()
		}

		,  := .bw.Write([:])
		if  != nil {
			return , 
		}

		 = mask(, .writeBuf[:.bw.Buffered()])

		 = [:]
		 += 
	}

	return , nil
}

// extractBufioWriterBuf grabs the []byte backing a *bufio.Writer
// and returns it.
func ( *bufio.Writer,  io.Writer) []byte {
	var  []byte
	.Reset(util.WriterFunc(func( []byte) (int, error) {
		 = [:cap()]
		return len(), nil
	}))

	.WriteByte(0)
	.Flush()

	.Reset()

	return 
}

func ( *Conn) ( StatusCode,  error) {
	.setCloseErr()
	.writeClose(, .Error())
	.close(nil)
}