package tipc

/*
#cgo CFLAGS: -I../include
#cgo LDFLAGS: -L../libtipc -ltipc

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/socket.h>
#include <linux/tipc.h>
#include "tipcc.h"
*/
import "C"

import (
	"syscall"
	"unsafe"
)

const (
        TIPC_MAX_USER_MSG_SIZE = 66000
)

const (
        TIPC_LOW_IMPORTANCE            = 0
        TIPC_MEDIUM_IMPORTANCE         = 1
        TIPC_HIGH_IMPORTANCE           = 2
        TIPC_CRITICAL_IMPORTANCE       = 3
)

const (
        TIPC_WAIT_FOREVER      = 0
)

type TipcAddr struct {
	Type uint32
	Instance uint32
	Node uint32
}

// Tipc Object Connection
type TipcConn struct {
	Fd int
	isOpen bool
}

// Topology Object Connection
type TipcTopSrvConn struct {
	Fd int
	isConnected bool
}

/* OOP implement for Tipc/Topology connection */
func (conn *TipcConn) GetFd() int {
	return conn.Fd
}

// Create AF_TIPC socket API
func (conn *TipcConn) socket(sk_type uint32) (int, error) {
	tmp, err := C.tipc_socket(C.int(sk_type))
	conn.Fd = int(tmp)
	return conn.Fd, err
}

// Bind a TIPC socket
func (conn *TipcConn) Bind(stype uint32, lower uint32, upper uint32, node uint32) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_bind(C.int(conn.Fd),C.uint32_t(stype), C.uint32_t(lower), C.uint32_t(upper), C.uint32_t(node)))
}

// Unbind a bound address
func (conn *TipcConn) Unbind(stype uint32, lower uint32, upper uint32) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_unbind(C.int(conn.Fd),C.uint32_t(stype), C.uint32_t(lower), C.uint32_t(upper)))
}

// Close a TIPC socket
func (conn *TipcConn) Close() {
	if conn.isOpen == false {
		return
	}
	C.tipc_close(C.int(conn.Fd))
}

// Get a bound socket information
func (conn *TipcConn) Sockaddr(sockid *TipcAddr) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sockaddr(C.int(conn.Fd), (*C.struct_tipc_addr)(unsafe.Pointer(sockid))))
}

// Set a TIPC socket to non-block
func (conn *TipcConn) Sock_Non_Block() (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sock_non_block(C.int(conn.Fd)))
}

// Set messages sent from this socket as rejectable
func (conn *TipcConn) Sock_Rejectable() (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sock_rejectable(C.int(conn.Fd)))
}

// Set priority from this socket
func (conn *TipcConn) Sock_Priority(pri uint32) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sock_importance(C.int(conn.Fd), C.uint32_t(pri)))
}

// Establish connection to a TIPC socket address (service address)
func (conn *TipcConn) Connect(dst *TipcAddr) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_connect(C.int(conn.Fd), (*C.struct_tipc_addr)(unsafe.Pointer(dst))))
}

func (conn *TipcConn) Accept(src *TipcAddr) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_accept(C.int(conn.Fd), (*C.struct_tipc_addr)(unsafe.Pointer(src))))
}

func (conn *TipcConn) Listen(backlog int) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_listen(C.int(conn.Fd), C.int(backlog)))
}

// Messaging
func (conn *TipcConn) Recvfrom(buf []byte, src *TipcAddr, dst *TipcAddr, err *int) (int) {
	if buf == nil {
		return -1
	}
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_recvfrom(C.int(conn.Fd),
		unsafe.Pointer(&buf[0]),
		C.size_t(len(buf)),
		(*C.struct_tipc_addr)(unsafe.Pointer(src)),
		(*C.struct_tipc_addr)(unsafe.Pointer(dst)),
		(*C.int)(unsafe.Pointer(err))))
}

func (conn *TipcConn) Recv(buf []byte, waitall bool) (int) {
	if buf == nil {
		return -1
	}
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_recv(C.int(conn.Fd),
		unsafe.Pointer(&buf[0]),
		C.size_t(len(buf)),
		C.bool(waitall)))
}

func (conn *TipcConn) Sendmsg(msg *syscall.Msghdr) (int) {
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sendmsg(C.int(conn.Fd), (*C.struct_msghdr)(unsafe.Pointer(msg))))
}

func (conn *TipcConn) Sendto(buf []byte, dst *TipcAddr) (int) {
	if buf == nil {
		return -1
	}
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_sendto(C.int(conn.Fd), unsafe.Pointer(&buf[0]), C.size_t(len(buf)), (*C.struct_tipc_addr)(unsafe.Pointer(dst))))
}

func (conn *TipcConn) Sendmcast(buf []byte, dst *TipcAddr) (int) {
	if buf == nil {
		return -1
	}
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_mcast(C.int(conn.Fd), unsafe.Pointer(&buf[0]), C.size_t(len(buf)), (*C.struct_tipc_addr)(unsafe.Pointer(dst))))
}

