package client

import (
	
	
	

	
	
	tpt 

	ma 
	manet 
)

// HopTagWeight is the connection manager weight for connections carrying relay hop streams
var HopTagWeight = 5

type statLimitDuration struct{}
type statLimitData struct{}

var (
	StatLimitDuration = statLimitDuration{}
	StatLimitData     = statLimitData{}
)

type Conn struct {
	stream network.Stream
	remote peer.AddrInfo
	stat   network.ConnStats

	client *Client
}

type NetAddr struct {
	Relay  string
	Remote string
}

var _ net.Addr = (*NetAddr)(nil)

func ( *NetAddr) () string {
	return "libp2p-circuit-relay"
}

func ( *NetAddr) () string {
	return fmt.Sprintf("relay[%s-%s]", .Remote, .Relay)
}

// Conn interface
var _ manet.Conn = (*Conn)(nil)

func ( *Conn) () error {
	.untagHop()
	return .stream.Reset()
}

func ( *Conn) ( []byte) (int, error) {
	return .stream.Read()
}

func ( *Conn) ( []byte) (int, error) {
	return .stream.Write()
}

func ( *Conn) ( time.Time) error {
	return .stream.SetDeadline()
}

func ( *Conn) ( time.Time) error {
	return .stream.SetReadDeadline()
}

func ( *Conn) ( time.Time) error {
	return .stream.SetWriteDeadline()
}

// TODO: is it okay to cast c.Conn().RemotePeer() into a multiaddr? might be "user input"
func ( *Conn) () ma.Multiaddr {
	// TODO: We should be able to do this directly without converting to/from a string.
	,  := ma.NewComponent(
		ma.ProtocolWithCode(ma.P_P2P).Name,
		.stream.Conn().RemotePeer().String(),
	)
	if  != nil {
		log.Error()
		return ma.Join(.stream.Conn().RemoteMultiaddr(), circuitAddr)
	}
	return ma.Join(.stream.Conn().RemoteMultiaddr(), .Multiaddr(), circuitAddr)
}

func ( *Conn) () ma.Multiaddr {
	return .stream.Conn().LocalMultiaddr()
}

func ( *Conn) () net.Addr {
	,  := manet.ToNetAddr(.stream.Conn().LocalMultiaddr())
	if  != nil {
		log.Error("failed to convert local multiaddr to net addr:", )
		return nil
	}
	return 
}

func ( *Conn) () net.Addr {
	return &NetAddr{
		Relay:  .stream.Conn().RemotePeer().String(),
		Remote: .remote.ID.String(),
	}
}

// ConnStat interface
var _ network.ConnStat = (*Conn)(nil)

func ( *Conn) () network.ConnStats {
	return .stat
}

// tagHop tags the underlying relay connection so that it can be (somewhat) protected from the
// connection manager as it is an important connection that proxies other connections.
// This is handled here so that the user code doesnt need to bother with this and avoid
// clown shoes situations where a high value peer connection is behind a relayed connection and it is
// implicitly because the connection manager closed the underlying relay connection.
func ( *Conn) () {
	.client.mx.Lock()
	defer .client.mx.Unlock()

	 := .stream.Conn().RemotePeer()
	.client.hopCount[]++
	if .client.hopCount[] == 1 {
		.client.host.ConnManager().TagPeer(, "relay-hop-stream", HopTagWeight)
	}
}

// untagHop removes the relay-hop-stream tag if necessary; it is invoked when a relayed connection
// is closed.
func ( *Conn) () {
	.client.mx.Lock()
	defer .client.mx.Unlock()

	 := .stream.Conn().RemotePeer()
	.client.hopCount[]--
	if .client.hopCount[] == 0 {
		.client.host.ConnManager().UntagPeer(, "relay-hop-stream")
		delete(.client.hopCount, )
	}
}

type capableConnWithStat interface {
	tpt.CapableConn
	network.ConnStat
}

type capableConn struct {
	capableConnWithStat
}

var transportName = ma.ProtocolWithCode(ma.P_CIRCUIT).Name

func ( capableConn) () network.ConnectionState {
	return network.ConnectionState{
		Transport: transportName,
	}
}