module net

// Addr represents an ip address
pub struct Addr {
	addr C.sockaddr
	len  int
pub:
	saddr string
	port  int
}

struct C.addrinfo {
}

pub fn (a Addr) str() string {
	return '$a.saddr:$a.port'
}

const (
	max_ipv4_addr_len = 24
	ipv4_addr_size    = sizeof(C.sockaddr_in)
)

fn new_addr(addr C.sockaddr) ?Addr {
	addr_len := if addr.sa_family == int(SocketFamily.inet) {
		sizeof(C.sockaddr)
	} else {
		// TODO NOOOOOOOOOOOO
		0
	}
	// Convert to string representation
	buf := []byte{len: net.max_ipv4_addr_len, init: 0}
	$if windows {
		res := C.WSAAddressToStringA(&addr, addr_len, C.NULL, buf.data, unsafe { &u32(&buf.len) })
		if res != 0 {
			socket_error(-1) ?
		}
	} $else {
		res := &char(C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len))
		if res == 0 {
			socket_error(-1) ?
		}
	}
	mut saddr := buf.bytestr()

	hport := unsafe { &C.sockaddr_in(&addr) }.sin_port
	port := C.ntohs(hport)

	$if windows {
		// strip the port from the address string
		saddr = saddr.split(':')[0]
	}

	return Addr{addr, int(addr_len), saddr, port}
}

pub fn resolve_addr(addr string, family SocketFamily, typ SocketType) ?Addr {
	address, port := split_address(addr) ?

	mut hints := C.addrinfo{}
	hints.ai_family = int(family)
	hints.ai_socktype = int(typ)
	hints.ai_flags = C.AI_PASSIVE
	hints.ai_protocol = 0
	hints.ai_addrlen = 0
	hints.ai_canonname = C.NULL
	hints.ai_addr = C.NULL
	hints.ai_next = C.NULL
	info := &C.addrinfo(0)

	sport := '$port'

	// This might look silly but is recommended by MSDN
	$if windows {
		socket_error(0 - C.getaddrinfo(&char(address.str), &char(sport.str), &hints,
			&info)) ?
	} $else {
		x := C.getaddrinfo(&char(address.str), &char(sport.str), &hints, &info)
		wrap_error(x) ?
	}

	return new_addr(*info.ai_addr)
}