package railo.commons.net;

import java.io.IOException;
import java.io.Serializable;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;

import railo.commons.lang.StringUtil;
import railo.runtime.type.util.ListUtil;

public class IPRange implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 4427999443422764L;
	private static final short N256 = 256;
	private static final int SIZE = 4;
	
	private Range[] ranges=new Range[SIZE];
	int max=0;
	
	
	private static class Range {
		private final short[] from;
		private final short[] to;
		private final boolean equality;

		private Range(short[] from, short[] to) throws IOException{
			if(from.length!=to.length)
				throw new IOException("both ip address must be from same type, IPv4 or IPv6");
			
			this.from=from;
			this.to=to;
			this.equality=equal(from,to);
		}
		private Range(short[] from){
			this.from=from;
			this.to=from;
			this.equality=true;
		}
		private boolean inRange(short[] sarr) {
			if(from.length!=sarr.length) return false;
			
			if(equality) return equal(from, sarr);
			
			for(int i=0;i<from.length;i++){
				if(from[i]>sarr[i] ||  to[i]<sarr[i])
					return false;
			}
			return true;
		}
		
		public String toString(){
			if(equality) return toString(from);
			return toString(from)+"-"+toString(to);
		}
		
		private String toString(short[] sarr) {
			if(sarr.length==4)
				return new StringBuilder().append(sarr[0]).append(".").append(sarr[1]).append(".").append(sarr[2]).append(".").append(sarr[3]).toString();
			
			return new StringBuilder()
			.append(toHex(sarr[0],sarr[1],false)).append(":")
			.append(toHex(sarr[2],sarr[3],true)).append(":")
			.append(toHex(sarr[4],sarr[5],true)).append(":")
			.append(toHex(sarr[6],sarr[7],true)).append(":")
			.append(toHex(sarr[8],sarr[9],true)).append(":")
			.append(toHex(sarr[10],sarr[11],true)).append(":")
			.append(toHex(sarr[12],sarr[13],true)).append(":")
			.append(toHex(sarr[14],sarr[15],false)).toString();
			
			
		}
		
		
		
		private String toHex(int first, int second, boolean allowEmpty) {
			String str1=Integer.toString(first,16);
			while(str1.length()<2)str1="0"+str1;
			String str2=Integer.toString(second,16);
			while(str2.length()<2)str2="0"+str2;
			str1+=str2;
			if(allowEmpty && str1.equals("0000")) return "";
			
			while(str1.length()>1 && str1.charAt(0)=='0')str1=str1.substring(1);
			
			
			
			return str1;
		}
		private boolean equal(short[] left, short[] right) {
			for(int i=0;i<left.length;i++){
				if(left[i]!=right[i]) return false;
			}
			return true;
		}
		
		
	}
	

	private void add(String ip) throws IOException {
		ip=ip.trim();
		// no wildcard defined
		if(ip.indexOf('*')==-1) {
			add(new Range(toShortArray(toInetAddress(ip))));
			return;
		}
		
		if("*".equals(ip)) {
			add("*.*.*.*");
			add("*:*:*:*:*:*:*:*");
			return;
		}
		
		String from = ip.replace('*', '0');
		String to;
		InetAddress addr1 = toInetAddress(from);
		if(addr1 instanceof Inet6Address) 
			to=StringUtil.replace(ip, "*","ffff",false);
		else 
			to=StringUtil.replace(ip, "*","255",false);
		add(new Range(toShortArray(addr1),toShortArray(toInetAddress(to))));
	}

	private void add(String ip1,String ip2) throws IOException {
		add(new Range(toShortArray(toInetAddress(ip1)),toShortArray(toInetAddress(ip2))));
	}
	public static IPRange getInstance(String raw) throws IOException {
		return getInstance(ListUtil.listToStringArray(raw, ','));
	}

	public static IPRange getInstance(String[] raw) throws IOException {
		IPRange range=new IPRange();
		String[] arr = ListUtil.trimItems(ListUtil.trim(raw));
		String str;
		int index;
		for(int i=0;i<arr.length;i++){
			str=arr[i];
			if(str.length()>0) {
				index=str.indexOf('-');
				if(index!=-1) range.add(str.substring(0,index), str.substring(index+1));
				else range.add(str);
			}
		}
		return range;
		
	}
	
	
	private synchronized void add(Range range) {
		if(max>=ranges.length) {
			Range[] tmp=new Range[ranges.length+SIZE];
			for(int i=0;i<ranges.length;i++){
				tmp[i]=ranges[i];
			}
			ranges=tmp;
		}
		ranges[max++]=range;
	}

	public boolean inRange(String ip) throws IOException {
		return inRange(toShortArray(ip));
	}

	public boolean inRange(short[] ip) {
		for(int i=0;i<max;i++){
			if(ranges[i].inRange(ip)) return true;
		}
		return false;
	}
	
	
	public String toString(){
		StringBuilder sb=new StringBuilder();
		for(int i=0;i<max;i++){
			if(i>0)sb.append(",");
			sb.append(ranges[i].toString());
		}
		return sb.toString();
	}

	public static short[] toShortArray(String ip) throws IOException {
		return toShortArray(toInetAddress(ip));
	}
	
	private static InetAddress toInetAddress(String ip) throws IOException {
		// TODO Auto-generated method stub
		try {
			return InetAddress.getByName(ip);
		} catch (UnknownHostException e) {
			throw new IOException("cannot parse the ip ["+ip+"]");
		}
	}

	private static short[] toShortArray(InetAddress ia){
		byte[] addr = ia.getAddress();
		short[] sarr=new short[addr.length];
		for(int i=0;i<addr.length;i++){
			sarr[i]=byte2short(addr[i]);
		}
		return sarr;
	}
	
	private static short byte2short(byte b){
		if(b<0) return (short)(b+N256);
		return b;
	}
}
