package neo

import (
	
	
	
	
	
	
)

// Net is virtual "net" package, implements mesh of peers.
type Net struct {
	peers map[string]*PacketConn
}

type packet struct {
	buf  []byte
	addr net.Addr
}

// PacketConn simulates mesh peer of Net.
type PacketConn struct {
	packets chan packet
	addr    net.Addr
	net     *Net

	closedMux sync.Mutex
	closed    bool

	mux           sync.Mutex
	deadline      notifier
	readDeadline  notifier
	writeDeadline notifier
}

func ( net.Addr) string {
	if ,  := .(*net.UDPAddr);  {
		return "udp/" + .String()
	}
	return .Network() + "/" + .String()
}

func ( *PacketConn) () bool {
	if  == nil {
		return false
	}
	.closedMux.Lock()
	defer .closedMux.Unlock()
	return !.closed
}

var ErrDeadline = errors.New("deadline")

// ReadFrom reads a packet from the connection,
// copying the payload into p.
func ( *PacketConn) ( []byte) ( int,  net.Addr,  error) {
	if !.ok() {
		return 0, nil, syscall.EINVAL
	}

	.mux.Lock()
	 := .deadline
	 := .readDeadline
	.mux.Unlock()

	select {
	case  := <-.packets:
		return copy(, .buf), .addr, nil
	case <-:
		return 0, nil, ErrDeadline
	case <-:
		return 0, nil, ErrDeadline
	}
}

// WriteTo writes a packet with payload p to addr.
func ( *PacketConn) ( []byte,  net.Addr) ( int,  error) {
	if !.ok() {
		return 0, syscall.EINVAL
	}

	.mux.Lock()
	 := .deadline
	 := .writeDeadline
	.mux.Unlock()

	select {
	case .net.peers[addrKey()].packets <- packet{
		addr: .addr,
		buf:  append([]byte{}, ...),
	}:
		return len(), nil
	case <-:
		return 0, ErrDeadline
	case <-:
		return 0, ErrDeadline
	}
}

func ( *PacketConn) () net.Addr { return .addr }

// Close closes the connection.
func ( *PacketConn) () error {
	if !.ok() {
		return syscall.EINVAL
	}
	.closedMux.Lock()
	defer .closedMux.Unlock()
	if .closed {
		return syscall.EINVAL
	}
	.closed = true
	close(.packets)
	return nil
}

type notifier chan struct{}

func ( time.Time) notifier {
	 := make(notifier)
	go func() {
		 := time.Now()
		<-time.After(.Sub())
		close()
	}()

	return 
}

func ( *PacketConn) ( time.Time) error {
	if !.ok() {
		return syscall.EINVAL
	}
	.mux.Lock()
	.deadline = simpleDeadline()
	.mux.Unlock()
	return nil
}

func ( *PacketConn) ( time.Time) error {
	if !.ok() {
		return syscall.EINVAL
	}
	.mux.Lock()
	.readDeadline = simpleDeadline()
	.mux.Unlock()
	return nil
}

func ( *PacketConn) ( time.Time) error {
	if !.ok() {
		return syscall.EINVAL
	}
	.mux.Lock()
	.writeDeadline = simpleDeadline()
	.mux.Unlock()
	return nil
}

type NetAddr struct {
	Net     string
	Address string
}

func ( NetAddr) () string { return .Net }
func ( NetAddr) () string  { return .Address }

// ResolveUDPAddr returns an address of UDP end point.
func ( *Net) (,  string) (*net.UDPAddr, error) {
	 := &net.UDPAddr{
		Port: 0,
		IP:   net.IPv4(127, 0, 0, 1),
	}
	, ,  := net.SplitHostPort()
	if  != nil {
		return nil, 
	}
	if .IP = net.ParseIP(); .IP == nil {
		// Probably we should use virtual DNS here.
		return nil, errors.New("bad IP")
	}
	if .Port,  = strconv.Atoi();  != nil {
		return nil, 
	}
	return , nil
}

// ListenPacket announces on the local network address.
func ( *Net) (,  string) (net.PacketConn, error) {
	if  != "udp4" &&  != "udp" &&  != "udp6" {
		return nil, errors.New("bad net")
	}
	,  := .ResolveUDPAddr(, )
	if  != nil {
		return nil, 
	}
	 := &PacketConn{
		net:     ,
		addr:    ,
		packets: make(chan packet, 10),
	}
	.peers[addrKey()] = 
	return , nil
}

// NAT implements facility for Network Address Translation simulation.
//
// Basic example:
// 	[ A ] <-----> [ NAT1 ] <-----> [ NAT2 ] <-----> [ B ]
//      IPa              IPa'     IPb'             IPb
//
// 	1) A sends packet P with dst = IPb'
//  2) NAT1 receives packet P and changes it's src to IPa',
//   sending it to NAT2 from IPa'.
//  3) NAT2 receives packet P from IPa' to IPb', does a lookup to
//   NAT translation table and finds association IPb' <-> IPb.
//   Then it sends packet P to B.
//  4) B receives packet P from NAT2, observing that it has src = IPa'.
//
//  Now B can repeat steps 1-4 and send packet back.
//
//  IPa  = 10.5.0.1:30000
//  IPa' = 83.30.100.1:23100
//  IPb' = 91.10.100.1:13000
//  IPb  = 10.1.0.1:20000
type NAT struct {
	// TODO(ar): implement
}