package network

import (
	"io"
	"syscall"

	"github.com/sagernet/sing/common"
	"github.com/sagernet/sing/common/buf"
	M "github.com/sagernet/sing/common/metadata"
)

type ReadWaitable interface {
	InitializeReadWaiter(options ReadWaitOptions) (needCopy bool)
}

type ReadWaitOptions struct {
	FrontHeadroom  int
	RearHeadroom   int
	MTU            int
	ReadOverhead   int
	IncreaseBuffer bool
	BatchSize      int
}

func NewReadWaitOptions(source any, destination any) ReadWaitOptions {
	return ReadWaitOptions{
		FrontHeadroom: CalculateFrontHeadroom(destination),
		RearHeadroom:  CalculateRearHeadroom(destination),
		MTU:           CalculateMTU(source, destination),
		ReadOverhead:  CalculateReaderOverhead(source),
	}
}

func (o ReadWaitOptions) NeedHeadroom() bool {
	return o.FrontHeadroom > 0 || o.RearHeadroom > 0
}

func (o ReadWaitOptions) Copy(buffer *buf.Buffer) *buf.Buffer {
	if o.FrontHeadroom > buffer.Start() ||
		o.RearHeadroom > buffer.FreeLen() {
		newBuffer := buf.NewSize(buffer.Len() + o.FrontHeadroom + o.RearHeadroom)
		if o.FrontHeadroom > 0 {
			newBuffer.Resize(o.FrontHeadroom, 0)
		}
		newBuffer.Write(buffer.Bytes())
		buffer.Release()
		return newBuffer
	} else {
		return buffer
	}
}

func (o ReadWaitOptions) NewBuffer() *buf.Buffer {
	bufferSize := buf.BufferSize
	if o.IncreaseBuffer {
		if o.MTU > 0 {
			bufferSize = o.MTU + o.ReadOverhead + o.FrontHeadroom + o.RearHeadroom
		} else {
			bufferSize = 65535
		}
		if bufferSize > buf.MaxPooledBufferSize {
			bufferSize = buf.MaxPooledBufferSize
		}
	} else if o.MTU > 0 {
		mtuBufferSize := o.MTU + o.ReadOverhead + o.FrontHeadroom + o.RearHeadroom
		if mtuBufferSize < bufferSize {
			bufferSize = mtuBufferSize
		}
	}
	minimumBufferSize := o.FrontHeadroom + o.RearHeadroom + 1
	if bufferSize < minimumBufferSize {
		bufferSize = minimumBufferSize
	}
	buffer := buf.NewSize(bufferSize)
	if o.FrontHeadroom > 0 {
		buffer.Resize(o.FrontHeadroom, 0)
	}
	if o.RearHeadroom > 0 {
		buffer.Reserve(o.RearHeadroom)
	}
	return buffer
}

func (o ReadWaitOptions) NewPacketBuffer() *buf.Buffer {
	bufferSize := buf.UDPBufferSize
	if o.MTU > 0 {
		bufferSize = o.MTU + o.ReadOverhead + o.FrontHeadroom + o.RearHeadroom
	}
	minimumBufferSize := o.FrontHeadroom + o.RearHeadroom + 1
	if bufferSize < minimumBufferSize {
		bufferSize = minimumBufferSize
	}
	buffer := buf.NewSize(bufferSize)
	if o.FrontHeadroom > 0 {
		buffer.Resize(o.FrontHeadroom, 0)
	}
	if o.RearHeadroom > 0 {
		buffer.Reserve(o.RearHeadroom)
	}
	return buffer
}

func (o ReadWaitOptions) NewBufferSize(bufferSize int) *buf.Buffer {
	bufferSize += o.FrontHeadroom + o.RearHeadroom
	buffer := buf.NewSize(bufferSize)
	if o.FrontHeadroom > 0 {
		buffer.Resize(o.FrontHeadroom, 0)
	}
	if o.RearHeadroom > 0 {
		buffer.Reserve(o.RearHeadroom)
	}
	return buffer
}

func (o ReadWaitOptions) PostReturn(buffer *buf.Buffer) {
	if o.RearHeadroom > 0 {
		buffer.OverCap(o.RearHeadroom)
	}
}

type ReadWaiter interface {
	ReadWaitable
	WaitReadBuffer() (buffer *buf.Buffer, err error)
}

type ReadWaitCreator interface {
	CreateReadWaiter() (ReadWaiter, bool)
}

type VectorisedReadWaiter interface {
	ReadWaitable
	WaitReadBuffers() (buffers []*buf.Buffer, err error)
}