func (conn *TipcConn) Send(buf []byte) (int) {
	if buf == nil {
		return -1
	}
	if conn.isOpen == false {
		return -1
	}
	return int(C.tipc_send(C.int(conn.Fd), unsafe.Pointer(&buf[0]), C.size_t(len(buf))))
}


// Topology Server
func (conn *TipcTopSrvConn) Srv_subscr(stype, lower, upper uint32, all bool, expire int) (int) {
	return int(C.tipc_srv_subscr(C.int(conn.Fd), C.uint32_t(stype), C.uint32_t(lower), C.uint32_t(upper), C.bool(all), C.int(expire)))
}

func (conn *TipcTopSrvConn) GetFd() int {
	if conn.isConnected == false {
		return -1
	}
	return conn.Fd
}

func (conn *TipcTopSrvConn) Close() {
	if conn.isConnected == false {
		return
	}
	C.tipc_close(C.int(conn.Fd))
}

func (conn *TipcTopSrvConn) connect(topsrv_node uint32) (int) {
	conn.Fd = int(C.tipc_topsrv_conn(C.uint32_t(topsrv_node)))
	return conn.Fd
}

func (conn *TipcTopSrvConn) Srv_evt(srv *TipcAddr, id *TipcAddr, up *bool, expired *bool) (int) {
	if conn.isConnected == false {
		return -1
	}
	return int(C.tipc_srv_evt(C.int(conn.Fd), (*C.struct_tipc_addr)(unsafe.Pointer(srv)), (*C.struct_tipc_addr)(unsafe.Pointer(id)), (*C.bool)(unsafe.Pointer(up)), (*C.bool)(unsafe.Pointer(expired))))
}

func (conn *TipcTopSrvConn) Neigh_evt(neigh_node *uint32, up *bool) (int) {
	if conn.isConnected == false {
		return -1
	}
	return int(C.tipc_neigh_evt(C.int(conn.Fd), (*C.uint32_t)(unsafe.Pointer(neigh_node)), (*C.bool)(unsafe.Pointer(up))))
}

func (conn *TipcTopSrvConn) Link_evt(neigh_node *uint32, up *bool, local_bearerid *int, remote_bearerid *int) (int) {
	if conn.isConnected == false {
		return -1
	}
	return int(C.tipc_link_evt(C.int(conn.Fd), (*C.uint32_t)(unsafe.Pointer(neigh_node)), (*C.bool)(unsafe.Pointer(up)), (*C.int)(unsafe.Pointer(local_bearerid)), (*C.int)(unsafe.Pointer(remote_bearerid))))
}
/* End OOP implement */

/* Some helpers function */
func Tipc_own_node() (uint32) {
	return uint32(C.tipc_own_node())
}

func Srv_wait(srv *TipcAddr, expire int) (bool) {
	return bool(C.tipc_srv_wait((*C.struct_tipc_addr)(unsafe.Pointer(srv)), C.int(expire)))
}

func Neigh_subscr(topsrv_node uint32) (int) {
	return int(C.tipc_neigh_subscr(C.uint32_t(topsrv_node)))
}

func Link_subscr(topsrv_node uint32) (int) {
	return int(C.tipc_link_subscr(C.uint32_t(topsrv_node)))
}

func Tipc_ntoa(sockid *TipcAddr, buf []byte) string {
	var str *C.char
	if buf == nil {
		return ""
	}
	str = C.tipc_ntoa((*C.struct_tipc_addr)(unsafe.Pointer(sockid)), (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
	return C.GoString(str)
}

func Tipc_rtoa(stype uint32, lower uint32, upper uint32, node uint32, buf []byte) string {
	var str *C.char
	if buf == nil {
		return ""
	}
	str = C.tipc_rtoa(C.uint32_t(stype), C.uint32_t(lower), C.uint32_t(upper), C.uint32_t(node), (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
	return C.GoString(str)
}

func Tipc_linkname(buf []byte, peer uint32, bearerid int) (string) {
	return C.GoString(C.tipc_linkname((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)), C.uint32_t(peer), C.int(bearerid)))
}

// Constructor to create TipcConn
func NewConn(sk_type uint32) (*TipcConn, error) {
	conn := &TipcConn{}
	_, err := conn.socket(sk_type)
	if err != nil {
		return nil, err
	}
	conn.isOpen = true
	return conn, nil
}

// Constructor to create TipcConn from file descriptor
func NewConnFromFd(fd int) (*TipcConn) {
	return &TipcConn{fd, true}
}

// Constructor to create TipcTopSrvConn
func NewTopSrvConn(topsrv_node uint32) (*TipcTopSrvConn) {
	conn := &TipcTopSrvConn{}
	err := conn.connect(topsrv_node)
	if err <= 0 {
		return nil
	}
	conn.isConnected = true
	return conn
}
