package conn

import (
	"context"
	"io"
	"net"
	"runtime/trace"
	"sync"
	"time"
)

const (
	bufferSize = 16 << 10
)

type netError struct {
	isTimeout   bool
	isTemporary bool
	message     string
}

var _ net.Error = (*netError)(nil)

func (ne *netError) Error() string {
	if ne == nil {
		return "<nil>"
	}
	return ne.message
}
func (ne *netError) Timeout() bool   { return ne != nil && ne.isTimeout }
func (ne *netError) Temporary() bool { return ne != nil && ne.isTemporary }

type deadlineRequest struct {
	deadline time.Time
	complete func()
}

// A TimeoutReader wraps a blocking io.Reader, presenting it as an io.Reader
// where Read calls are able to time out and return early. The timeouts are
// controlled by SetReadDeadline, which conforms to the matching method of
// net.Conn.
type TimeoutReader struct {
	mu        sync.Mutex
	readCond  *sync.Cond
	emptyCond *sync.Cond
	data      []byte
	err       error
	stickyErr error
	deadline  chan *deadlineRequest
	timeout   bool // current and future reads should return immediately
	used      bool // has ReadFrom been called already on this value
}

var _ io.Closer = (*TimeoutReader)(nil)
var _ io.Reader = (*TimeoutReader)(nil)
var _ io.ReaderFrom = (*TimeoutReader)(nil)

func (tr *TimeoutReader) initLocked() {
	if tr.readCond != nil {
		return
	}

	tr.readCond = sync.NewCond(&tr.mu)
	tr.emptyCond = sync.NewCond(&tr.mu)

	ch := make(chan *deadlineRequest, 1)
	tr.deadline = ch
	go tr.watchDeadline(ch)
}

func (tr *TimeoutReader) watchDeadline(ch chan *deadlineRequest) {
	timer := time.NewTimer(0)
	if !timer.Stop() {
		<-timer.C
	}
	running := false
	for {
		select {
		case req, ok := <-ch:
			if !ok {
				timer.Stop()
				return
			}
			running = tr.setDeadline(req, timer, running)
		case <-timer.C:
			running = false
			tr.expireTimeout()
		}
	}
}

func (tr *TimeoutReader) setDeadline(req *deadlineRequest, timer *time.Timer, running bool) bool {
	defer req.complete()

	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.setDeadline")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	defer tr.mu.Unlock()

	if !timer.Stop() && running {
		<-timer.C
	}

	if req.deadline.IsZero() {
		trace.Logf(ctx, "timeout", "IsZero")
		tr.timeout = false
		return false
	}

	d := time.Until(req.deadline)
	trace.Logf(ctx, "timeout", "%s", d)
	if d <= 0 {
		tr.timeout = true
		tr.readCond.Broadcast()
		tr.emptyCond.Broadcast()
		return false
	}

	tr.timeout = false
	timer.Reset(d)
	return true
}

func (tr *TimeoutReader) expireTimeout() {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.expireTimeout")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	defer tr.mu.Unlock()

	tr.timeout = true
	tr.readCond.Broadcast()
	tr.emptyCond.Broadcast()
}

// Close interrupts calls to Read and ReadFrom, causing current and future calls
// to those methods return immediately with an error.
func (tr *TimeoutReader) Close() error {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.Close")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	defer tr.mu.Unlock()
	tr.initLocked()

	if tr.deadline == nil {
		// duplicate Close
		return nil
	}
	close(tr.deadline)
	tr.deadline = nil

	if tr.stickyErr == nil {
		tr.stickyErr = io.EOF
	}
	tr.readCond.Broadcast()
	tr.emptyCond.Broadcast()

	tr.lockedReadFromReady(ctx)
	tr.lockedReadReady(ctx)

	return nil
}

