package org.kne.cloud.network.klalb;

import java.io.IOException;
import java.io.StreamCorruptedException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicLong;

import org.kne.cloud.network.BufferedChannel;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ThreadTool;
import org.kne.io.KNEChannels;

public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
	private static final boolean debug = false;
	private volatile long timeoutTimer;
	private volatile boolean timerenabled=false;
	private boolean enableBuffer=true;
	private Runnable timeouter=new Runnable() {
		public void run() {
			timeoutTimer=System.nanoTime();
			while(connectSocket.isOpen()) {
				if(timerenabled&&(System.nanoTime()-timeoutTimer>sotimeout*1000000L)) {
					if(connectSocket!=null)
						try {
							connectSocket.close();
						} catch (IOException e) {
						}
				}
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	};
	@Override
	public String toString() {
		try {
			return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteAddress());
		} catch (Exception e) {
			e.printStackTrace();
			return "?←?";
		}
	}

	private SocketChannel connectSocket;
	private ScatteringByteChannel readableChannel;
	private GatheringByteChannel writableChannel;
	private int sotimeout=getDefaultSoTimeout();
	public StreamChannelKLALBPacketLink(SocketChannel connectSocket) throws IOException {
		this(connectSocket,true);
	}
	public StreamChannelKLALBPacketLink(SocketChannel connectSocket,boolean enableBuffer) throws IOException {
		try {
		this.connectSocket=connectSocket;
		if(enableBuffer) {
			BufferedChannel buf=new BufferedChannel(connectSocket,connectSocket,256*1024);
			this.readableChannel =buf;
			this.writableChannel =buf;
		}else {
			this.readableChannel=connectSocket;
			this.writableChannel=connectSocket;
		}
		this.enableBuffer=enableBuffer;
		connectSocket.setOption(StandardSocketOptions.TCP_NODELAY,true);
		ThreadTool.makeVDaemonThread("连接超时计时线程", timeouter);
		new KLALBOutputStream(Channels.newOutputStream(writableChannel));
		if(writableChannel instanceof BufferedChannel)
			((BufferedChannel) writableChannel).flush();
		new KLALBInputStream(Channels.newInputStream(readableChannel));
		}catch(Throwable e) {
			connectSocket.close();
			throw e;
		}
	}
	
	
	

	@Override
	public void close() throws IOException {
		//System.out.println("连接被关闭");
		//new Exception().printStackTrace();
		connectSocket.close();
	}

	@Override
	public boolean isClosed() {
		return !connectSocket.isOpen();
	}

	@Override
	public void setSoTimeout(int val) throws SocketException {
		sotimeout=val;
	}

	@Override
	public int getSoTimeout() throws SocketException {
		return	sotimeout;
	}

	@Override
	public void flush() throws IOException {
		checkflush();
	}

	@Override
	public boolean isStream() {
		return true;
	}

	

	@Override
	public void writePacket(ByteBuffer kp) throws IOException {
		ByteBuffer szeWrite=NetworkPacket.bufferAllocator.allocate(4);
		szeWrite.limit(4);
		//szeWrite.clear();
		szeWrite.putInt(kp.limit());
		szeWrite.flip();
		KLALBVirtualSocketChannel obj = null;
		writableChannel.write(new ByteBuffer[] {szeWrite,kp});
		checkflush();
		
		
	}
    
	


	@Override
	public ByteBuffer readPacket() throws IOException {

		ByteBuffer szeRead=NetworkPacket.bufferAllocator.allocate(4);
		timeoutTimer=System.nanoTime();
		timerenabled=true;
		ByteBuffer kp;
		szeRead.clear();
		szeRead.limit(4);
		//szeRead.clear();
		try {
			KNEChannels.readFully(readableChannel,szeRead);
			szeRead.flip();
			int size=szeRead.getInt(0);
			kp=NetworkPacket.bufferAllocator.allocate(size);
			kp.limit(size);
			KNEChannels.readFully(readableChannel, kp);
			kp.flip();
		return kp;
		}finally {
			timerenabled=false;
		}
	}
	
	
	
	
	@Override
	public void writeKLALBPacket(KLALBPacket kp) throws IOException {
	
		/*ByteBuffer szeWrite=NetworkPacket.bufferAllocator.allocate(4);
		szeWrite.limit(4);
		//szeWrite.clear();
		int length=(int) kp.getTotalLength();
		szeWrite.putInt(0,length);
		szeWrite.flip();
		
		
		
		writableChannel.write(szeWrite);*/

		if(debug) {

			int length=(int) kp.getTotalLength();
			AtomicLong al=new AtomicLong(0);
			KLALBPacket.writeKLALBPacketToChannel(new MonitoredChannel(writableChannel,null,new AtomicLong[] { al}), kp);
			if(length!=al.get()) {
				throw new StreamCorruptedException(kp+" packet length error:"+al.get()+"!="+length);
			}
		}else {
			KLALBPacket.writeKLALBPacketToChannel(writableChannel, kp);
		}
		
		
	}

	private void checkflush() throws IOException {
		if(writableChannel instanceof BufferedChannel) {
			((BufferedChannel) writableChannel).flush();
		}else if(writableChannel instanceof KLALBVirtualSocketChannel) {
			((KLALBVirtualSocketChannel) writableChannel).flush();
			
		}
	}

	@Override
	public KLALBPacket readKLALBPacket() throws IOException {
	
		/*ByteBuffer szeWrite=NetworkPacket.bufferAllocator.allocate(4);
		szeWrite.limit(4);
		KNEChannels.readFully(readableChannel, szeWrite);
		szeWrite.flip();
		int readSize=szeWrite.getInt();*/
		KLALBPacket kp=KLALBPacket.readKLALBPacketFromChannel(readableChannel);
	if(debug) {
		int readSize=0;
		long kpl=kp.getTotalLength();
		if(kpl!=readSize) {
			throw new StreamCorruptedException(kp+" packet length error:"+kpl+"!="+readSize);
		}
	}
		return kp;
	}

	
	
}
