package mux

import (
	"encoding/binary"
	"io"
	"math/rand"
	"net"

	"github.com/sagernet/sing/common"
	"github.com/sagernet/sing/common/buf"
	"github.com/sagernet/sing/common/bufio"
	N "github.com/sagernet/sing/common/network"
	"github.com/sagernet/sing/common/rw"
)

const kFirstPaddings = 16

type paddingConn struct {
	N.ExtendedConn
	writer           N.VectorisedWriter
	readPadding      int
	writePadding     int
	readRemaining    int
	paddingRemaining int
}

func newPaddingConn(conn net.Conn) net.Conn {
	writer, isVectorised := bufio.CreateVectorisedWriter(conn)
	if isVectorised {
		return &vectorisedPaddingConn{
			paddingConn{
				ExtendedConn: bufio.NewExtendedConn(conn),
				writer:       bufio.NewVectorisedWriter(conn),
			},
			writer,
		}
	} else {
		return &paddingConn{
			ExtendedConn: bufio.NewExtendedConn(conn),
			writer:       bufio.NewVectorisedWriter(conn),
		}
	}
}

func (c *paddingConn) Read(p []byte) (n int, err error) {
	if c.readRemaining > 0 {
		if len(p) > c.readRemaining {
			p = p[:c.readRemaining]
		}
		n, err = c.ExtendedConn.Read(p)
		if err != nil {
			return
		}
		c.readRemaining -= n
		return
	}
	if c.paddingRemaining > 0 {
		err = rw.SkipN(c.ExtendedConn, c.paddingRemaining)
		if err != nil {
			return
		}
		c.paddingRemaining = 0
	}
	if c.readPadding < kFirstPaddings {
		var paddingHdr []byte
		if len(p) >= 4 {
			paddingHdr = p[:4]
		} else {
			paddingHdr = make([]byte, 4)
		}
		_, err = io.ReadFull(c.ExtendedConn, paddingHdr)
		if err != nil {
			return
		}
		originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
		paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))
		if len(p) > originalDataSize {
			p = p[:originalDataSize]
		}
		n, err = c.ExtendedConn.Read(p)
		if err != nil {
			return
		}
		c.readPadding++
		c.readRemaining = originalDataSize - n
		c.paddingRemaining = paddingLen
		return
	}
	return c.ExtendedConn.Read(p)
}

func (c *paddingConn) Write(p []byte) (n int, err error) {
	for pLen := len(p); pLen > 0; {
		var data []byte
		if pLen > 65535 {
			data = p[:65535]
			p = p[65535:]
			pLen -= 65535
		} else {
			data = p
			pLen = 0
		}
		var writeN int
		writeN, err = c.write(data)
		n += writeN
		if err != nil {
			break
		}
	}
	return n, err
}

func (c *paddingConn) write(p []byte) (n int, err error) {
	if c.writePadding < kFirstPaddings {
		paddingLen := 256 + rand.Intn(512)
		buffer := buf.NewSize(4 + len(p) + paddingLen)
		defer buffer.Release()
		header := buffer.Extend(4)
		binary.BigEndian.PutUint16(header[:2], uint16(len(p)))
		binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
		common.Must1(buffer.Write(p))
		buffer.Extend(paddingLen)
		_, err = c.ExtendedConn.Write(buffer.Bytes())
		if err == nil {
			n = len(p)
		}
		c.writePadding++
		return
	}
	return c.ExtendedConn.Write(p)
}

func (c *paddingConn) ReadBuffer(buffer *buf.Buffer) error {
	p := buffer.FreeBytes()
	if c.readRemaining > 0 {
		if len(p) > c.readRemaining {
			p = p[:c.readRemaining]
		}
		n, err := c.ExtendedConn.Read(p)
		if err != nil {
			return err
		}
		c.readRemaining -= n
		buffer.Truncate(n)
		return nil
	}
	if c.paddingRemaining > 0 {
		err := rw.SkipN(c.ExtendedConn, c.paddingRemaining)
		if err != nil {
			return err
		}
		c.paddingRemaining = 0
	}
	if c.readPadding < kFirstPaddings {
		var paddingHdr []byte
		if len(p) >= 4 {
			paddingHdr = p[:4]
		} else {
			paddingHdr = make([]byte, 4)
		}
		_, err := io.ReadFull(c.ExtendedConn, paddingHdr)
		if err != nil {
			return err
		}
		originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
		paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))

		if len(p) > originalDataSize {
			p = p[:originalDataSize]
		}
		n, err := c.ExtendedConn.Read(p)
		if err != nil {
			return err
		}
		c.readPadding++
		c.readRemaining = originalDataSize - n
		c.paddingRemaining = paddingLen
		buffer.Truncate(n)
		return nil
	}
	return c.ExtendedConn.ReadBuffer(buffer)
}

func (c *paddingConn) WriteBuffer(buffer *buf.Buffer) error {
	if c.writePadding < kFirstPaddings {
		bufferLen := buffer.Len()
		if bufferLen > 65535 {
			return common.Error(c.Write(buffer.Bytes()))
		}
		paddingLen := 256 + rand.Intn(512)
		header := buffer.ExtendHeader(4)
		binary.BigEndian.PutUint16(header[:2], uint16(bufferLen))
		binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
		buffer.Extend(paddingLen)
		c.writePadding++
	}
	return c.ExtendedConn.WriteBuffer(buffer)
}

func (c *paddingConn) FrontHeadroom() int {
	return 4 + 256 + 1024
}

func (c *paddingConn) Upstream() any {
	return c.ExtendedConn
}

type vectorisedPaddingConn struct {
	paddingConn
	writer N.VectorisedWriter
}

func (c *vectorisedPaddingConn) WriteVectorised(buffers []*buf.Buffer) error {
	if c.writePadding < kFirstPaddings {
		bufferLen := buf.LenMulti(buffers)
		if bufferLen > 65535 {
			defer buf.ReleaseMulti(buffers)
			for _, buffer := range buffers {
				_, err := c.Write(buffer.Bytes())
				if err != nil {
					return err
				}
			}
			return nil
		}
		paddingLen := 256 + rand.Intn(512)
		header := buf.NewSize(4)
		common.Must(
			binary.Write(header, binary.BigEndian, uint16(bufferLen)),
			binary.Write(header, binary.BigEndian, uint16(paddingLen)),
		)
		c.writePadding++
		padding := buf.NewSize(paddingLen)
		padding.Extend(paddingLen)
		buffers = append(append([]*buf.Buffer{header}, buffers...), padding)
	}
	return c.writer.WriteVectorised(buffers)
}