// SetReadDeadline sets the deadline for future Read calls and any
// currently-blocked Read call. A zero value for t means Read will not time out.
func (tr *TimeoutReader) SetReadDeadline(t time.Time) error {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.SetReadDeadline")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	tr.initLocked()
	tr.mu.Unlock()

	var wg sync.WaitGroup
	wg.Add(1)
	req := &deadlineRequest{
		deadline: t,
		complete: func() { wg.Done() },
	}

	// The deadline channel is closed and then set to nil while holding mu, so
	// we must hold mu while attempting to send on the channel.
	for {
		sent := false

		trace.WithRegion(ctx, "Lock", func() {
			tr.mu.Lock()
		})
		ch := tr.deadline
		select {
		case ch <- req:
			sent = true
		default:
		}
		tr.mu.Unlock()

		if sent {
			wg.Wait()
			return nil
		}

		if ch == nil {
			// Closed, no goroutine was even waiting to read the channel
			return nil
		}

		// Otherwise: The channel cannot fit our message. Wait for the deadline
		// goroutine to clear the backlog.
	}
}

// ReadFrom reads data from r until EOF or error. The return value is the number
// of bytes read. Any error except io.EOF encountered during the read is also
// returned.
func (tr *TimeoutReader) ReadFrom(r io.Reader) (int64, error) {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.ReadFrom")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	if tr.used {
		err := &netError{message: "timeoutReader is already initialized"}
		tr.mu.Unlock()
		return 0, err
	}
	tr.used = true
	tr.initLocked()
	tr.mu.Unlock()

	var nn int64
	var stickyErr error
	buf := make([]byte, bufferSize)
	for stickyErr == nil {
		trace.WithRegion(ctx, "Lock", func() {
			tr.mu.Lock()
		})
		trace.WithRegion(ctx, "Wait", func() {
			for !tr.lockedReadFromReady(ctx) {
				tr.emptyCond.Wait()
			}
		})
		stickyErr = tr.stickyErr
		// Aside from the case of tr.stickyErr being set: We know tr.data is
		// empty here, and it will remain empty until this function refills it.
		// (And tr.used verifies that this function is called only once.) We
		// also know that tr.err was consumed when the last of tr.data was
		// consumed.
		tr.mu.Unlock()

		if stickyErr != nil {
			break
		}

		var n int
		var err error
		trace.WithRegion(ctx, "Read", func() {
			n, err = r.Read(buf)
		})
		nn += int64(n)
		ne, ok := err.(net.Error)
		trace.Logf(ctx, "ReadFrom",
			"%p nn=%d err=%v ok=%t timeout=%t temporary=%t",
			tr, nn, err, ok, ok && ne.Timeout(), ok && ne.Temporary())

		trace.WithRegion(ctx, "Lock", func() {
			tr.mu.Lock()
		})
		tr.data = buf[:n]
		tr.err = err
		if ne, ok := err.(net.Error); err == io.EOF || (err != nil && !ok) || (ok && !ne.Temporary()) {
			stickyErr = err
			tr.stickyErr = err
		}
		tr.mu.Unlock()

		tr.readCond.Broadcast()
	}

	if stickyErr == io.EOF {
		// io.ReaderFrom says we don't return EOF
		stickyErr = nil
	}

	return nn, stickyErr
}

func (tr *TimeoutReader) lockedReadFromReady(ctx context.Context) bool {
	ready := len(tr.data) == 0 || tr.stickyErr != nil
	trace.Logf(ctx, "lockedReadFromReady",
		"ready=%t len(tr.data)=%d tr.stickyErr=%v",
		ready, len(tr.data), tr.stickyErr)
	return ready
}

func (tr *TimeoutReader) lockedReadReady(ctx context.Context) bool {
	ready := len(tr.data) > 0 || tr.err != nil || tr.stickyErr != nil || tr.timeout
	trace.Logf(ctx, "lockedReadReady",
		"ready=%t len(tr.data)=%d tr.err=%v tr.stickyErr=%v tr.timeout=%t",
		ready, len(tr.data), tr.err, tr.stickyErr, tr.timeout)
	return ready
}

