/**
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2018, Ericsson Canada
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of Ericsson Canada nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Version 0.1: Jon Maloy, 2018
 *
 * ------------------------------------------------------------------------
 */
package com.tipcj;

import java.nio.BufferOverflowException;
/**
 * @author Hoang Le (hoang.h.le@dektech.com.au)
 *
 */
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;

public class TipcConn {

	/**
	 * JNI instance
	 */
	static {
		tipcj = TipcBaseApi.getInstance();
	}

	public TipcConn(ByteBuffer buffer, SocketType sk_type) {
		poolBuffer = buffer;
		this.Fd = Socket(sk_type);
		if (this.Fd > 0) {
			this.isOpen = true;
		}
	}

	public TipcConn(SocketType sk_type) {
		this.Fd = Socket(sk_type);
		if (this.Fd > 0) {
			this.isOpen = true;
		}
		/*
		 * initialize default byte buffer
		 */
		poolBuffer = ByteBuffer.allocateDirect(TipcConfig.TIPC_MAX_USER_MSG_SIZE);
	}

	/**
	 * Create AF_TIPC socket API
	 */
	public int Socket(SocketType sk_type) {
		return tipcj.tipc_socket(sk_type.val);
	}

	public int GetFd() {
		return this.Fd;
	}

	/**
	 * Bind a TIPC socket to service address/socket address
	 */
	public int Bind(int stype, int lower, int upper, int scope) {
		return tipcj.tipc_bind(Fd, stype, lower, upper, scope);
	}

	/**
	 * Unbind a bound address
	 */
	public int Unbind(int stype, int lower, int upper) {
		return tipcj.tipc_unbind(Fd, stype, lower, upper);
	}

	/**
	 * Close a TIPC socket
	 */
	public int Close() {
		return tipcj.tipc_close(Fd);
	}

	/**
	 * Get a bound socket information
	 */
	public int Sockaddr(TipcAddr tipcAddr) {
		return tipcj.tipc_sockaddr(Fd, tipcAddr);
	}

	public TipcAddr Sockaddr() {
		TipcAddr tipcAddr = new TipcAddr(0,0,0);
		int ret = 0;

		ret = tipcj.tipc_sockaddr(Fd, tipcAddr);
		if (ret == 0) {
			return tipcAddr;
		}
		return null;
	}

	public int Sock_Non_Block() {
		return tipcj.tipc_sock_non_block(Fd);
	}

	/**
	 * Set messages sent from this socket as rejectable
	 */
	public int Sock_Rejectable() {
		return tipcj.tipc_sock_rejectable(Fd);
	}

	public int Connect(TipcAddr dst) {
		return tipcj.tipc_connect(Fd, dst);
	}

	public int Listen(int backlog) {
		return tipcj.tipc_listen(Fd, backlog);
	}

	public int Accept(TipcAddr src) {
		int ret = tipcj.tipc_accept(Fd, src);
		return ret;
	}

	/**
	 * Group communication API
	 */
	public int Join(TipcAddr member, boolean events, boolean loopback) {
		return tipcj.tipc_join(Fd, member, events, loopback);
	}

	public int Leave() {
		return tipcj.tipc_leave(Fd);
	}

	/** Messaging:
	 * - NULL pointer parameters are always accepted
	 * - tipc_sendto() to an accepting socket initiates two-way connect
	 * - If no err pointer given, tipc_recvfrom() returns 0 on rejected message
	 * - If (*err != 0) buf contains a potentially truncated rejected message
	 * - Group event: tipc_recvfrom() returns 0; err == 0/-1 indicates up/down
	 */
	public int Recvfrom(ByteBuffer buf, int len, TipcAddr socket, TipcAddr member, java.lang.Integer err) {
		return tipcj.tipc_recvfrom(Fd, buf, len, socket, member, err);
	}

	public int Recvfrom(byte[] rbuf, TipcAddr socket, TipcAddr member, java.lang.Integer err) {
		int ret = 0;
		ByteBuffer rbytebuf = ByteBuffer.allocateDirect(rbuf.length);
		rbytebuf.clear();

		ret = Recvfrom(rbytebuf, rbytebuf.remaining(), socket, member, err);
		if (ret > 0) {
			rbytebuf.get(rbuf, 0, ret);
		}
		rbytebuf.clear();
		return ret;
	}

