// 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.bgp4Peer.peer;

import eu.teraflow.tid.bgp4Peer.bgp4session.BGP4SessionsInformation;
import eu.teraflow.tid.bgp4Peer.management.BGP4ManagementServer;
import eu.teraflow.tid.bgp4Peer.updateTEDB.UpdateDispatcher;
import eu.teraflow.tid.tedb.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * BGP-LS Speaker. 
 * This class is a BGP-LS peer which has two modes:
 * - Listens to incoming connections
 * - Launches new BGP session with a list of peers.
 * It can send periodically its learnt topology to other peers.
 * TEDB can be initialized from:
 * - XML file with the topolgoy
 * - Other BGP-LS Sessions 
 * 
 * @author pac ogondio 
 *
 */
public class BGPPeer {
	/**
	 * Session server. It opens a socket to listen to new connections.
	 */
	private BGP4SessionServerManager bgp4SessionServer;
	/**
	 * Session client. It connects to peers.
	 */
	private  BGP4SessionClientManager  bgp4SessionClientManager;
	/**
	 * BGP4 parameters. Needed to configure the connections.
	 */
	private BGP4Parameters params;
	
	/**
	 * List of opened BGP4 sessions. 
	 */
	private BGP4SessionsInformation bgp4SessionsInformation;
	
	/**
	 * Topology database only for interDomain Links.
	 */
	private MultiDomainTEDB multiDomainTEDB;
	
	/**
	 * Table with domainID - TEDB.
	 * The BGP-LS Peer can have several domains
	 */
	private Hashtable<String,TEDB> intraTEDBs;
	
	/**
	 * Full TEDB with all Links
	 */
	
	private SimpleTEDB fullTEDB;
	
	
	/**
	 * Class to send periodically the topology
	 */
	//private DomainTEDB readDomainTEDB;
	
	/**
	 * True: This peer sends the topology to other peers
	 * False: This peer does NOT send the topology to other peers
	 */
	private boolean sendTopology;
	/**
	 * 
	 */
	private SendTopology sendTopologyTask;
	/**
	 * 
	 */
	private boolean saveTopology;
	
	private SaveTopologyinDB saveTopologyDB;
	
	/**
	 * 
	 */
	/**
	 * List of peers to establish connections.
	 */
	private LinkedList<BGP4LSPeerInfo> peersToConnect;
	
	//Whitelist and blacklist not yet implemented
	
	/**
	 * Loggers
	 */
	private Logger logParser;
	private Logger logClient;
	private Logger logServer;
	/**
	 * Class to read and process the BGP4 update messages
	 */
	private UpdateDispatcher ud;
	/**
	 * Executor. To execute the session server, to execute periodically the session client.
	 */
	private ScheduledThreadPoolExecutor executor;

	/**
	 * Function to configure the BGP4 Peer without specifying the file. It will read a file with name: BGP4Parameters.xml
	 */
	public void  configure (){
		this.configure (null);
	}
	
	/**
	 * Function to configure the BGP4 peer. 
	 * It created the loggers, the executor, 
	 * @param nameParametersFile Name of the Parameters File
	 */
	public void configure(String nameParametersFile){
		configure(nameParametersFile, null, null);
	}
	
