// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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.controllers;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import com.metaparadigm.jsonrpc.JSONSerializer;

import eu.teraflow.tid.tedb.DomainTEDB;
import eu.teraflow.tid.tedb.IntraDomainEdge;
import eu.teraflow.tid.tedb.SimpleTEDB;
import eu.teraflow.tid.tedb.TE_Information;
import eu.teraflow.tid.tedb.controllers.TEDUpdaterController.MyEdge;
import eu.teraflow.tid.tedb.elements.RouterInfoPM;

public class TEDUpdaterTREMA extends TEDUpdaterController
{
	public static String controllerName = "TREMA";
	private Hashtable<Integer,MyEdge> interDomainLinks = new Hashtable<Integer,MyEdge>();
	
	
	//Overwritten father variables and fixing the topology urls in code
	private String topologyPathNodes = "/get_topology/";
	private String topologyPathLinks = "/get_graph/";//??
	
	public TEDUpdaterTREMA(String ip, String port, String topologyPathLinks, String topologyPathNodes,DomainTEDB ted, Logger log)
	{
		super( ip,  port,  topologyPathLinks,  topologyPathNodes, ted,  log);
	}
	
	public TEDUpdaterTREMA(String ip, String port, String topologyPathLinks, String topologyPathNodes,DomainTEDB ted, Logger log, Lock lock)
	{
		super( ip,  port,  topologyPathLinks,  topologyPathNodes, ted,  log,  lock);
	}
	
	public TEDUpdaterTREMA(ArrayList<String> ips, ArrayList<String>ports , String topologyPathLinks, String topologyPathNodes,DomainTEDB ted, Logger log)
	{
		super(ips, ports ,  topologyPathLinks,  topologyPathNodes, ted,  log);
	}
	
	
	@Override
	public void run()
	{	
		
		if(interDomainFile != null)
		{
			interDomainLinks = TEDUpdaterController.readInterDomainFile(interDomainFile);
		}
		
		String responseLinks = "";
		String responseNodes = "";
		
		try
		{
			
			Hashtable<String,RouterInfoPM> nodes = new Hashtable<String,RouterInfoPM>();
			
			for (int i = 0; i < ips.size(); i++)
			{
				responseNodes = queryForNodes(ips.get(i), ports.get(i));//query for topology
				parseNodes(responseNodes, nodes, ips.get(i), ports.get(i));
				log.info("responseNodes:::"+responseNodes);
			}
			
		}
		catch (Exception e)
		{
			log.info(e.toString());
		}
	}
	
	
	private String KDDItoFloodlight(String BristolFormat)
	{
		String floodFormat = new String(BristolFormat);
		//Por algo se me conoce como el hacker
		for (int i = 2; i < floodFormat.length(); i += 3)
		{
			floodFormat =  floodFormat.substring(0, i) + ":" + floodFormat.substring(i, floodFormat.length());
		}
		log.info("BristolFormat--> " + BristolFormat + ", floodFormat-->" + floodFormat);
		return floodFormat;
	}

	private void parseNodes(String response, Hashtable<String,RouterInfoPM> routerInfoList, String ip, String port)
	{	
		try
		{
			JSONParser parser = new JSONParser();
			Object obj = parser.parse(response);
		
			JSONObject jsonaux=(JSONObject) obj;
			String host=(String)jsonaux.get("hostConfig").toString();
			obj=parser.parse(host);
			JSONObject jsonhost=(JSONObject) obj;
			
			//FIXME: This info has been taken from TREMA controller but is not used. Take a look if it's necessary
			String OPS_Label=(String)jsonhost.get("OPS label");
			String Port_id=(String)jsonhost.get("Port_id");
			String reach_nodes=(String)jsonhost.get("Reachable nodes");
			String node_type=(String)jsonhost.get("nodeType");//Used for RouterType
			
			RouterInfoPM rInfo = new RouterInfoPM();
			String dpid=((String)jsonhost.get("Virtual_node_id")).replace("-", ":");
			rInfo.setRouterID(dpid);//Bristol to floodlight?
			rInfo.setConfigurationMode("Openflow");
			rInfo.setControllerType(TEDUpdaterNOX.controllerName);
			rInfo.setRouterType(node_type);
			
			rInfo.setReachable_nodes(parseReachability(reach_nodes));
			
			rInfo.setControllerIdentifier(ip, port);
			rInfo.setControllerIP(ip);
			rInfo.setControllerPort(port);
			
			routerInfoList.put(rInfo.getRouterID(),rInfo);
			((SimpleTEDB)TEDB).getNetworkGraph().addVertex(rInfo);
			//Getting Links
			//parseLinks(???);
			//parseLinks(dpid, jsonarray)
		}
		catch (Exception e)
		{
			log.info(e.toString());
		}
	}
	