// Read reads data from the connection. Read can be made to time out and return
// an error after a fixed time limit; see SetReadDeadline.
func (tr *TimeoutReader) Read(p []byte) (int, error) {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutReader.Read")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tr)

	trace.WithRegion(ctx, "Lock", func() {
		tr.mu.Lock()
	})
	defer tr.mu.Unlock()
	tr.initLocked()

	trace.WithRegion(ctx, "Wait", func() {
		for !tr.lockedReadReady(ctx) {
			tr.readCond.Wait()
		}
	})

	n := copy(p, tr.data)
	tr.data = tr.data[n:]

	// There's still some data left, so it's not yet time to report an error or
	// timeout.
	if len(tr.data) > 0 {
		tr.readCond.Signal()
		if !tr.lockedReadReady(ctx) {
			panic("signaled, but not ready")
		}
		return n, nil
	}

	// This call has consumed all available data.
	tr.emptyCond.Signal()
	if !tr.lockedReadFromReady(ctx) {
		panic("signaled, but not ready")
	}
	// It may now also consume any available error.
	if err := tr.err; err != nil {
		tr.err = nil
		return n, err
	}

	if err := tr.stickyErr; err != nil {
		return n, err
	}

	if tr.timeout {
		err := &netError{isTimeout: true, isTemporary: true, message: "timeout"}
		return n, err
	}

	return n, nil
}

// A TimeoutWriter wraps a blocking io.Writer, presenting it as an io.Writer
// where Write calls are able to time out and return early. The timeouts are
// controlled by SetWriteDeadline, which conforms to the matching method of
// net.Conn.
type TimeoutWriter struct {
	writeQueue sync.Mutex

	mu            sync.Mutex
	writePartCond *sync.Cond
	writeToCond   *sync.Cond
	closeCond     *sync.Cond
	data          []byte
	err           error
	deadline      chan *deadlineRequest
	timeout       bool // current and future writes should return immediately
	closed        bool
	writeError    bool // has WriteTo encountered an error
	used          bool // has WriteTo been called already on this value
}

var _ io.Closer = (*TimeoutWriter)(nil)
var _ io.Writer = (*TimeoutWriter)(nil)
var _ io.WriterTo = (*TimeoutWriter)(nil)

func (tw *TimeoutWriter) initLocked() {
	if tw.writePartCond != nil {
		return
	}

	tw.writePartCond = sync.NewCond(&tw.mu)
	tw.writeToCond = sync.NewCond(&tw.mu)
	tw.closeCond = sync.NewCond(&tw.mu)

	ch := make(chan *deadlineRequest)
	tw.deadline = ch
	go tw.watchDeadline(ch)
}

func (tw *TimeoutWriter) watchDeadline(ch chan *deadlineRequest) {
	timer := time.NewTimer(0)
	if !timer.Stop() {
		<-timer.C
	}
	running := false
	for {
		select {
		case req, ok := <-ch:
			if !ok {
				timer.Stop()
				return
			}
			running = tw.setDeadline(req, timer, running)
		case <-timer.C:
			running = false
			tw.expireTimeout()
		}
	}
}

func (tw *TimeoutWriter) setDeadline(req *deadlineRequest, timer *time.Timer, running bool) bool {
	defer req.complete()

	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.setDeadline")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	defer tw.mu.Unlock()

	if !timer.Stop() && running {
		<-timer.C
	}

	if req.deadline.IsZero() {
		trace.Logf(ctx, "timeout", "IsZero")
		tw.timeout = false
		return false
	}

	d := time.Until(req.deadline)
	trace.Logf(ctx, "timeout", "%s", d)
	if d <= 0 {
		tw.timeout = true
		tw.writePartCond.Broadcast()
		tw.writeToCond.Broadcast()
		tw.closeCond.Broadcast()
		return false
	}

	tw.timeout = false
	timer.Reset(d)
	return true
}

func (tw *TimeoutWriter) expireTimeout() {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.expireTimeout")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	defer tw.mu.Unlock()

	tw.timeout = true
	tw.writePartCond.Broadcast()
	tw.writeToCond.Broadcast()
	tw.closeCond.Broadcast()
}

