module net

import os

pub struct Socket {
pub:
	sockfd int
	family int
	_type  int
	proto  int
}

struct C.in_addr {
mut:
	s_addr int
}

struct C.sockaddr_in {
mut:
	sin_family int
	sin_port   int
	sin_addr   C.in_addr
}

struct C.addrinfo {
mut:
	ai_family    int
	ai_socktype  int
	ai_flags     int
	ai_protocol  int
	ai_addrlen   int
	ai_addr      voidptr
	ai_canonname voidptr
	ai_next      voidptr
}

struct C.sockaddr_storage {
}

fn C.socket() int


fn C.setsockopt() int


fn C.htonl() int


fn C.htons() int


fn C.bind() int


fn C.listen() int


fn C.accept() int


fn C.getaddrinfo() int


fn C.connect() int


fn C.send() int


fn C.recv() int


fn C.read() int


fn C.shutdown() int


fn C.close() int


fn C.ntohs() int


fn C.getsockname() int
// create socket
pub fn new_socket(family int, _type int, proto int) ?Socket {
	sockfd := C.socket(family, _type, proto)
	one := 1
	// This is needed so that there are no problems with reusing the
	// same port after the application exits.
	C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(int))
	if sockfd == 0 {
		return error('net.socket: failed')
	}
	s := Socket{
		sockfd: sockfd
		family: family
		_type: _type
		proto: proto
	}
	return s
}

pub fn socket_udp() ?Socket {
	return new_socket(C.AF_INET, C.SOCK_DGRAM, C.IPPROTO_UDP)
}

// set socket options
pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
	res := C.setsockopt(s.sockfd, level, optname, optvalue, sizeof(&int))
	if res < 0 {
		return error('net.setsocketopt: failed with $res')
	}
	return res
}

// bind socket to port
pub fn (s Socket) bind(port int) ?int {
	mut addr := C.sockaddr_in{
	}
	addr.sin_family = s.family
	addr.sin_port = C.htons(port)
	addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
	size := 16 // sizeof(C.sockaddr_in)
	res := C.bind(s.sockfd, &addr, size)
	if res < 0 {
		return error('net.bind: failed with $res')
	}
	return res
}

// put socket into passive mode and wait to receive
pub fn (s Socket) listen() ?int {
	backlog := 128
	res := C.listen(s.sockfd, backlog)
	if res < 0 {
		return error('net.listen: failed with $res')
	}
	$if debug {
		println('listen res = $res')
	}
	return res
}

// put socket into passive mode with user specified backlog and wait to receive
pub fn (s Socket) listen_backlog(backlog int) ?int {
	mut n := 0
	if backlog > 0 {
		n = backlog
	}
	res := C.listen(s.sockfd, n)
	if res < 0 {
		return error('net.listen_backlog: failed with $res')
	}
	return res
}

// helper method to create, bind, and listen given port number
pub fn listen(port int) ?Socket {
	$if debug {
		println('net.listen($port)')
	}
	s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) or {
		return error(err)
	}
	_ = s.bind(port) or {
		return error(err)
	}
	_ = s.listen() or {
		return error(err)
	}
	return s
}

// accept first connection request from socket queue
pub fn (s Socket) accept() ?Socket {
	$if debug {
		println('accept()')
	}
	addr := C.sockaddr_storage{
	}
	size := 128 // sizeof(sockaddr_storage)
	sockfd := C.accept(s.sockfd, &addr, &size)
	if sockfd < 0 {
		return error('net.accept: failed with $sockfd')
	}
	c := Socket{
		sockfd: sockfd
		family: s.family
		_type: s._type
		proto: s.proto
	}
	return c
}

// connect to given addrress and port
pub fn (s Socket) connect(address string, port int) ?int {
	mut hints := C.addrinfo{
	}
	hints.ai_family = s.family
	hints.ai_socktype = s._type
	hints.ai_flags = C.AI_PASSIVE
	hints.ai_protocol = s.proto
	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'
	info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
	if info_res != 0 {
		error_message := os.get_error_msg(net.error_code())
		return error('net.connect: getaddrinfo failed "$error_message"')
	}
	res := C.connect(s.sockfd, info.ai_addr, info.ai_addrlen)
	if res < 0 {
		error_message := os.get_error_msg(net.error_code())
		return error('net.connect: connect failed "$error_message"')
	}
	return res
}

// helper method to create socket and connect
pub fn dial(address string, port int) ?Socket {
	s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) or {
		return error(err)
	}
	_ = s.connect(address, port) or {
		return error(err)
	}
	return s
}

