package ray

import (
	"io"
	"net"
	"time"

	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/signal"
)

type ConnectionOption func(*connection)

func ConnLocalAddr(addr net.Addr) ConnectionOption {
	return func(c *connection) {
		c.localAddr = addr
	}
}

func ConnRemoteAddr(addr net.Addr) ConnectionOption {
	return func(c *connection) {
		c.remoteAddr = addr
	}
}

func ConnCloseSignal(s *signal.Notifier) ConnectionOption {
	return func(c *connection) {
		c.closeSignal = s
	}
}

type connection struct {
	input       InputStream
	output      OutputStream
	closed      bool
	localAddr   net.Addr
	remoteAddr  net.Addr
	closeSignal *signal.Notifier

	reader *buf.BufferedReader
}

var zeroAddr net.Addr = &net.TCPAddr{IP: []byte{0, 0, 0, 0}}

// NewConnection wraps a Ray into net.Conn.
func NewConnection(input InputStream, output OutputStream, options ...ConnectionOption) net.Conn {
	c := &connection{
		input:      input,
		output:     output,
		localAddr:  zeroAddr,
		remoteAddr: zeroAddr,
		reader:     buf.NewBufferedReader(input),
	}

	for _, opt := range options {
		opt(c)
	}

	return c
}

// Read implements net.Conn.Read().
func (c *connection) Read(b []byte) (int, error) {
	if c.closed {
		return 0, io.EOF
	}
	return c.reader.Read(b)
}

// ReadMultiBuffer implements buf.Reader.
func (c *connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
	return c.reader.ReadMultiBuffer()
}

// Write implements net.Conn.Write().
func (c *connection) Write(b []byte) (int, error) {
	if c.closed {
		return 0, io.ErrClosedPipe
	}

	l := len(b)
	mb := buf.NewMultiBufferCap(l/buf.Size + 1)
	mb.Write(b)
	return l, c.output.WriteMultiBuffer(mb)
}

func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
	if c.closed {
		return io.ErrClosedPipe
	}

	return c.output.WriteMultiBuffer(mb)
}

// Close implements net.Conn.Close().
func (c *connection) Close() error {
	c.closed = true
	c.output.Close()
	c.input.CloseError()
	if c.closeSignal != nil {
		c.closeSignal.Signal()
	}
	return nil
}

// LocalAddr implements net.Conn.LocalAddr().
func (c *connection) LocalAddr() net.Addr {
	return c.localAddr
}

// RemoteAddr implements net.Conn.RemoteAddr().
func (c *connection) RemoteAddr() net.Addr {
	return c.remoteAddr
}

// SetDeadline implements net.Conn.SetDeadline().
func (c *connection) SetDeadline(t time.Time) error {
	return nil
}

// SetReadDeadline implements net.Conn.SetReadDeadline().
func (c *connection) SetReadDeadline(t time.Time) error {
	return nil
}

// SetWriteDeadline implement net.Conn.SetWriteDeadline().
func (c *connection) SetWriteDeadline(t time.Time) error {
	return nil
}