// Close interrupts calls to Write and WriteTo, causing current and future calls
// to those methods return immediately with an error.
//
// It blocks until it has presented all buffered data to the target of WriteTo,
// or until that target has returned an error.
func (tw *TimeoutWriter) Close() error {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.Close")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	// Unblock calls to Write
	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	tw.initLocked()
	if tw.closed {
		// duplicate Close
		tw.mu.Unlock()
		return nil
	}
	tw.closed = true
	close(tw.deadline)
	tw.deadline = nil
	tw.writePartCond.Broadcast()
	tw.mu.Unlock()

	// Flush out calls to Write
	trace.WithRegion(ctx, "Enqueue", func() {
		tw.writeQueue.Lock()
	})
	defer tw.writeQueue.Unlock()
	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	defer tw.mu.Unlock()

	for !tw.lockedCloseReady(ctx) {
		trace.WithRegion(ctx, "Wait", func() {
			tw.closeCond.Wait()
		})
	}

	tw.writeToCond.Broadcast()

	return nil
}

// SetWriteDeadline sets the deadline for future Write calls and any
// currently-blocked Write call. Even if Write times out, it may return n > 0,
// indicating that some of the data was successfully written. A zero value for t
// means Write will not time out.
func (tw *TimeoutWriter) SetWriteDeadline(t time.Time) error {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.SetWriteDeadline")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	tw.initLocked()
	tw.mu.Unlock()

	var wg sync.WaitGroup
	wg.Add(1)
	req := &deadlineRequest{
		deadline: t,
		complete: func() { wg.Done() },
	}

	// The deadline channel is closed and then set to nil while holding mu, so
	// we must hold mu while attempting to send on the channel.
	for {
		sent := false

		trace.WithRegion(ctx, "Lock", func() {
			tw.mu.Lock()
		})
		ch := tw.deadline
		select {
		case ch <- req:
			sent = true
		default:
		}
		tw.mu.Unlock()

		if sent {
			wg.Wait()
			return nil
		}

		if ch == nil {
			// Closed, no goroutine was even waiting to read the channel
			return nil
		}

		// Otherwise: The channel cannot fit our message. Wait for the deadline
		// goroutine to clear the backlog.
	}
}

// WriteTo writes data to w until there's no more data to write or when an error
// occurs. The return value is the number of bytes written. Any error
// encountered during the write is also returned.
func (tw *TimeoutWriter) WriteTo(w io.Writer) (int64, error) {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.WriteTo")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	if tw.used {
		err := &netError{message: "timeoutWriter is already initialized"}
		tw.mu.Unlock()
		return 0, err
	}
	tw.used = true
	tw.initLocked()
	buf := make([]byte, bufferSize)
	tw.data = buf[:0]
	tw.writePartCond.Signal()
	tw.closeCond.Broadcast()
	if !tw.lockedWritePartReady(ctx) {
		panic("signaled, but not ready")
	}

	var nn int64
	for {
		trace.WithRegion(ctx, "Wait", func() {
			for !tw.lockedWriteToReady(ctx) {
				tw.writeToCond.Wait()
			}
		})

		// Finish writing out data before processing sticky errors.
		if tw.closed && len(tw.data) == 0 {
			tw.mu.Unlock()
			err := &netError{message: "closed"}
			return nn, err
		}

		// WriteTo, Write, Close, and SetWriteDeadline have a tricky interation:
		//
		// First, any data accepted via an inbound call to Write must be passed
		// along via a matching outbound call (from WriteTo to the provided
		// io.Writer) before Close can return. In practice, a user may call
		// Write, then call Close, and immediately expect the data to be present
		// on the io.Writer they provided in a call to WriteTo.
		//
		// Second, a user must be able to trigger any calls to Write and to
		// Close to return through a call to SetWriteDeadline. A slow call from
		// WriteTo out to the provided io.Writer must not be able to delay the
		// return from those functions indefinitely.
		//
		// In support of the first requirement, we read tw.data here and start
		// an outbound call to the provided io.Writer. After the call is
		// complete, we clear out the buffered data.

		buf = tw.data
		tw.mu.Unlock()

		var n int
		var err error
		trace.WithRegion(ctx, "Write", func() {
			n, err = w.Write(buf)
		})

		trace.WithRegion(ctx, "Lock", func() {
			tw.mu.Lock()
		})

		nn += int64(n)
		ne, ok := err.(net.Error)
		trace.Logf(ctx, "WriteTo",
			"%p nn=%d err=%v ok=%t timeout=%t temporary=%t",
			tw, nn, err, ok, ok && ne.Timeout(), ok && ne.Temporary())
		if err != nil {
			tw.writeError = true
			tw.closeCond.Broadcast()
			tw.mu.Unlock()
			return nn, err
		}

		tw.data = buf[:0]
		tw.writePartCond.Signal()
		tw.closeCond.Broadcast()
		if !tw.lockedWritePartReady(ctx) {
			panic("signaled, but not ready")
		}
	}
}