	/**
	 * Function to configure the BGP4 peer. 
	 * It created the loggers, the executor, 
	 * @param nameParametersFile Name of the Parameters File
	 * @param multiTEDB multidomain database
	 * @param iTEDBs internal domains database 
	 */
	public void configure(String nameParametersFile, MultiDomainTEDB multiTEDB, Hashtable<String,TEDB> iTEDBs){
		//First of all, read the parameters
		if (nameParametersFile != null){
			params=new BGP4Parameters(nameParametersFile);
		}else{
			params=new BGP4Parameters();
		}
		params.initialize();
		peersToConnect = params.getPeersToConnect();
		sendTopology = params.isSendTopology();
		saveTopology = params.isSaveTopologyDB();

		//Initialize loggers
//		FileHandler fh;
//		FileHandler fh1;
//		FileHandler fh2;
//		try {
//			fh=new FileHandler(params.getBGP4LogFile());
			logParser=LoggerFactory.getLogger("BGP4Parser");
//			logParser.addHandler(fh);
//			logParser.setLevel(Level.ALL);
//			fh1=new FileHandler(params.getBGP4LogFileClient());
			logClient=LoggerFactory.getLogger("BGP4Client");
//			logClient.addHandler(fh1);
//			logClient.setLevel(Level.ALL);
//			fh2=new FileHandler(params.getBGP4LogFileServer());
			logServer=LoggerFactory.getLogger("BGP4Peer");
//			logServer.addHandler(fh2);
//			logServer.setLevel(Level.ALL);
//
//		} catch (Exception e1) {
//			e1.printStackTrace();
//			System.exit(1);
//		}
			logServer.info("Inizializing BGP4 Peer");
		if (iTEDBs!= null) intraTEDBs=iTEDBs;
		else intraTEDBs=new Hashtable<String,TEDB>();
		
		if (multiTEDB!= null) multiDomainTEDB = multiTEDB;
		else multiDomainTEDB = new MDTEDB();
	
		if (params.getLearnTopology().equals("fromXML")){
			//intraTEDBs=new Hashtable<Inet4Address,DomainTEDB>();
			//multiDomainTEDB = new MDTEDB();
			//intraTEDBs = FileTEDBUpdater.readMultipleDomainSimpleNetworks(params.getTopologyFile(), null, false,0,Integer.MAX_VALUE, false);
			logParser.info("BGPIdentifier: "+params.getBGPIdentifier());
			intraTEDBs = FileTEDBUpdater.readMultipleDomainSimpleNetworks(params.getTopologyFile(), null, false,0,Integer.MAX_VALUE, false, params.getBGPIdentifier());

			//multiDomainTEDB.initializeFromFile(params.getTopologyFile());
			multiDomainTEDB.initializeFromFile(params.getTopologyFile(), params.getBGPIdentifier());

		}
		// Create Thread executor
		//FIXME: Actualizar n�mero de threads que se crean
		executor = new ScheduledThreadPoolExecutor(20);//1 para el servidor, 1 para el que lanza y vigila los clientes
		// Information about all the sessions of the PCE
		if (params.isTest()) {
			bgp4SessionsInformation = new BGP4SessionsInformation(params.isTest());
		}
		else{
			bgp4SessionsInformation = new BGP4SessionsInformation();
		}
		//Create the task to send the topology. It has to be created because you can start sending the topology in the management (wirting): send topology on.
		sendTopologyTask = new SendTopology();
		saveTopologyDB= new SaveTopologyinDB();
		if (params.isSaveTopologyDB() == true){
			saveTopologyDB.configure(intraTEDBs, multiDomainTEDB, params.isSaveTopologyDB(), params.getTopologyDBIP().getHostAddress(), params.getTopologyDBport());
		}

	}
	
	public void setWriteMultiTEDB(MultiDomainTEDB multiTEDB) {
		
		this.multiDomainTEDB = multiTEDB;
		saveTopologyDB.setMultiDomainTEDB(multiTEDB);
	}

	/*
	//new function from Andrea
	public void setWriteMultiAndIntraTEDB(MultiDomainTEDB multiTEDB, Hashtable<Inet4Address,DomainTEDB> intraTEDBs) {

		this.multiDomainTEDB = multiTEDB;
		this.intraTEDBs = intraTEDBs;
		saveTopologyDB.setMultiDomainTEDB(multiTEDB);
		saveTopologyDB.setIntraTEDBs(intraTEDBs);
	}
	 */


	public void setReadDomainTEDB(DomainTEDB readDomainTEDB) {
		//this.readDomainTEDB = readDomainTEDB;
		//System.out.println("setReadDomain: readFomainTEDB().getDomainID()="+readDomainTEDB.getDomainID());
		//System.out.println("setReadDomain: readFomainTEDB="+readDomainTEDB.printTopology());
		if(readDomainTEDB.getDomainID() == null)
			this.intraTEDBs.put("default", readDomainTEDB);
		else
			this.intraTEDBs.put(readDomainTEDB.getDomainID().toString(), readDomainTEDB);
	}
	public void createUpdateDispatcher(){
		//Updater dispatcher
		ud = new UpdateDispatcher(multiDomainTEDB,intraTEDBs);
	}
	
