package org.kne.cloud.network.scanner;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ThreadTool;

public class TCPNetworkScanner implements Runnable {
	private BiConsumer<MultiProtocolSocketAddress,Socket> consumer;
	private Consumer<Float>processConsumer;

	public Consumer<Float> getProcessConsumer() {
		return processConsumer;
	}

	public void setProcessConsumer(Consumer<Float> processConsumer) {
		this.processConsumer = processConsumer;
	}


	private List<ScanRange> ranges = new ArrayList<>();

	

	public List<ScanRange> getRanges() {
		return ranges;
	}

	public BiConsumer<MultiProtocolSocketAddress,Socket> getConsumer() {
		return consumer;
	}

	public void setConsumer(BiConsumer<MultiProtocolSocketAddress,Socket> consumer) {
		this.consumer = consumer;
	}

		private BigInteger tot=new BigInteger("0");
		private BigInteger counter=new BigInteger("0");
		private int connectTimeout=500;
		private int soTimeout=3000;
	@Override
	public void run() {
		
		ThreadPoolExecutor exc=(ThreadPoolExecutor) Executors.newFixedThreadPool(512,new ThreadFactory() {
			
			@Override
			public Thread newThread(Runnable r) {
				Thread t=ThreadTool.makeVThreadIfSupport("端口扫描线程", r);
				t.setPriority(Thread.MIN_PRIORITY);
				return t;
			}
		});
		
ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
		
		BigInteger bip=new BigInteger("0");
		for (Iterator<ScanRange> iterator = ranges.iterator(); iterator.hasNext();) {
			ScanRange sRange = (ScanRange) iterator.next();
			bip=bip.add(sRange.getPortRange().getElementCount().multiply(sRange.getAddressRange().getElementCount()));
		}
		
		tot=bip;
		
		ReentrantLock counterlock=new ReentrantLock();
		counter=new BigInteger("0");
		
		
		counterlock.lock();
		try {
			if(processConsumer!=null) {
				BigDecimal bdt=new BigDecimal(tot);
				BigDecimal bdc=new BigDecimal(counter);
				float fp;
				if(bdt.equals(BigDecimal.ZERO)) {
					fp=100;
				}else {
				fp=bdc.divide(bdt, 8, BigDecimal.ROUND_HALF_UP).floatValue()*100;
				}
				processConsumer.accept(fp);
			}
		}finally {
			counterlock.unlock();
		}
		
		try {
			for (Iterator<ScanRange> iterator = ranges.iterator(); iterator.hasNext();) {
				ScanRange sRange = (ScanRange) iterator.next();
				PortRange portRange=sRange.getPortRange();
				InetAddressRange inetAddressRange=sRange.getAddressRange();
				
				while( excd.getQueue().size()>500) {
					LockSupport.parkNanos(1000000);
				}
				excd.execute(()->{
			int curport = portRange.getBegin();
			while (curport <= portRange.getEnd()) {
				
				
				
					byte[]endarr=inetAddressRange.getEnd().getAddress();
					byte[] curr = inetAddressRange.getBegin().getAddress();
					while (compareIPAddress(curr,endarr)) {
						
						int curport0=curport;
						InetAddress currAddress=pga(curr);
						
						while( exc.getQueue().size()>500) {
							LockSupport.parkNanos(1000000);
						}
						exc.execute(()->{
						
						
							MultiProtocolSocketAddress mpsa;
							if (currAddress.equals(inetAddressRange.getBegin())) {
								mpsa = new MultiProtocolSocketAddress(inetAddressRange.getBeginHost(), curport0);
							}else if (currAddress.equals(inetAddressRange.getEnd())) {
									mpsa = new MultiProtocolSocketAddress(inetAddressRange.getEndHost(), curport0);
							} else {
								mpsa = new MultiProtocolSocketAddress(currAddress.getHostAddress(),
										curport0);
							}
						Socket s = null;
						try {
							s=new Socket();
							InetSocketAddress isa=new InetSocketAddress(currAddress, curport0);
							//System.out.println(isa);
							s.connect(isa,connectTimeout);
							s.setSoTimeout(soTimeout);
							try {
							acceptMultipurposeSocketAddress(mpsa, s);
							}catch(Exception e) {
								e.printStackTrace();
							}
						} catch (Exception e) {
							try {
							acceptMultipurposeSocketAddress(mpsa, null);
							}catch(Exception ed) {
								ed.printStackTrace();
							}
						} finally {
							if (s != null)
								try {
									s.close();
								} catch (IOException e) {
								}
						}
						counterlock.lock();
						try {
							counter=counter.add(BigInteger.valueOf(1));
							if(processConsumer!=null) {
								BigDecimal bdt=new BigDecimal(tot);
								BigDecimal bdc=new BigDecimal(counter);
								float fp=bdc.divide(bdt, 8, BigDecimal.ROUND_HALF_UP).floatValue()*100;
								try {
								processConsumer.accept(fp);
								}catch(Exception e) {
									e.printStackTrace();
								}
							}
						}finally {
							counterlock.unlock();
						}
						});
						increaceIPAddress(curr);
					}
				
				
				try {
					Thread.sleep(10);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				curport++;
			}
				});
		}
		
		}finally {
			excd.shutdown();
			try {
				excd.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			exc.shutdown();
			try {
				exc.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private InetAddress pga(byte[] curr) {
		try {
			return InetAddress.getByAddress(curr);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		return null;
	}

	private boolean compareIPAddress(byte[] curr, byte[] endarr) {
		
		return new BigInteger(curr).compareTo(new BigInteger(endarr))<=0;
	}

	private void increaceIPAddress(byte[] curr) {
		System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
	}

	protected void acceptMultipurposeSocketAddress(MultiProtocolSocketAddress scanned, Socket s) {

		consumer.accept(scanned,s);
	}

	public int getConnectTimeout() {
		return connectTimeout;
	}

	public void setConnectTimeout(int connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public int getSoTimeout() {
		return soTimeout;
	}

	public void setSoTimeout(int soTimeout) {
		this.soTimeout = soTimeout;
	}
	
	

	public TCPNetworkScanner(int connectTimeout, int soTimeout) {
		super();
		this.connectTimeout = connectTimeout;
		this.soTimeout = soTimeout;
	}

	public TCPNetworkScanner() {
	}

	public static void main(String[] args) throws UnknownHostException {
		TCPNetworkScanner tcn = new TCPNetworkScanner();
		//tcn.getRanges().add(new ScanRange(new InetAddressRange("127.0.0.1","127.0.0.2"),new PortRange(25565,37000)));
		tcn.getRanges().add(new ScanRange("ip.8mi.work:50000~ip.8mi.work:60000"));
		tcn.setConsumer((scanned,s) -> {
			if(s!=null)
		System.out.println(scanned);
		});
		tcn.setProcessConsumer((f)->{System.out.println(f);
			});
		
		tcn.run();
		
	}
}