// Write writes data to the connection. Write can be made to time out and return
// an error after a fixed time limit; see SetWriteDeadline.
func (tw *TimeoutWriter) Write(p []byte) (int, error) {
	ctx := context.Background()
	ctx, task := trace.NewTask(ctx, "timeoutWriter.Write")
	defer task.End()
	trace.Logf(ctx, "pointer", "%p", tw)

	trace.WithRegion(ctx, "Enqueue", func() {
		tw.writeQueue.Lock()
	})
	defer tw.writeQueue.Unlock()

	var nn int
	for {
		n, err := tw.writePart(ctx, p)
		nn += n
		p = p[n:]
		if err != nil || len(p) == 0 {
			func() {
				// Wait until WriteTo has accepted the data, or a deadline (from
				// SetWriteDeadline) has expired.
				trace.WithRegion(ctx, "Lock", func() {
					tw.mu.Lock()
				})
				defer tw.mu.Unlock()
				tw.initLocked()
				trace.WithRegion(ctx, "Wait", func() {
					for !tw.lockedWritePartReady(ctx) {
						tw.writePartCond.Wait()
					}
				})
			}()
			return nn, err
		}
	}
}

// writePart follows the definition of io.Writer.Write, but allows short writes
// without returning an error.
func (tw *TimeoutWriter) writePart(ctx context.Context, p []byte) (int, error) {
	region := trace.StartRegion(ctx, "writePart")
	defer region.End()

	trace.WithRegion(ctx, "Lock", func() {
		tw.mu.Lock()
	})
	defer tw.mu.Unlock()
	tw.initLocked()

	trace.WithRegion(ctx, "Wait", func() {
		for !tw.lockedWritePartReady(ctx) {
			tw.writePartCond.Wait()
		}
	})

	if tw.closed {
		err := &netError{message: "closed"}
		return 0, err
	}

	if tw.timeout {
		err := &netError{isTimeout: true, isTemporary: true, message: "timeout"}
		return 0, err
	}

	if len(p) == 0 {
		return 0, nil
	}

	if len(tw.data) > 0 {
		panic("timeoutWriter data structure is corrupt")
	}

	n := copy(tw.data[:cap(tw.data)], p)
	tw.data = tw.data[:n]
	tw.writeToCond.Signal()
	if !tw.lockedWriteToReady(ctx) {
		panic("signaled, but not ready")
	}

	return n, nil
}

func (tw *TimeoutWriter) lockedWritePartReady(ctx context.Context) bool {
	ready := (len(tw.data) == 0 && cap(tw.data) > 0) || tw.closed || tw.timeout
	trace.Logf(ctx, "lockedWritePartReady",
		"ready=%t len(tw.data)=%d cap(tw.data)=%d tw.closed=%t tw.timeout=%t",
		ready, len(tw.data), cap(tw.data), tw.closed, tw.timeout)
	return ready
}

func (tw *TimeoutWriter) lockedWriteToReady(ctx context.Context) bool {
	ready := len(tw.data) > 0 || tw.closed
	trace.Logf(ctx, "lockedWriteToReady",
		"ready=%t len(tw.data)=%d tw.closed=%t",
		ready, len(tw.data), tw.closed)
	return ready
}

