// 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.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Timer;

/**
 * BGP4 session server
 * 
 * @author mcs
 *
 */
public class BGP4PeerInitiatedSession extends GenericBGP4Session{
	/**
	 * 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;


	/**
	 * Constructor of the BGP4 Session
	 * @param s Socket of the BGP4Peer-BGP4Peer Communication
	 * @param bgp4SessionsInformation bgp4SessionsInformation
	 * @param updateDispatcher updateDispatcher
	 * @param holdTime holdTime
	 * @param BGPIdentifier BGPIdentifier
	 * @param version version
	 * @param myAutonomousSystem myAutonomousSystem
	 * @param noDelay noDelay
	 * @param keepAliveTimer keepAliveTimer
	 */
	public BGP4PeerInitiatedSession(Socket s, BGP4SessionsInformation bgp4SessionsInformation, UpdateDispatcher updateDispatcher,int holdTime,Inet4Address BGPIdentifier,int version,int myAutonomousSystem,boolean noDelay,int keepAliveTimer ){
		super(bgp4SessionsInformation, holdTime, BGPIdentifier, version, myAutonomousSystem,keepAliveTimer);

		this.setFSMstate(BGP4StateSession.BGP4_STATE_IDLE);
		log=LoggerFactory.getLogger("BGP4Server");
		log.debug("New BGP4Session: "+s);
		this.socket = s;
		try {
			s.setTcpNoDelay(noDelay);
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.newSessionId();

		this.remotePeerIP =(Inet4Address) ((InetSocketAddress) socket.getRemoteSocketAddress()).getAddress();

			
		timer=new Timer();
		this.updateDispatcher = updateDispatcher;
		//this.keepAliveLocal=params.getKeepAliveTimer();
		//this.deadTimerLocal=params.getDeadTimer();
	}

	

	/**
	 * Initiates a Session the BGP-4 Peers
	 */
	public void run() {
		try {
			initializeBGP4Session();
		} catch (BGP4Exception e2) {
			// TODO Auto-generated catch block
			try {
				this.socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return;
		}
		log.info("BGP4 Session established with peer "+this.remotePeerIP);
		this.deadTimerT=new DeadTimerThread(this, this.holdTime);
		startDeadTimer();	
		this.keepAliveT=new KeepAliveThread(out, this.keepAliveTimer);
		startKeepAlive();

		//Listen to new messages
		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("Exception Closing BGP4 Session with "+this.remotePeerIP);
					}
					log.debug("Finishing BGP4 Session with "+this.remotePeerIP);
					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("OPEN message received");
						//After the session has been started, ignore subsequent OPEN messages
						log.warn("OPEN message ignored");
						break;

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

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

					case BGP4MessageTypes.MESSAGE_UPDATE:
						log.debug("UPDATE message from "+this.remotePeerIP);						
						BGP4Update bgp4Update = new BGP4Update(msg);
						log.debug(bgp4Update.toString());
						bgp4Update.setLearntFrom(this.getRemotePeerIP().toString());
						updateDispatcher.dispatchRequests(bgp4Update);
						break;

					default:
						log.warn("ERROR: unexpected message from "+this.remotePeerIP);
						bgp4Msg = false;
					}

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

	@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()+" from peer "+this.remotePeerIP);
		BGP4SessionsInformation.deleteSession(this.getSessionId());
	}

}
