package bufio

import (
	"io"
	"net"

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

func NewInt64CounterConn(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *CounterConn {
	return &CounterConn{
		NewExtendedConn(conn),
		common.Map(readCounter, func(it *atomic.Int64) N.CountFunc {
			return func(n int64) {
				it.Add(n)
			}
		}),
		common.Map(writeCounter, func(it *atomic.Int64) N.CountFunc {
			return func(n int64) {
				it.Add(n)
			}
		}),
	}
}

func NewCounterConn(conn net.Conn, readCounter []N.CountFunc, writeCounter []N.CountFunc) *CounterConn {
	return &CounterConn{NewExtendedConn(conn), readCounter, writeCounter}
}

type CounterConn struct {
	N.ExtendedConn
	readCounter  []N.CountFunc
	writeCounter []N.CountFunc
}

func (c *CounterConn) Read(p []byte) (n int, err error) {
	n, err = c.ExtendedConn.Read(p)
	if n > 0 {
		for _, counter := range c.readCounter {
			counter(int64(n))
		}
	}
	return n, err
}

func (c *CounterConn) ReadBuffer(buffer *buf.Buffer) error {
	err := c.ExtendedConn.ReadBuffer(buffer)
	if err != nil {
		return err
	}
	if buffer.Len() > 0 {
		for _, counter := range c.readCounter {
			counter(int64(buffer.Len()))
		}
	}
	return nil
}

func (c *CounterConn) Write(p []byte) (n int, err error) {
	n, err = c.ExtendedConn.Write(p)
	if n > 0 {
		for _, counter := range c.writeCounter {
			counter(int64(n))
		}
	}
	return n, err
}

func (c *CounterConn) WriteBuffer(buffer *buf.Buffer) error {
	dataLen := int64(buffer.Len())
	err := c.ExtendedConn.WriteBuffer(buffer)
	if err != nil {
		return err
	}
	if dataLen > 0 {
		for _, counter := range c.writeCounter {
			counter(dataLen)
		}
	}
	return nil
}

func (c *CounterConn) UnwrapReader() (io.Reader, []N.CountFunc) {
	return c.ExtendedConn, c.readCounter
}

func (c *CounterConn) UnwrapWriter() (io.Writer, []N.CountFunc) {
	return c.ExtendedConn, c.writeCounter
}

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