	/**
	 * Function to create the TEDBs of the peer.
	 * @param nameParametersFile Name of the Parameters File
	 */

	
	/**
	 * Start the session for the management of the BGP4.
	 */
	public void startManagementServer(){
		logServer.debug("Initializing Management Server");
		BGP4ManagementServer bms=new BGP4ManagementServer(params.getBGP4ManagementPort(),multiDomainTEDB,intraTEDBs,bgp4SessionsInformation,sendTopologyTask);	
		bms.start();
	}
	/**
	 * Function which start the peer as a client which try to establish new sessions with peers.
	 * It starts a new process for each peer.  
	 */
	public void startClient(){
		logClient.debug("Initializing Session Manager to connect as client");
		if (params.getBGPIdentifier() != null){
			Inet4Address BGPIdentifier=null;
			try {
				BGPIdentifier = (Inet4Address) InetAddress.getByName(params.getBGPIdentifier());
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
			for (int i =0;i<peersToConnect.size();i++){		
				bgp4SessionClientManager=new BGP4SessionClientManager(bgp4SessionsInformation,ud, peersToConnect.get(i), params.getBGP4Port(),params.getLocalBGPAddress(),params.getLocalBGPPort(),params.getHoldTime(),BGPIdentifier,params.getVersion(),params.getMyAutonomousSystem(),params.getKeepAliveTimer());
				//FIXME: Ver si dejamos delay fijo o variable	
				executor.scheduleWithFixedDelay(bgp4SessionClientManager, 0,params.getDelay(), TimeUnit.MILLISECONDS);
			}
		}
		else{
			logClient.error("ERROR: BGPIdentifier is not configured. To configure: XML file (BGP4Parameters.xml) <localBGPAddress>.");
			System.exit(1);
		}
		
	}
	/**
	 * Function which starts the peer (listening BGP4 protocol) as a server.
	 * It starts once the session server manager.
	 */
	public  void startServer(){
		logServer.info("Initializing Session Manager to connect as server");
		Inet4Address localAddress=null;
		Inet4Address BGPIdentifier=null;
		if (params.getBGPIdentifier() != null){
			try {
				localAddress = (Inet4Address) InetAddress.getByName(params.getLocalBGPAddress());
				BGPIdentifier = (Inet4Address) InetAddress.getByName(params.getBGPIdentifier());
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
			if (params.isTest()) {
				bgp4SessionServer = new BGP4SessionServerManager(bgp4SessionsInformation, multiDomainTEDB, ud, params.getBGP4Port(), params.getHoldTime(), BGPIdentifier, params.getVersion(), params.getMyAutonomousSystem(), params.isNodelay(), localAddress, params.getKeepAliveTimer(), peersToConnect, params.isTest());
				executor.execute(bgp4SessionServer);
			}
			else {
				bgp4SessionServer = new BGP4SessionServerManager(bgp4SessionsInformation, multiDomainTEDB, ud, params.getBGP4Port(), params.getHoldTime(), BGPIdentifier, params.getVersion(), params.getMyAutonomousSystem(), params.isNodelay(), localAddress, params.getKeepAliveTimer(), peersToConnect);
				executor.execute(bgp4SessionServer);
			}

		}else{
			logServer.error("ERROR: BGPIdentifier is not configured. To configure: XML file (BGP4Parameters.xml) <localBGPAddress>.");
			System.exit(1);
		}
	}
	
	public void startSendTopology(){
		if (params.isTest()) {
			sendTopologyTask.configure(intraTEDBs, bgp4SessionsInformation, sendTopology, params.getInstanceID(),params.isSendIntradomainLinks(),this.multiDomainTEDB, params.isTest());
		}
		else{
			sendTopologyTask.configure(intraTEDBs, bgp4SessionsInformation, sendTopology, params.getInstanceID(),params.isSendIntradomainLinks(),this.multiDomainTEDB);
		}
		executor.scheduleWithFixedDelay(sendTopologyTask, 0,params.getSendTopoDelay(), TimeUnit.MILLISECONDS);
	}
	
	
	
	public void startSaveTopology(){
		//FIXME: ADD param to configure the delay
		executor.scheduleWithFixedDelay(saveTopologyDB, 0,5000, TimeUnit.MILLISECONDS);
	}
		
	
	public SaveTopologyinDB getSaveTopologyDB() {
		return saveTopologyDB;
	}
	
	public void setSaveTopologyDB(SaveTopologyinDB saveTopologyDB) {
		this.saveTopologyDB = saveTopologyDB;
	}
	
	public boolean isSaveTopology() {
		return saveTopology;
	}
	
	public void setSaveTopology(boolean saveTopology) {
		this.saveTopology = saveTopology;
	}
	
	public UpdateDispatcher getUd() {
		return ud;
	}
	
	public void setUd(UpdateDispatcher ud) {
		this.ud = ud;
	}

	public void addSimpleTEDB(SimpleTEDB simpleTEDB, String domainID) {
			this.intraTEDBs.put(domainID, simpleTEDB);
	}
	
	public void setSimpleTEDB(SimpleTEDB simpleTEDB) {
		if(simpleTEDB.getDomainID() == null)
			this.intraTEDBs.put("default", simpleTEDB);
		else
			this.intraTEDBs.put(simpleTEDB.getDomainID().toString(), simpleTEDB);
	}
	
	
	public void stopPeer(){
		executor.shutdown();
	}
	public MultiDomainTEDB getMultiDomainTEDB() {
		return multiDomainTEDB;
	}
	

	public Hashtable<String, TEDB> getIntraTEDBs() {
		return intraTEDBs;
	}

	public void setIntraTEDBs(Hashtable<String, TEDB> intraTEDBs) {
		this.intraTEDBs = intraTEDBs;
	}

	public void setMultiDomainTEDB(MultiDomainTEDB multiDomainTEDB) {
		this.multiDomainTEDB = multiDomainTEDB;
	}
	
	
	
	
	
}
