package org.kne.cloud.network.kltp;

import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.SocketTimeoutException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.congestion.MpscMessageBatcher;
import org.kne.cloud.network.congestion.ReceivePacketSlidingWindow;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6Packet;
import org.kne.cloud.network.klalb.DATATPacket;
import org.kne.cloud.network.klalb.KLALBController;

public class KLTPInputStream extends InputStream implements KLTPPacketConsumer, ReadableByteChannel{


	
	private KLALBController controller;
	
	private IPv6Address remoteaddr;


	private UUID streamUUID;


	private ReceivePacketSlidingWindow<Long, KLTPPacket>recvMap=new ReceivePacketSlidingWindow<Long, KLTPPacket>(Integer.MAX_VALUE,-20);
	
	
	private AtomicLong inputcount = new AtomicLong();
		private KLTPPacket  dataPack = null;
		
	private long soTimeout=0;

	public IPv6Address getRemoteAddress() {
		return remoteaddr;
	}
	public KLTPInputStream(KLALBController controller,IPv6Address remoteaddr,UUID uuid) throws BindException {
		this.controller=controller;
		this.streamUUID =uuid;
		this.remoteaddr=remoteaddr;
		controller.getKLTPregister().registerReceiveStream(this);
		
	}
	

		@Override
		public int read() throws IOException {
			if (dataPack == null ||(!dataPack.getKLTPData().hasRemaining())) {
				dataPack=nextPacket(true);
			}
			if (dataPack.getDataSize() == 0) {
				return -1;
			} else {
				int ret= dataPack.getKLTPData().get() & 0xff;
				return ret;
			}
		}
		private KLTPPacket nextPacket(boolean block) throws IOException {
			try {
			KLTPPacket dtp2 =null;
			if(block) {
			if(soTimeout==0) {
				dtp2= recvMap.take(inputcount.get());
			
			}else {
			dtp2= recvMap.take(inputcount.get(),soTimeout);
			}
			}else {
				dtp2=recvMap.poll(inputcount.get());
			}
				if (dtp2 != null) {
					inputcount.setPlain( inputcount.getPlain()+1);
					int size=dtp2.getDataSize();
					//socketMonitor.getDownloadBandwidth().recordPacket(pid, size);
					//controller.getDatatMonitor().getDownloadBandwidth().recordPacket(KLALBUtils.createGlobalUUID(), size);
					//checkFlowControl(dtp2);
				//	System.out.println("序列号："+dtp2.getSequence());
					return dtp2;
				}
			}catch(SocketTimeoutException e) {
				close0();
				throw e;
			}
			return null;
		}
		
		