type VectorisedReadWaitCreator interface {
	CreateVectorisedReadWaiter() (VectorisedReadWaiter, bool)
}

type PacketReadWaiter interface {
	ReadWaitable
	WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error)
}

type PacketReadWaitCreator interface {
	CreateReadWaiter() (PacketReadWaiter, bool)
}

type PacketBatchReadWaiter interface {
	ReadWaitable
	WaitReadPackets() (buffers []*buf.Buffer, destinations []M.Socksaddr, err error)
}

type PacketBatchReadWaitCreator interface {
	CreatePacketBatchReadWaiter() (PacketBatchReadWaiter, bool)
}

type ConnectedPacketBatchReadWaiter interface {
	ReadWaitable
	WaitReadConnectedPackets() (buffers []*buf.Buffer, destination M.Socksaddr, err error)
}

type ConnectedPacketBatchReadWaitCreator interface {
	CreateConnectedPacketBatchReadWaiter() (ConnectedPacketBatchReadWaiter, bool)
}

// Deprecated: use PacketBatchReadWaiter.
type VectorisedPacketReadWaiter = PacketBatchReadWaiter

// Deprecated: use PacketBatchReadWaitCreator.
type VectorisedPacketReadWaitCreator interface {
	CreateVectorisedPacketReadWaiter() (PacketBatchReadWaiter, bool)
}

type SyscallReader interface {
	SyscallConnForRead() syscall.RawConn
	HandleSyscallReadError(inputErr error) ([]byte, error)
}

func SyscallAvailableForRead(reader io.Reader) bool {
	if _, ok := reader.(syscall.Conn); ok {
		return true
	}
	if _, ok := reader.(SyscallReader); ok {
		return true
	}
	if u, ok := reader.(ReaderWithUpstream); !ok || !u.ReaderReplaceable() {
		return false
	}
	if u, ok := reader.(WithUpstreamReader); ok {
		return SyscallAvailableForRead(u.UpstreamReader().(io.Reader))
	}
	if u, ok := reader.(common.WithUpstream); ok {
		return SyscallAvailableForRead(u.Upstream().(io.Reader))
	}
	return false
}

func SyscallConnForRead(reader io.Reader) (SyscallReader, syscall.RawConn) {
	if c, ok := reader.(syscall.Conn); ok {
		conn, _ := c.SyscallConn()
		return nil, conn
	}
	if c, ok := reader.(SyscallReader); ok {
		return c, c.SyscallConnForRead()
	}
	if u, ok := reader.(ReaderWithUpstream); !ok || !u.ReaderReplaceable() {
		return nil, nil
	}
	if u, ok := reader.(WithUpstreamReader); ok {
		return SyscallConnForRead(u.UpstreamReader().(io.Reader))
	}
	if u, ok := reader.(common.WithUpstream); ok {
		return SyscallConnForRead(u.Upstream().(io.Reader))
	}
	return nil, nil
}

type SyscallWriter interface {
	SyscallConnForWrite() syscall.RawConn
}

func SyscallAvailableForWrite(writer io.Writer) bool {
	if _, ok := writer.(syscall.Conn); ok {
		return true
	}
	if _, ok := writer.(SyscallWriter); ok {
		return true
	}
	if u, ok := writer.(WriterWithUpstream); !ok || !u.WriterReplaceable() {
		return false
	}
	if u, ok := writer.(WithUpstreamWriter); ok {
		return SyscallAvailableForWrite(u.UpstreamWriter().(io.Writer))
	}
	if u, ok := writer.(common.WithUpstream); ok {
		return SyscallAvailableForWrite(u.Upstream().(io.Writer))
	}
	return false
}

func SyscallConnForWrite(writer io.Writer) (SyscallWriter, syscall.RawConn) {
	if c, ok := writer.(syscall.Conn); ok {
		conn, _ := c.SyscallConn()
		return nil, conn
	}
	if c, ok := writer.(SyscallWriter); ok {
		return c, c.SyscallConnForWrite()
	}
	if u, ok := writer.(WriterWithUpstream); !ok || !u.WriterReplaceable() {
		return nil, nil
	}
	if u, ok := writer.(WithUpstreamWriter); ok {
		return SyscallConnForWrite(u.UpstreamWriter().(io.Writer))
	}
	if u, ok := writer.(common.WithUpstream); ok {
		return SyscallConnForWrite(u.Upstream().(io.Writer))
	}
	return nil, nil
}