	private LinkedList<String> parseReachability(String reach_nodes) {
		boolean end=false;
		int beginIndex=0;
		LinkedList<String> nodes= new LinkedList<String>();
		reach_nodes=reach_nodes.replace(" ", "");
		System.out.println("Reachable Nodes:"+reach_nodes);
		while (!end){
			int pointer=reach_nodes.indexOf(",");
			if (pointer==-1)
				end=true;
			else {
				String node=reach_nodes.substring(beginIndex, pointer);
				System.out.println("Reachable Node:"+node);
				reach_nodes=reach_nodes.substring(pointer+1);
				nodes.add(node);
			}
		}
		if (reach_nodes.length()>0){
			System.out.println("Reachable Node:"+reach_nodes);
			nodes.add(reach_nodes);
		}
		return nodes;
	}

	private void parseLinks(String dpid,String links, Hashtable<String,RouterInfoPM> nodes) //NOX Parse Links. it's not used in TREMA
	{
		try {
			//log.info("Inside parseJSON");
			JSONParser parser = new JSONParser();
			Object obj = parser.parse(links);
		
			JSONArray msg = (JSONArray) obj;
			Iterator<JSONObject> iterator = msg.iterator();
			while (iterator.hasNext()) 
			{
				JSONObject jsonObject = (JSONObject) iterator.next();
				//System.out.println(jsonObject.get("src-switch"));
				IntraDomainEdge edge= new IntraDomainEdge();
				
				String labels = (String) jsonObject.get("labels");//??
				String destnode = ((String) jsonObject.get("peer_node")).replace("-", ":");
				String type = (String) jsonObject.get("port_type");

				RouterInfoPM source = nodes.get(dpid);
				RouterInfoPM dest = nodes.get(destnode);
					
				//((SimpleTEDB)TEDB).getNetworkGraph().addVertex(source);
				//((SimpleTEDB)TEDB).getNetworkGraph().addVertex(dest);
				
				System.out.println("Srcnode: "+dpid);
				System.out.println("Dstnode: "+destnode);
				
				System.out.println("Srcport: "+jsonObject.get("port_id"));
				System.out.println("Dstport: "+jsonObject.get("peer_port"));
				
				if ((dest!= null)&&(source!=null)){
					
				
					edge.setSrc_if_id(Long.parseLong((String)jsonObject.get("port_id")));
					edge.setDst_if_id(Long.parseLong((String)jsonObject.get("peer_port")));
					
					edge.setType(type);
					
					// This is a big problem because info is not initialized from file
					// and the controller doesn't give information about how many wlans
					// the are
					
					TE_Information tE_info = new TE_Information();
					System.out.println("Labels: Original ("+labels+") and Parsed ("+labels.substring(labels.indexOf(",")+1,labels.indexOf("]"))+")");
					tE_info.setNumberWLANs(Integer.parseInt(labels.substring(labels.indexOf(",")+1,labels.indexOf("]"))));
					tE_info.initWLANs();
					
					if (interDomainFile != null)
					{
						completeTE_Information(tE_info, source.getRouterID(), dest.getRouterID());
					}
					
					edge.setTE_info(tE_info);
					
					
					edge.setDirectional(true);
					//Bidirectional case? See other TEDUpdaters.
				
					((SimpleTEDB)TEDB).getNetworkGraph().addEdge(source, dest, edge);
				
				} else {
					System.out.println("Link with an unknown node. Ingnoring...");
				}
				//log.info("Edge added:"+edge);
				//log.info(((SimpleTEDB)TEDB).getIntraDomainLinks().toString());
			}
			//parseRemainingLinksFromXML(nodes);
	 
	 
		} 
		catch (Exception e)
		{
			log.info(e.toString());
		}
	}

	
//	private void parseLinks(String links,Hashtable<String,RouterInfoPM> nodes)
//	{
//		try {
//			//log.info("Inside parseJSON");
//			JSONParser parser = new JSONParser();
//			Object obj = parser.parse(links);
//		
//			JSONArray msg = (JSONArray) obj;
//			Iterator<JSONObject> iterator = msg.iterator();
//			while (iterator.hasNext()) 
//			{
//				JSONObject jsonObject = (JSONObject) iterator.next();
//				//System.out.println(jsonObject.get("src-switch"));
//				IntraDomainEdge edge= new IntraDomainEdge();
//				
//				JSONObject jsonObject_src = (JSONObject) jsonObject.get("src");
//				JSONObject jsonObject_dst = (JSONObject) jsonObject.get("dst");
//
//				RouterInfoPM source = nodes.get(BristoltoFloodlight((String)jsonObject_src.get("dpid")));
//				RouterInfoPM dest = nodes.get(BristoltoFloodlight((String)jsonObject_dst.get("dpid")));
//					
//				//((SimpleTEDB)TEDB).getNetworkGraph().addVertex(source);
//				//((SimpleTEDB)TEDB).getNetworkGraph().addVertex(dest);
//
//				log.info("Adding Vertex->"+source+" hashcode:"+source.hashCode());
//				log.info("Adding Vertex->"+dest+" hashcode:"+dest.hashCode());
//				
//				edge.setSrc_if_id((Long)jsonObject_src.get("port_no"));
//				edge.setDst_if_id((Long)jsonObject_dst.get("port_no"));
//				
//				
//				// This is a big problem because info is not initialized from file
//				// and the controller doesn't give information about how many wlans
//				// the are
//				
//				TE_Information tE_info = new TE_Information();
//				tE_info.setNumberWLANs(15);
//				tE_info.initWLANs();
//				
//				if (interDomainFile != null)
//				{
//					completeTE_Information(tE_info, source.getRouterID(), dest.getRouterID());
//				}
//				
//				edge.setTE_info(tE_info);
//				
//				String isBidirectional = (String)jsonObject.get("direction");
//				
//				
//				// En Bristol los enlaces son unidirecctionales, pero asumimos que hay uno en una direccion
//				// esta el otro
//				isBidirectional = "bidirectional";
//				
//				//log.info("isBidirectional::"+isBidirectional);
//				
//				if ((1==1)||(isBidirectional != null) && (isBidirectional.equals("bidirectional")))
//				{
//					//((SimpleTEDB)TEDB).getNetworkGraph().addEdge(source, dest, edge);
//					
//					TE_Information tE_infoOtherWay = new TE_Information();
//					tE_infoOtherWay.setNumberWLANs(15);
//					tE_infoOtherWay.initWLANs();
//					IntraDomainEdge edgeOtherWay= new IntraDomainEdge();
//					
//					edgeOtherWay.setSrc_if_id((Long)jsonObject_src.get("port_no"));
//					edgeOtherWay.setDst_if_id((Long)jsonObject_dst.get("port_no"));
//					edgeOtherWay.setTE_info(tE_infoOtherWay);
//					
//					((SimpleTEDB)TEDB).getNetworkGraph().addEdge(source, dest, edge);
//					((SimpleTEDB)TEDB).getNetworkGraph().addEdge(dest, source, edgeOtherWay);
//					
//					completeTE_Information(tE_info, dest.getRouterID(), source.getRouterID());
//					
//					log.info("source::"+source);
//					log.info("dest::"+dest);
//					log.info("edgeOtherWay::"+edgeOtherWay);
//					log.info("edge::"+edge);
//					//log.info("Adding two!");
//				}
//				else
//				{
//					((SimpleTEDB)TEDB).getNetworkGraph().addEdge(source, dest, edge);
//				}
//				
//				//log.info("Edge added:"+edge);
//				//log.info(((SimpleTEDB)TEDB).getIntraDomainLinks().toString());
//			}
//			//parseRemainingLinksFromXML(nodes);
//	 
//	 
//		} 
//		catch (Exception e)
//		{
//			log.info(UtilsFunctions.exceptionToString(e));
//		}
//	}

