package pipe

import (
	"io"
	"net"

	"github.com/AdguardTeam/golibs/log"
	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsutil"
)

// wsConn represents a WebSocket connection that's been already initialized.
type wsConn struct {
	rwc        io.ReadWriteCloser
	remoteAddr net.Addr
	r          *wsutil.Reader
	w          *wsutil.Writer
}

// newWsConn creates a wrapper over the existing network connection that is
// able to send/read messages using WebSocket protocol.
func newWsConn(rwc io.ReadWriteCloser, remoteAddr net.Addr, state ws.State) (c *wsConn) {
	r := wsutil.NewReader(rwc, state)
	w := wsutil.NewWriter(rwc, state, ws.OpBinary)

	return &wsConn{
		rwc:        rwc,
		remoteAddr: remoteAddr,
		r:          r,
		w:          w,
	}
}

// type check
var _ io.ReadWriteCloser = (*wsConn)(nil)

// Read implements the io.ReadWriteCloser interface for *wsConn.
func (w *wsConn) Read(b []byte) (n int, err error) {
	n, err = w.r.Read(b)
	if err == wsutil.ErrNoFrameAdvance {
		log.Debug("Reading the next WebSocket frame from %v", w.remoteAddr)

		hdr, fErr := w.r.NextFrame()
		if fErr != nil {
			return 0, io.EOF
		}

		log.Debug(
			"Received WebSocket frame with opcode=%d len=%d fin=%v from %v",
			hdr.OpCode,
			hdr.Length,
			hdr.Fin,
			w.remoteAddr,
		)

		// Reading again after the frame has been read.
		n, err = w.r.Read(b)

		// EOF in the case of wsutil.Reader does not mean that the connection is
		// closed, it only means that the current frame is finished.
		if err == io.EOF {
			err = nil
		}
	}

	return n, err
}

// Write implements the io.ReadWriteCloser interface for *wsConn.
func (w *wsConn) Write(b []byte) (n int, err error) {
	log.Debug("Writing data len=%d to the WebSocket %v", len(b), w.remoteAddr)

	n, err = w.w.Write(b)
	if err != nil {
		return 0, err
	}

	err = w.w.Flush()

	return n, err
}

// Close implements the io.ReadWriteCloser interface for *wsConn.
func (w *wsConn) Close() (err error) {
	return w.rwc.Close()
}