	public int Recv(ByteBuffer buf, int len, boolean waitall) {
		return tipcj.tipc_recv(Fd, buf, len, waitall);
	}

	public int Recv(byte[] rbuf, boolean waitall) {
		int ret = 0;
		int len = rbuf.length;
		ByteBuffer rbytebuf = ByteBuffer.allocateDirect(len);
		rbytebuf.clear();

		ret = tipcj.tipc_recv(Fd, rbytebuf, len, waitall);
		if (ret > 0) {
			rbytebuf.get(rbuf, 0, ret);
		}
		rbytebuf.clear();
		return ret;
	}

	public static int Recv(int sd, byte[] rbuf, boolean waitall) {
		int ret = 0;
		int len = rbuf.length;
		ByteBuffer rbytebuf = ByteBuffer.allocateDirect(len);
		rbytebuf.clear();

		ret = tipcj.tipc_recv(sd, rbytebuf, len, waitall);
		if (ret > 0) {
			rbytebuf.get(rbuf, 0, ret);
		}
		rbytebuf.clear();
		return ret;
	}

	public int Sendmsg(byte[] msg) {
		int len = msg.length;
		poolBuffer.clear();

		if (len > poolBuffer.remaining()) {
			len = poolBuffer.remaining();
		}

		try {
			poolBuffer.put(msg, 0, len);
		} catch (BufferOverflowException ex) {
			return -1;
		} catch (ReadOnlyBufferException ex) {
			return -1;
		}

		return tipcj.tipc_send(Fd, poolBuffer, len);
	}

	public int Sendto(byte[] msg, TipcAddr dst) {
		int len = msg.length;
		poolBuffer.clear();

		if (len > poolBuffer.remaining()) {
			len = poolBuffer.remaining();
		}

		try {
			poolBuffer.put(msg, 0, len);
		} catch (BufferOverflowException ex) {
			return -1;
		} catch (ReadOnlyBufferException ex) {
			return -1;
		}

		return tipcj.tipc_sendto(Fd, poolBuffer, len, dst);
	}

	public int Sendto(ByteBuffer buf, int len, TipcAddr dst) {
		return tipcj.tipc_sendto(Fd, buf, len, dst);
	}

	public int Sendmcast(byte[] msg, TipcAddr dst) {
		int len = msg.length;
		poolBuffer.clear();

		if (len > poolBuffer.remaining()) {
			len = poolBuffer.remaining();
		}

		try {
			poolBuffer.put(msg, 0, len);
		} catch (BufferOverflowException ex) {
			return -1;
		} catch (ReadOnlyBufferException ex) {
			return -1;
		}

		return tipcj.tipc_mcast(Fd, poolBuffer, len, dst);
	}

	public int Send(byte[] msg) {
		int len = msg.length;
		poolBuffer.clear();

		if (len > poolBuffer.remaining()) {
			len = poolBuffer.remaining();
		}

		try {
			poolBuffer.put(msg, 0, len);
		} catch (BufferOverflowException ex) {
			return -1;
		} catch (ReadOnlyBufferException ex) {
			return -1;
		}

		return tipcj.tipc_send(Fd, poolBuffer, len);
	}

	public static int Send(int sd, byte[] msg) {
		int len = msg.length;
		ByteBuffer rbytebuf = ByteBuffer.allocateDirect(len);
		rbytebuf.clear();

		try {
			rbytebuf.put(msg, 0, len);
		} catch (BufferOverflowException ex) {
			return -1;
		} catch (ReadOnlyBufferException ex) {
			return -1;
		}

		return tipcj.tipc_send(sd, rbytebuf, len);
	}

	/**
	 * Close a TIPC socket
	 */
	public static int Close(int fd) {
		return tipcj.tipc_close(fd);
	}

	private static TipcBaseApi tipcj;
	protected int Fd = 0;
	protected boolean isOpen = false;
	private ByteBuffer poolBuffer;
}
