/**
 * Created on 30 Oct. 2009
 * Created by Takaki Tsue, Masato Kawahashi
 * Copyright (C) 2009 HIMACS,Ltd. 
 * Licensed under the Apache License, Version 2.0
 *
 */
/**
 * To avoid the follwing bug
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6230761
 */
package jp.himacs.avoidbugs.ipv6.bug6230761;

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.util.Set;

/**
 * @author t-tsue
 * to avoid the follwing bug
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6230761
 */
public class WinsockIPv6 {
	public static final int SOCK_STREAM = 1;
	public static final int SOCK_DGRAM = 2;
	public static final int IPPROTO_TCP = 6;
	public static final int IPPROTO_UDP = 17;

	static {
		  System.loadLibrary("bug6230761");
	}
	
	/**
	 * create sokcet for only IPv6
	 * @param type SOCK_STREAM or SOCK_DGRAM
	 * @param protocol IPPROTO_TCP or IPPROTO_UDP
	 * @return new socket 
	 */
	public static native int createSocket(int type, int protocol);

	/**
	 * bind a socket. 
	 * @param fd file descripter
	 * @param port port number
	 * @param host IP address
	 * @return result.
	 **/
	//public static native int bind(int fd, int port, byte[] host);

	/**
	 * connect a socket. 
	 * @param fd file descripter
	 * @param port port number
	 * @param host IP address
	 * @return result.
	 **/
	public static native int connect(int fd, int port, byte[] host);

	/**
	 *  
	 * @param fd file descripter
	 * @param port port number
	 * @param host IP address
	 * @return result.
	 **/
	public static native int sendto(int fd, int port, byte[] host, int scope_id, byte[] data);

	/**
	 * to call select(2).
	 * @param fd array of filedescriptor
	 * @param opts array of option
	 * @param timeout timeout
	 * @return result of select.
	 */
	public static native int select(int[] fd, int[] opts, int timeout);

	/**
	 * to call native select.
	 * @param keys key set
	 * @param timeout timeout
	 * @return result of select.
	 */
	public static int select(Set<SelectionKey>keys, int timeout) {
		int result = 0;
		int [] fds  = new int[keys.size()];
		int [] opts = new int[keys.size()];
		SelectionKeyIPv6[] keyarr = (SelectionKeyIPv6[])keys.toArray(new SelectionKeyIPv6[0]);
		for(int i = 0; i < fds.length; i++) {
			// Now, Only DatagramChannel is available !
			DatagramChannelImplIPv6 channel = (DatagramChannelImplIPv6)keyarr[i].channel();
			try {
				fds[i] = getFDVal(channel.getFd());
			} catch (Exception e) {
				IPv6Logger.IPV6.severe("select"+ e);
			}
			opts[i] = keyarr[i].interestOps();
		}
		result = select(fds,opts,timeout);
		for(int i = 0; i < keyarr.length; i++) {
			keyarr[i].readyOpts=opts[i];
		}
		return result;
	}
	/**
	 * to call closesocket.
	 * @return result of closesocket.
	 */
	public static native int closesocket(int fd);

	/**
	 * to call closesocket.
	 * @return result of closesocket.
	 */
	public static void close(FileDescriptor fd) {
		try {
			closesocket(getFDVal(fd));
		} catch (Exception e) {
			IPv6Logger.IPV6.severe("close"+e);
		}
	}
	/**
	 * to call native setsockopt.
	 * @return result of setsockopt.
	 */
	public static native int setsockopt(int fd, int level, int opt, int val);

	/**
	 * to call native setsockopt.
	 * @param fd file descriptor
	 * @param level SOL_SOCKET,...
	 * @param opt SO_REUSEADDR,...
	 * @param val option value
	 * @return result of setsockopt
	 */
	public static int setsockopt(FileDescriptor fd, int level, int opt, int val) {
		int result = -1;
		try {
			result = setsockopt(getFDVal(fd), level, opt, val);
		} catch (Exception e) {
			IPv6Logger.IPV6.severe("setsockopt"+e);
		}
		return result;
	}
	/**
	 * to call native getsockopt.
	 * @return result of getsockopt.
	 */
	public static native int getsockopt(int fd, int level, int opt);

	/**
	 * to call native getsockopt
	 * @param fd file descriptor
	 * @param level SOL_SOCKET,...
	 * @param opt SO_REUSEADDR,...
	 * @return result of getsockopt
	 */
	public static int getsockopt(FileDescriptor fd,int level, int opt ) {
		int result = -1;
		try {
			result = getsockopt(getFDVal(fd),level, opt);
		} catch (Exception e) {
			IPv6Logger.IPV6.severe("getsockopt"+e);
		}
		return result;
	}
	/**
	 * blocking mode on/off.
	 * @param fd filedescriptor.
	 * @param opt 0 or 1 corresponding false or true
	 * @return result
	 */
	public static native int blocking(int fd, int opt);
	
