// 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.bgp4session;

import es.tid.bgp.bgp4.messages.BGP4Message;
import es.tid.bgp.bgp4.messages.BGP4MessageTypes;
import es.tid.bgp.bgp4.messages.BGP4Update;
import eu.teraflow.tid.bgp4Peer.peer.BGP4Exception;
import eu.teraflow.tid.bgp4Peer.updateTEDB.UpdateDispatcher;

import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Socket;
import java.util.Timer;
/**
 * Client session
 * 
 * @author mcs
 *
 */
public class BGP4SessionClient extends GenericBGP4Session{
	/**
	 * Peer BGP port to which the session is connected
	 */
	private int peerBGP_port;
	/**
	 * Delay
	 */
	
	
	private boolean no_delay=true;
	private String localBGP4Address;
	private int localBGP4Port; 
	/**
	 * Class to dispatch the BGP4 update messages. 
	 * If a BGP5 update message is received, it is stored in a queue of UpdateDispatcher. 
	 */
	private UpdateDispatcher updateDispatcher;
	
	public BGP4SessionClient(BGP4SessionsInformation bgp4SessionsInformation,UpdateDispatcher updateDispatcher, Inet4Address peerBGP_IPaddress, int peerBGP_port, int holdTime,Inet4Address BGPIdentifier,int version,int myAutonomousSystem, String localBGP4Address, int localBGP4Port,int keepAliveTimer){
		super(bgp4SessionsInformation, holdTime, BGPIdentifier, version, myAutonomousSystem,keepAliveTimer);
		timer=new Timer();
		log = LoggerFactory.getLogger("BGP4Client");		
		this.peerBGP_port = peerBGP_port;
		this.updateDispatcher=updateDispatcher;
		this.localBGP4Address=localBGP4Address;
		this.localBGP4Port=localBGP4Port;
		this.remotePeerIP = peerBGP_IPaddress;
	}
	/**
	 * Initiates a Session between the local BGP Peer and the remote BGP Peer
	 */
	public void run() {
	
		log.info("Opening new BGP4 Session with host "+ this.remotePeerIP.getHostAddress() + " on port " + this.peerBGP_port);
		log.debug("Do we want to update from peer?" + updateFrom);
		log.debug("Do we want to send to peer?" + sendTo);
		try {
			Inet4Address addr = (Inet4Address) Inet4Address.getByName(localBGP4Address);
			Inet4Address addrPeer = remotePeerIP;
			socket = new Socket(addrPeer, peerBGP_port, addr, 0);
			if (no_delay){
				this.socket.setTcpNoDelay(true);
				log.debug("No delay activated");
			}
			
		} catch (IOException e) {
			log.info("Connection refused trying to connect " + remotePeerIP.getHostAddress() + " on port " + peerBGP_port);
			//As there is not yet a session added (it is added in the beginning of initializeBGP4Session());
			//endSession();
			return;
		}			

		try {
			initializeBGP4Session();
			log.info("BGP4 Session established with peer "+this.remotePeerIP);
			this.keepAliveT= new KeepAliveThread(this.getOut(),this.keepAliveTimer);
			keepAliveT.start();
		} catch (BGP4Exception e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
			log.debug("Session with "+this.remotePeerIP+" already exists: "+e2.getMessage());
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				log.error("Problem closing socket "+e.getMessage());
			}
			return;
		}
		
		
		
		
		try{
			while(this.FSMstate==BGP4StateSession.BGP4_STATE_SESSION_UP) {
				try {

					this.msg = readBGP4Msg(in);//Read a new message	

				}catch (IOException e){
					cancelDeadTimer();
					cancelKeepAlive();
					timer.cancel();		
					try {
						in.close();
						out.close();
					} catch (Exception e1) {
						log.warn("problem closing sockets");
					}
					log.debug("Finishing BGP4 Session abruptly!");
					return;
				}
				if (this.msg != null) {//If null, it is not a valid PCEP message			
					boolean bgp4Msg = true;//By now, we assume a valid PCEP message has arrived
					//Depending on the type a different action is performed
					switch(BGP4Message.getMessageType(this.msg)) {

					case BGP4MessageTypes.MESSAGE_OPEN:
						log.debug("BGP OPEN message received from "+this.remotePeerIP);
						//After the session has been started, ignore subsequent OPEN messages
						log.warn("OPEN message ignored");
						break;

					case BGP4MessageTypes.MESSAGE_KEEPALIVE:
						log.debug("BGP KEEPALIVE message received from "+this.remotePeerIP);
						//The Keepalive message allows to reset the deadtimer
						break;

					case BGP4MessageTypes.MESSAGE_NOTIFICATION:
						log.debug("BGP NOTIFICATION message received from "+this.remotePeerIP);
						break;

					case BGP4MessageTypes.MESSAGE_UPDATE:
						log.debug("BGP UPDATE message received from "+this.remotePeerIP);
						if(this.getUpdateFrom()){
							BGP4Update bgp4Update = new BGP4Update(msg);
							log.debug(bgp4Update.toString());
							bgp4Update.setLearntFrom(this.remotePeerIP.getHostAddress() );
							updateDispatcher.dispatchRequests(bgp4Update);
						}
						else
							log.debug("Update message from " + this.remotePeerIP + " discarded");
						break;

					default:
						log.warn("ERROR: unexpected message received");
						bgp4Msg = false;
					}

					if (bgp4Msg) {
						//Reseting Dead Timer as BGP4 Session Message has arrived
						resetDeadTimer();
					}
				} 
			}
		}finally{
			//log.error("SESSION "+ internalSessionID+" IS KILLED");
			log.info("BGP4 session with peer "+this.remotePeerIP+" has been closed");
			cancelDeadTimer();
			cancelKeepAlive();
			this.FSMstate=BGP4StateSession.BGP4_STATE_IDLE;
			endSession();
		}
	}
	

	public int getPeerBGP_port() {
		return peerBGP_port;
	}
	public void setPeerBGP_port(int peerBGP_port) {
		this.peerBGP_port = peerBGP_port;
	}
	public Boolean getUpdateFrom() {
		return updateFrom;
	}
	public void setUpdateFrom(Boolean updateFrom) {
		this.updateFrom = updateFrom;
	}
	public Boolean getSendTo() {
		return sendTo;
	}
	public void setSendTo(Boolean sendTo) {
		this.sendTo = sendTo;
	}
	public boolean isNo_delay() {
		return no_delay;
	}
	public void setNo_delay(boolean no_delay) {
		this.no_delay = no_delay;
	}


	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}


	@Override
	protected void endSession() {
		// TODO Auto-generated method stub
		log.debug("Ending session with id "+this.getSessionId());
		this.BGP4SessionsInformation.deleteSession(this.getSessionId());
	}

}
