// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package ice

import (
	
	
	
	
	
	

	
	
)

type activeTCPConn struct {
	readBuffer, writeBuffer *packetio.Buffer
	localAddr, remoteAddr   atomic.Value
	closed                  int32
}

func newActiveTCPConn(
	 context.Context,
	 string,
	 netip.AddrPort,
	 logging.LeveledLogger,
) ( *activeTCPConn) {
	 = &activeTCPConn{
		readBuffer:  packetio.NewBuffer(),
		writeBuffer: packetio.NewBuffer(),
	}

	,  := getTCPAddrOnInterface()
	if  != nil {
		atomic.StoreInt32(&.closed, 1)
		.Infof("Failed to dial TCP address %s: %v", , )

		return 
	}
	.localAddr.Store()

	go func() {
		defer func() {
			atomic.StoreInt32(&.closed, 1)
		}()

		 := &net.Dialer{
			LocalAddr: ,
		}
		,  := .DialContext(, "tcp", .String())
		if  != nil {
			.Infof("Failed to dial TCP address %s: %v", , )

			return
		}
		.remoteAddr.Store(.RemoteAddr())

		go func() {
			 := make([]byte, receiveMTU)

			for atomic.LoadInt32(&.closed) == 0 {
				,  := readStreamingPacket(, )
				if  != nil {
					.Infof("Failed to read streaming packet: %s", )

					break
				}

				if ,  := .readBuffer.Write([:]);  != nil {
					.Infof("Failed to write to buffer: %s", )

					break
				}
			}
		}()

		 := make([]byte, receiveMTU)

		for atomic.LoadInt32(&.closed) == 0 {
			,  := .writeBuffer.Read()
			if  != nil {
				.Infof("Failed to read from buffer: %s", )

				break
			}

			if _,  = writeStreamingPacket(, [:]);  != nil {
				.Infof("Failed to write streaming packet: %s", )

				break
			}
		}

		if  := .Close();  != nil {
			.Infof("Failed to close connection: %s", )
		}
	}()

	return 
}

func ( *activeTCPConn) ( []byte) ( int,  net.Addr,  error) {
	if atomic.LoadInt32(&.closed) == 1 {
		return 0, nil, io.ErrClosedPipe
	}

	,  = .readBuffer.Read()
	// RemoteAddr is assuredly set *after* we can read from the buffer
	 = .RemoteAddr()

	return
}

func ( *activeTCPConn) ( []byte,  net.Addr) ( int,  error) {
	if atomic.LoadInt32(&.closed) == 1 {
		return 0, io.ErrClosedPipe
	}

	return .writeBuffer.Write()
}

func ( *activeTCPConn) () error {
	atomic.StoreInt32(&.closed, 1)
	_ = .readBuffer.Close()
	_ = .writeBuffer.Close()

	return nil
}

func ( *activeTCPConn) () net.Addr {
	if ,  := .localAddr.Load().(*net.TCPAddr);  {
		return 
	}

	return &net.TCPAddr{}
}

// RemoteAddr returns the remote address of the connection which is only
// set once a background goroutine has successfully dialed. That means
// this may return ":0" for the address prior to that happening. If this
// becomes an issue, we can introduce a synchronization point between Dial
// and these methods.
func ( *activeTCPConn) () net.Addr {
	if ,  := .remoteAddr.Load().(*net.TCPAddr);  {
		return 
	}

	return &net.TCPAddr{}
}

func ( *activeTCPConn) (time.Time) error      { return io.EOF }
func ( *activeTCPConn) (time.Time) error  { return io.EOF }
func ( *activeTCPConn) (time.Time) error { return io.EOF }

func getTCPAddrOnInterface( string) (*net.TCPAddr, error) {
	,  := net.ResolveTCPAddr("tcp", )
	if  != nil {
		return nil, 
	}

	,  := net.ListenTCP("tcp", )
	if  != nil {
		return nil, 
	}
	defer func() {
		_ = .Close()
	}()

	,  := .Addr().(*net.TCPAddr)
	if ! {
		return nil, errInvalidAddress
	}

	return , nil
}