	private void completeTE_Information(TE_Information tE_info, String source, String dest) 
	{
		MyEdge auxEdge = new MyEdge(source, dest);
		MyEdge completEdge = interDomainLinks.get(auxEdge.hashCode());
		if ((completEdge != null)&&(completEdge.vlan != null))
		{
			tE_info.setVlanLink(true);
			tE_info.setVlan(completEdge.vlan);
			//If it has been found it will be removed so the rest can be proccessed later
			interDomainLinks.remove(completEdge.vlan);
		}
		else
		{
			tE_info.setVlanLink(false);
		}
	}

	private String queryForLinks(String ip, String port)
	{
		String response = ""; 
		try
		{
			URL topoplogyURL = new URL("http://"+ip+":"+port+topologyPathLinks);
			
			log.info("URL::"+"http://"+ip+":"+port+topologyPathLinks);
			
	        URLConnection yc = topoplogyURL.openConnection();
	        BufferedReader in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
	        String inputLine;
	
	        while ((inputLine = in.readLine()) != null) 
	        {
	        	response = response + inputLine;
	        }
		}
		catch (Exception e)
		{
			log.info(e.toString());
		}
        return response;
	}
	
	private String queryForNodes(String ip, String port)
	{
        String response = "";
		try
		{
			URL topoplogyURL = new URL("http://"+ip+":"+port+topologyPathNodes);
			
			log.info("http://+port+topologyPathNodes:::"+"http://"+ip+":"+port+topologyPathNodes);
			
	        URLConnection yc = topoplogyURL.openConnection();
	        BufferedReader in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
	        
	        String inputLine;	
	        while ((inputLine = in.readLine()) != null) 
	        {
	        	response = response + inputLine;
	        }
	        in.close();
		}
		catch (Exception e)
		{
			log.info(e.toString());
		}
        return response;
	}
	
	public String getInterDomainFile() 
	{
		return interDomainFile;
	}

	public void setInterDomainFile(String interDomainFile) 
	{
		this.interDomainFile = interDomainFile;
	}
	
	
	private void lock()
	{
		if (lock != null)
		{
			lock.lock();
		}
	}
	
	private void unlock()
	{
		if (lock != null)
		{
			lock.unlock();
		}
	}
}