	/**
	 * 
	 * @param fd
	 * @param opt
	 * @return
	 */
	public static int blocking(FileDescriptor fd, int opt) {
		int result = -1;
		try {
			result = blocking(getFDVal(fd), opt);
		} catch (Exception e) {
			IPv6Logger.IPV6.severe("blocking"+e);
		}
		return result;
	}
	/**
	 * 
	 * @param fd
	 * @param addr
	 * @param dataArr
	 * @return
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 */
	public  static int sendto(FileDescriptor fd,InetSocketAddress addr, byte[] dataArr) throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
		int port = addr.getPort();
		if (dataArr!=null) {
			IPv6Logger.IPV6.info("sendto:"+addr+",size:"+dataArr.length);
		} else {
			IPv6Logger.IPV6.info("sendto:"+addr+",data is null");
		}
		Inet6Address i6addr = (Inet6Address)addr.getAddress();
		byte[] addrArr = i6addr.getAddress();
		return sendto(getFDVal(fd),port,addrArr, i6addr.getScopeId(),dataArr);
	}
	/**
	 * to call native recvfrom(2). 
	 * @param fd file descripter
	 * @param port port number
	 * @param host IP address
	 * @return result.
	 **/
	public static native int recvfrom(int fd, IPv6AddressBean i6addr, byte[] data);
	
	/**
	 * to call native recvfrom(2). 
	 * @param fd file descripter
	 * @param port port number
	 * @param host IP address
	 * @return result.
	 * @throws IOException 
	 **/
	public  static DatagramPacket recvfrom(FileDescriptor fd, byte[] data) throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException, IOException {
		DatagramPacket packet = null;
		int fdVal = getFDVal(fd);
		IPv6AddressBean i6bean = new IPv6AddressBean();
		int result =  recvfrom(fdVal,i6bean, data);
		byte[] buf = new byte[0];
		if (result > 0) {
			buf = new byte[result];
			System.arraycopy(data, 0, buf, 0, buf.length);
		}		packet = new DatagramPacket(buf,buf.length);
		InetSocketAddress addr = i6bean.getSocketAddress();
		IPv6Logger.IPV6.info("recvfrom:"+addr+",size:"+result);
		packet.setSocketAddress(addr);
		if (result<1) {
			throw new IOException("recvfrom has an error:remote-port:["+addr.getPort()+"],remote-address:["+addr.getAddress().getHostAddress()+"]");
		}
		return packet;
	}

	/**
	 * to call native bind.
	 * @param fd file descriptor
	 * @param port port number
	 * @param host ip address
	 * @param scope_id scope_id
	 * @return result of bind
	 */
	public static native int bind(int fd, int port, byte[] host, int scope_id);

	/**
	 * to call native bind.
	 * @param fd file descriptor
	 * @param addr ip address
	 * @return result of bind
	 * @throws SecurityException unexpected
	 * @throws IllegalArgumentException unexpected
	 * @throws NoSuchFieldException unexpected
	 * @throws IllegalAccessException unexpected
	 */
	public static int bind(FileDescriptor fd, InetSocketAddress addr) throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
		Inet6Address i6addr = (Inet6Address)addr.getAddress();
		int fdVal = getFDVal(fd);
		int port = addr.getPort();
		byte[] hostArr = i6addr.getAddress();
		int scopeId = i6addr.getScopeId();
		return bind(fdVal,port,hostArr,scopeId);
	}
	/**
	 * create new FileDescriptor of socket
	 * @param type SOCK_STREAM or SOCK_DGRAM
	 * @param protocol SOCK_STREAM or SOCK_DGRAM
	 * @return new FileDescriptor
	 * @throws SecurityException unexpected
	 * @throws NoSuchMethodException unexpected
	 * @throws IllegalArgumentException unexpected
	 * @throws IllegalAccessException unexpected
	 * @throws InvocationTargetException unexpected
	 * @throws NoSuchFieldException 
	 */
	public static FileDescriptor newFD(int type, int protocol) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
		int socket = createSocket(type,protocol);
		Method method = FileDescriptor.class.getDeclaredMethod("standardStream", int.class);
		method.setAccessible(true);
		FileDescriptor fd = (FileDescriptor) method.invoke(null, socket);
		Field field = fd.getClass().getDeclaredField("fd");
		field.setAccessible(true);
		field.setInt(fd, socket);
		return fd;
	}
	/**
	 * get fdval from FileDescriptor.
	 * @param fd FileDescriptor
	 * @return fdval
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public static int getFDVal(FileDescriptor fd) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
		Field field = fd.getClass().getDeclaredField("fd");
		field.setAccessible(true);
		int fdval = (int)field.getInt(fd);
		return fdval;
	}
}
