// Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//      http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package eu.teraflow.tid.tedb;

import java.net.Inet4Address;
import java.util.Iterator;
import java.util.LinkedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import es.tid.rsvp.objects.subobjects.EROSubobject;
import es.tid.rsvp.objects.subobjects.IPv4prefixEROSubobject;
import es.tid.rsvp.objects.subobjects.SubObjectValues;

/**
 * Class that maintains a table with Reachability entries.
 * It has a method to add a reachability entry
 * It has a method to query the domain of an IPv4 endpoint
 * It has a method to query if an address begins to a given IP range
 * KNOWN BUGS: 
 * * When a reachability entry is added, it is not checked if
 *   it is already there or not.
 * * Reachability entries cannot be removed
 *  
 * @author ogondio
 *
 */
public class ReachabilityManager {
	
	/**
	 * Table with IP address/prefix --> domain
	 */
	LinkedList<ReachabilityEntry> reachability;
	/**
	 * The logger
	 */
	private Logger log;
	
	/**
	 * ReachabilityManager constructor
	 */
	public ReachabilityManager(){
		log=LoggerFactory.getLogger("PCEServer");
		reachability=new LinkedList<ReachabilityEntry>();
	}
	
	/**
	 * gets the domain of an IPv4 EndpointAddress
	 * @param endpointAddress the address to query
	 * @return The domain id if the endpointAddress belongs to a domain, null otherwise.
	 */
	public Inet4Address getDomain(Inet4Address endpointAddress) {
		//String domain="0.0.0.1";
		Iterator<ReachabilityEntry> it=reachability.iterator();
		while (it.hasNext()){			
			ReachabilityEntry ra=it.next();
			//log.info("reachability entry "+ra.aggregatedIPRange+" mask "+(ra.mask[0]&0xFF)+"."+(ra.mask[1]&0xFF)+"."+(ra.mask[2]&0xFF)+"."+(ra.mask[3]&0xFF));			
			if (belongsToDomain(endpointAddress, ra.mask, ra.aggregatedIPRange)){
				return ra.domainId;
			}
		}
		return null;
		
	}
	
	/**
	 * Method to determine if the address 'nodeAdrress' belongs to the aggregated IP address range given by the mask and the addresss 'aggregatedIPRange'
	 * @param nodeAddress The address to query
	 * @param mask The 32 bit network mask
	 * @param aggregatedIPRange The aggregated IP address
	 * @return true if it belongs to the range, false if it does not belong to the range
	 */
	public boolean belongsToDomain(Inet4Address nodeAddress, byte []mask, Inet4Address aggregatedIPRange){
		int i;
		//byte [] networkAddress=new byte[4];
		byte [] bytesAggregatedIPRange=aggregatedIPRange.getAddress();
		byte [] bytesNodeAddress=nodeAddress.getAddress();
		boolean found=true;		
		for (i=0;i<4;++i){
			//networkAddress[i]= (byte) ((bytesNodeAddress[i]&mask[i])&0xFF)  ;
			//log.info("network Node Address[i]: "+(networkAddress[i]&0xFF));
			//log.info("bytesAggregatedIPRange["+i+"]: "+((bytesAggregatedIPRange[i]&mask[i])&0xFF));
			//log.info("bytesNodeAddress["+i+"]: "+((bytesNodeAddress[i]&mask[i])&0xFF));
			if ((byte)((bytesAggregatedIPRange[i]&mask[i])&0xFF)!=(byte)((bytesNodeAddress[i]&mask[i])&0xFF)){
				found=false;
				return found;
			}
		}
		return found;		
	}
	
	/**
	 * Adds a ERO Subobject associated with a given domainId to the reachability table 
	 * @param domainId Domain ID
	 * @param eroso ERO Subobject
	 */
	public void addEROSubobject(Inet4Address domainId, EROSubobject eroso ){	
		if (eroso.getType()==SubObjectValues.ERO_SUBOBJECT_IPV4PREFIX){
			log.info("Adding IPv4 ERO Subobject to the Reachability Database");
			IPv4prefixEROSubobject ipv4eroso=(IPv4prefixEROSubobject)eroso;
			//long mask=(long)0xFFFFFFFF-(long)Math.pow(2, 32-ipv4eroso.getPrefix())-1;
			log.info("prefix "+ipv4eroso.getPrefix());
			long resta=((long)0x1<<ipv4eroso.getPrefix())-1;
			long maskLong=resta<<(32-ipv4eroso.getPrefix());
			byte[] mask=new byte[4];
			mask[0]=(byte)(maskLong>>>24 & 0xFF);
			mask[1]=(byte)(maskLong>>>16 & 0xFF);
			mask[2]=(byte)(maskLong>>>8 & 0xFF);
			mask[3]=(byte)(maskLong& 0xFF);
			log.info("mask: "+(mask[0]&0xFF)+"."+(mask[1]&0xFF)+"."+(mask[2]&0xFF)+"."+(mask[3]&0xFF));
			log.info("Domain ID: "+domainId+" mask "+mask+" IPv4 Address: "+ipv4eroso.getIpv4address());//Pasar a FINE
			addIPv4Entry(domainId,mask,ipv4eroso.getIpv4address(),ipv4eroso.getPrefix());
		}
		
	}
	
	private void addIPv4Entry(Inet4Address domainId,byte [] mask,Inet4Address aggregatedIPRange,int prefix){
		ReachabilityEntry ra=new ReachabilityEntry();
		ra.aggregatedIPRange=aggregatedIPRange;
		ra.mask=mask;
		ra.domainId=domainId;
		ra.prefix=prefix;
		if (!(reachability.contains(ra))){
			reachability.add(ra);
		}	
		return;
	}
	
	/**
	 * Print the reachability table
	 * @return String with the reachability information
	 */
	public String printReachability(){
		String reachabilityString;
		Iterator <ReachabilityEntry> reachabIterator=reachability.iterator();
		reachabilityString="Reachability entries: \r\n";
		while (reachabIterator.hasNext()){
			ReachabilityEntry entry= reachabIterator.next();
			reachabilityString=reachabilityString+"\t"+entry.toString()+"\r\n";
		}
	
		return reachabilityString;
	}

	public LinkedList<ReachabilityEntry> getReachability() {
		return reachability;
	}
	
	
}