// send data to socket (when you have a memory buffer)
pub fn (s Socket) send(buf byteptr, len int) ?int {
	mut dptr := buf
	mut dlen := len
	for {
		sbytes := C.send(s.sockfd, dptr, dlen, MSG_NOSIGNAL)
		if sbytes < 0 {
			return error('net.send: failed with $sbytes')
		}
		dlen -= sbytes
		if dlen <= 0 {
			break
		}
		dptr += sbytes
	}
	return len
}

// send string data to socket (when you have a v string)
pub fn (s Socket) send_string(sdata string) ?int {
	return s.send(sdata.str, sdata.len)
}

// receive string data from socket. NB: you are responsible for freeing the returned byteptr
pub fn (s Socket) recv(bufsize int) (byteptr,int) {
	mut buf := byteptr(0)
	unsafe { buf = malloc(bufsize) }
	res := C.recv(s.sockfd, buf, bufsize, 0)
	return buf,res
}

// TODO: remove cread/2 and crecv/2 when the Go net interface is done
pub fn (s Socket) cread(buffer byteptr, buffersize int) int {
	return C.read(s.sockfd, buffer, buffersize)
}

// Receive a message from the socket, and place it in a preallocated buffer buf,
// with maximum message size bufsize. Returns the length of the received message.
pub fn (s Socket) crecv(buffer byteptr, buffersize int) int {
	return C.recv(s.sockfd, buffer, buffersize, 0)
}

// shutdown and close socket
pub fn (s Socket) close() ?int {
	mut shutdown_res := 0
	$if windows {
		shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
	} $else {
		shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
	}
	// TODO: should shutdown throw an error? close will
	// continue even if shutdown failed
	// if shutdown_res < 0 {
	// return error('net.close: shutdown failed with $shutdown_res')
	// }
	mut res := 0
	$if windows {
		res = C.closesocket(s.sockfd)
	} $else {
		res = C.close(s.sockfd)
	}
	if res < 0 {
		return error('net.close: failed with $res')
	}
	return 0
}

pub const (
	CRLF = '\r\n'
	MAX_READ = 400
	MSG_PEEK = 0x02
)
// write - write a string with CRLF after it over the socket s
pub fn (s Socket) write(str string) ?int {
	line := '$str$CRLF'
	res := C.send(s.sockfd, line.str, line.len, MSG_NOSIGNAL)
	if res < 0 {
		return error('net.write: failed with $res')
	}
	return res
}

// read_line - retrieves a line from the socket s (i.e. a string ended with \n)
pub fn (s Socket) read_line() string {
	mut buf := [MAX_READ]byte // where C.recv will store the network data
	mut res := '' // The final result, including the ending \n.
	for {
		mut line := '' // The current line. Can be a partial without \n in it.
		n := C.recv(s.sockfd, buf, MAX_READ - 1, MSG_PEEK)
		if n == -1 {
			return res
		}
		if n == 0 {
			return res
		}
		buf[n] = `\0`
		mut eol_idx := -1
		for i in 0..n {
			if int(buf[i]) == `\n` {
				eol_idx = i
				// Ensure that tos_clone(buf) later,
				// will return *only* the first line (including \n),
				// and ignore the rest
				buf[i + 1] = `\0`
				break
			}
		}
		line = tos_clone(buf)
		if eol_idx > 0 {
			// At this point, we are sure that recv returned valid data,
			// that contains *at least* one line.
			// Ensure that the block till the first \n (including it)
			// is removed from the socket's receive queue, so that it does
			// not get read again.
			C.recv(s.sockfd, buf, eol_idx + 1, 0)
			res += line
			break
		}
		// recv returned a buffer without \n in it .
		C.recv(s.sockfd, buf, n, 0)
		res += line
		res += CRLF
		break
	}
	return res
}

// TODO
pub fn (s Socket) read_all() string {
	mut buf := [MAX_READ]byte // where C.recv will store the network data
	mut res := '' // The final result, including the ending \n.
	for {
		n := C.recv(s.sockfd, buf, MAX_READ - 1, 0)
		if n == -1 {
			return res
		}
		if n == 0 {
			return res
		}
		res += tos_clone(buf)
	}
	return res
}

pub fn (s Socket) get_port() int {
	mut addr := C.sockaddr_in{
	}
	size := 16 // sizeof(sockaddr_in)
	C.getsockname(s.sockfd, &addr, &size)
	return C.ntohs(addr.sin_port)
}