package hh.dejsem.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;

import hh.dejsem.K;
import hh.lib.D;

/**
 * SSL blocking network IO (SSL socket)
 */
public class NetSslScIO implements NetIO, GetPut {
	D d;
	SSLServerSocket sslssc;
	SSLSocket sslsc = null;
	Socket sc = null;
	InputStream in = null;
	public OutputStream out = null;
	ReadableByteChannel ic;
	WritableByteChannel oc;
	// ----------------------------------------------------------

	public NetSslScIO(D d) {
		this.d = d.klon(this);
	}

	public void bind(String host, int port) throws Exception {
		sslssc = (SSLServerSocket) K.netNode.ssf.createServerSocket(port);
	}

	public boolean isBound() { return sslssc != null; }

	public void accept() throws Exception {
		sslssc.setSoTimeout(K.acceptTO);
		sslsc = (SSLSocket)sslssc.accept();
		getChannels();
	}

	public void connect(InetSocketAddress hostIp) throws Exception {
		sslsc = (SSLSocket) K.netNode.sf.createSocket();
		sslsc.setSoTimeout(K.socketTO);
		sslsc.connect(hostIp);
		sslsc.startHandshake();
		getChannels();
	}

	public void getChannels() throws Exception {
		in = sslsc.getInputStream();
		ic = Channels.newChannel(in);
		out = sslsc.getOutputStream();
		oc = Channels.newChannel(out);
	}

	public boolean isConnected() { return sslsc != null && sslsc.isConnected() && !sslsc.isClosed(); }

	public boolean get(ByteBuffer buf) throws Exception {		// false on EOD
		try {
			if(d.ll(5)) d.l(String.format("get from net, buf=%s ...", D.bufStat(buf)));
			while(buf.hasRemaining()) { if(ic.read(buf) < 0) break; }
			if(d.ll(5)) d.l(String.format("%d bytes read", buf.position()));
			return buf.position() > 0;
		}
		catch(Exception x) {
			closeSc();
			throw new Exception(String.format("%s[get]: net channel read", d.logPrefix), x);
		}
	}

	public int put(ByteBuffer buf) throws Exception {
		int n = 0;
		if(d.ll(5)) d.l("output to net, buf=" + D.bufStat(buf));
		while(buf.hasRemaining()) {
			n = 0;
			try { n = oc.write(buf); }
			catch(Exception x) {
				closeSc();
				throw new Exception("net channel write", x); }
			if(d.ll(5)) d.l("" + n + " bytes written"); }
		n = buf.position();
		buf.clear();
		return n;
	}

	public void closeSc() throws Exception { if(sslsc != null) { sslsc.close(); } }

	public void closeSsc() throws Exception {
		if(sslssc != null) {
			sslssc.close();
			sslssc = null; }
	}
}