func (tw *TimeoutWriter) lockedCloseReady(ctx context.Context) bool {
	ready := len(tw.data) == 0 || tw.timeout || tw.writeError
	trace.Logf(ctx, "lockedCloseReady",
		"ready=%t len(tw.data)=%d tw.timeout=%t tw.writeError=%t",
		ready, len(tw.data), tw.timeout, tw.writeError)
	return ready
}

// A Reader is the minimal set of net.Conn methods relevant to reading data. It
// supports Read calls, which a caller can cancel temporarily with
// SetReadDeadline, or disrupt permanently with Close.
type Reader interface {
	io.Reader
	io.Closer
	SetReadDeadline(time.Time) error
}

// A Writer is the minimal set of net.Conn methods relevant to writing data. It
// supports Write calls, which a caller can cancel temporarily with
// SetWriteDeadline, or disrupt permanently with Close.
type Writer interface {
	io.Writer
	io.Closer
	SetWriteDeadline(time.Time) error
}

// A ReadWriter combines Read-only and Write-only aspects into a complete
// implementation of net.Conn.
type ReadWriter struct {
	Reader
	Writer
	Closers []io.Closer
	Local   net.Addr
	Remote  net.Addr
}

var _ net.Conn = (*ReadWriter)(nil)

// LocalAddr returns the local network address.
func (c *ReadWriter) LocalAddr() net.Addr { return c.Local }

// RemoteAddr returns the remote network address.
func (c *ReadWriter) RemoteAddr() net.Addr { return c.Remote }

// Read reads data from the connection. Read can be made to time out and return
// an error after a fixed time limit; see SetDeadline and SetReadDeadline.
func (c *ReadWriter) Read(p []byte) (int, error) { return c.Reader.Read(p) }

// Write writes data to the connection. Write can be made to time out and return
// an error after a fixed time limit; see SetDeadline and SetWriteDeadline.
func (c *ReadWriter) Write(p []byte) (int, error) { return c.Writer.Write(p) }

// Close closes the connection. Any blocked Read or Write operations will be
// unblocked and return errors.
func (c *ReadWriter) Close() error {
	closers := append([]io.Closer{c.Reader, c.Writer}, c.Closers...)
	var err error
	for _, closer := range closers {
		e := closer.Close()
		if err == nil {
			err = e
		}
	}
	return err
}

// SetReadDeadline sets the deadline for future Read calls and any
// currently-blocked Read call. A zero value for t means Read will not time out.
func (c *ReadWriter) SetReadDeadline(t time.Time) error { return c.Reader.SetReadDeadline(t) }

// SetWriteDeadline sets the deadline for future Write calls and any
// currently-blocked Write call. Even if Write times out, it may return n > 0,
// indicating that some of the data was successfully written. A zero value for t
// means Write will not time out.
func (c *ReadWriter) SetWriteDeadline(t time.Time) error { return c.Writer.SetWriteDeadline(t) }

// SetDeadline sets the read and write deadlines associated with the connection.
// It is equivalent to calling both SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations fail instead of
// blocking. The deadline applies to all future and pending I/O, not just the
// immediately following call to Read or Write. After a deadline has been
// exceeded, the connection can be refreshed by setting a deadline in the
// future.
//
// If the deadline is exceeded a call to Read or Write or to other I/O methods
// will return an error that wraps os.ErrDeadlineExceeded. This can be tested
// using errors.Is(err, os.ErrDeadlineExceeded). The error's Timeout method will
// return true, but note that there are other possible errors for which the
// Timeout method will return true even if the deadline has not been exceeded.
//
// An idle timeout can be implemented by repeatedly extending the deadline after
// successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
func (c *ReadWriter) SetDeadline(t time.Time) error {
	// TODO: wrap os.ErrDeadlineExceeded as net.Conn promises

	rerr := c.Reader.SetReadDeadline(t)
	werr := c.Writer.SetWriteDeadline(t)

	if rerr != nil {
		return rerr
	}
	return werr
}