		/*@Override
		public int read(ByteBuffer dst) throws IOException {
			int oldlmt=dst.limit();
			try {

				
				if (dataPack == null ||(!dataPack.getKLTPData().hasRemaining())) {
					nextPacket();
				}
			if (dataPack.getDataSize() == 0) {
				return -1;
			} else {
			int len = Math.min(dst.remaining(), available());
			dst.limit(dst.position()+len);
				dst.put( dataPack.getKLTPData().get()) ;
				
			}
			int i = 1;
			try {
				while (dst.hasRemaining()) {

					if (dataPack == null ||(!dataPack.getKLTPData().hasRemaining())) {
						nextPacket();
					}
					if (dataPack.getDataSize() == 0) {
						break;
					} 
					int min=Math.min(dataPack.getKLTPData().remaining(), dst.remaining());
					int oldlm=dataPack.getKLTPData().limit();
					dataPack.getKLTPData().limit(dataPack.getKLTPData().position()+min);
					System.out.println("dst:"+dst+" datapack:"+dataPack);
					dst.put(dataPack.getKLTPData());
					dataPack.getKLTPData().limit(oldlm);
					i+=min;
						
					
				}
			} catch (IOException ee) {
			}
			return i;
			}catch(BufferOverflowException e) {
				System.err.println("dst:"+dst+" datapack:"+dataPack);
				throw e;
			}finally {
				dst.limit(oldlmt);
			}
		}*/
		@Override
		public int read(ByteBuffer dst) throws IOException {
		    if (!dst.hasRemaining()) {
		        return 0;
		    }
		    
		    int totalRead = 0;
		    
		    try {
		        // 如果当前没有数据包或当前数据包已读完，获取下一个
		        if (dataPack == null || (!dataPack.getKLTPData().hasRemaining()&&(dataPack.getDataSize()!=0))) {
		        	dataPack=nextPacket(true);
		        }
		        // EOF 检查
		        if (dataPack.getDataSize() == 0) {
			        //System.out.println("EOF recv:"+dataPack);
		            return -1;
		        }
		        
		        // 循环读取直到 dst 满或没有更多数据
		        while (dst.hasRemaining()) {
		            // 获取当前数据包的剩余数据
		            ByteBuffer src = dataPack.getKLTPData();
		            
		            if (!src.hasRemaining()) {
		                // 当前包读完，尝试获取下一个包
		            	dataPack=nextPacket(false);
		                if (dataPack==null||dataPack.getDataSize() == 0) {
		                    break;  // 下一个包还没来或EOF
		                }
		                src = dataPack.getKLTPData();
		            }
		            
		            // 计算本次可拷贝的字节数
		            int bytesToCopy = Math.min(src.remaining(), dst.remaining());
		            
		            // 保存原 limit
		            int srcOldLimit = src.limit();
		            int dstOldLimit = dst.limit();
		            
		            try {
		                // 设置临时 limit
		                src.limit(src.position() + bytesToCopy);
		                dst.limit(dst.position() + bytesToCopy);
		                
		                // 执行拷贝
		                dst.put(src);
		                totalRead += bytesToCopy;
		            } finally {
		                // 恢复 limit
		                src.limit(srcOldLimit);
		                dst.limit(dstOldLimit);
		            }
		        }
		    } catch (SocketTimeoutException e) {
		        close0();
		        throw e;
		    } catch (BufferOverflowException e) {
		        // 不应该发生，因为我们做了 min() 检查
		        throw new IOException("Buffer overflow in KLTPInputStream.read", e);
		    }
		    
		    return totalRead > 0 ? totalRead : -1;
		}
		
		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			return read(ByteBuffer.wrap(b,off,len));
		}
		
		@Override
		public void close() throws IOException {
			close0();
		}
		private void close0() throws IOException{
			try {
			recvMap.close();
			}finally {
			controller.getKLTPregister().unregisterReceiveStream(this);
			}
		}
		@Override
		public int available() throws IOException {
			//long i = recvMap.getRecvWindowUsed();
			long i=0;
			if (dataPack != null)
				i+=dataPack.getKLTPData().remaining();
			return (int) i;
		}

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


		public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
			long lth=0;
			for(int i=offset;i<offset+length;i++) {
				lth+=read(dsts[i]);
				if(dsts[i].hasRemaining()) {
					break;
				}
			}
			return lth;
		}

		

		@Override
		public void accept(IPv6Packet u) {
			if(u.getPayload() instanceof KLTPPacket) {
				KLTPPacket kltp=(KLTPPacket) u.getPayload();
				switch(kltp.getType()) {
				case KLTPPacket.KLTP_TYPE_DATA:
					//ackSequenceBatcher.putMessage(kseq); 
					recvMap.put(kltp.getSequence(), kltp);
					controller.getIpv6Router().enqueuePacketSendTask(()->{
						KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
						pack.setCE(u.isCE());
						return controller.createPacketToAddress(remoteaddr,0,pack);
						});
					break;
				case KLTPPacket.KLTP_TYPE_DATAFIN:
					//ackSequenceBatcher.putMessage(kseq2);
					recvMap.put(kltp.getSequence(), kltp);
					controller.getIpv6Router().enqueuePacketSendTask(()->{
						KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
						pack.setCE(u.isCE());
						return controller.createPacketToAddress(remoteaddr,0,pack);
						});
					//System.out.println(inputcount+" "+ recvMap.getRecvMap());
					break;
				}
			}
		}


		@Override
		public UUID getStreamUUID() {
			return streamUUID;
		}
		public boolean isClosed() {
			return recvMap.isClosed();
		}
		public void setSoTimeout(int value) {
			soTimeout=value*1000000L;
		}
		public int getSoTimeout() {
			return (int) (soTimeout/1000000L);
		}
		@Override
		public String toString() {
			return "KLTPInputStream [streamUUID=" + streamUUID + ", recvMap=" + recvMap + "]";
		}



	}