/*******************************************************************************
 * Copyright 2014 Centre for Development of Advanced Computing(C-DAC), Pune
 *
 * 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 in.cdac.medinfo.csnotk.csnolib.agents;

import in.cdac.medinfo.csnotk.csnolib.db.QueryManager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;
/**
 * Creates transitive closure table
 * from relationship file.
 */
public class ClosureBuilderAgent
{
	/**
	 * <code>relFile</code> refers to the path of the relationship file.
	 */
	private String relFile;
	/**
	 * Concept Id for IS A relationship.
	 */
	private String isRel = "116680003";
	/**
	 * <code>inferredCharType</code> refers to Inferred relationship.
	 */
	private String inferredCharType = "900000000000011006";
	/**
	 * <code>root</code> refers to the root Concept Id
	 * i.e Concept identifier of SNOMED CT Concept (SNOMED RT+CTV3).
	 */
	//private String root = "138875005";
	/**
	 * Creates instance of {@link QueryManager}.
	 */
	QueryManager qm = new QueryManager();
    //Logger object
    private static final Logger logger = LogManager.getLogger(ClosureBuilderAgent.class);


	/**
	 * Default constructor.
	 */
	public ClosureBuilderAgent()
	{

	}
	/**
	 * Sets the relationship file path in ClosureBuilderAgent.
	 * @param relFile
	 * 				 path of the relationship file.
	 */
	public void setrelFile(String relFile)
	{
		this.relFile = relFile;
	}

	/**
	 * Returns all  descendants of a given concept.
	 * @param parent
	 * 				 concept Id of the parent concept whose descendants are to be retrieved.
	 * @param seen
	 * 				 Set of concept Ids of the parent concepts that have already been parsed.
	 * @param parChd
	 * 				 Map of parent-child relationships.
	 * @return list of {@link String} which consists of all the descendants corresponding
	 * to the specified Concept Id.
	 */
	private Set<String> getAllDescendants(String parent, Set<String> seen,	Map<String, Set<String>> parChd)
	{
		if (seen.contains(parent))
		{
			return new HashSet<>();
		}
		seen.add(parent);
		Set<String> children = parChd.get(parent);
		if (children == null || children.isEmpty())
		{
			return new HashSet<>();
		}
		Set<String> descendants = new HashSet<>();
		for (String chd : children)
		{
			descendants.add(chd);
			Set<String> grandChildren = getAllDescendants(chd, seen, parChd);
			for (String grandChidl : grandChildren)
			{
				descendants.add(grandChidl);
			}
		}
		return descendants;
	}

	/**
	 * Returns immediate  descendants of a given concept.
	 * @param parent
	 * 				 concept Id of the parent concept whose descendants are to be retrieved.
	 * @param parChd
	 * 				 Map of parent-child relationships.
	 * @return list of {@link String} which consists of immediate the descendants corresponding
	 * to the specified Concept Id.
	 */
	private Set<String> getImmediateDescendants(String parent,	Map<String, Set<String>> parChd)
	{

		Set<String> childrens = parChd.get(parent);
		if (childrens == null || childrens.isEmpty())
		{
			return new HashSet<>();
		}
		Set<String> descendants = new HashSet<>();
		for (String chd : childrens)
		{
			 descendants.add(chd);

		}
		return descendants;
	}

	/**
	 * Returns all descendants of a given concept.
	 * @param parent
	 * 				 concept Id of the parent concept whose descendants are to be retrieved.
	 * @param parChd
	 * 				 Map of parent-child relationships.
	 * @return list of {@link String} which consists of all the descendants corresponding
	 * to the specified Concept Id.
	 */
	private Set<String> getCountOfDescendants(String parent,Map<String, Set<String>> parChd)
	{

		Set<String> children = parChd.get(parent);
		if (children == null || children.isEmpty())
		{
			return new HashSet<>();
		}
		Set<String> descendants = new HashSet<>();
		for (String chd : children)
		{
			descendants.add(chd);
			Set<String> grandChildren = getCountOfDescendants(chd, parChd);
			for (String grandChidl : grandChildren)
			{
				descendants.add(grandChidl);
			}
		}
		return descendants;
	}

	/**
	 * Implementation to build transitive closure table during DB creation.
	 * @param forExtensionDb boolean value indicating closure table is being built for Extension
	 * @throws Exception in any error occurs.
	 */
	public void buildClosureTable(boolean forExtensionDb) throws Exception
	{
		boolean setHeader=true;
		if (relFile == null) {
			logger.error(LoggerUtility.ConsoleWithException, "Unexpected null relationships file.");
			throw new Exception("Unexpected null relationships file.");
		}

		File rf = new File(relFile);
		File closureTemp = File.createTempFile("closure", ".tmp");
		if (!rf.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, "relationships file does not exist.");
			throw new Exception("relationships file does not exist.");
		}

		BufferedReader in = new BufferedReader(new FileReader(rf));
		PrintWriter out = new PrintWriter(new FileWriter(closureTemp));

		String line;
		Map<String, Set<String>> parChd = new HashMap<>();
		// skip header
		// In case of Extensions, remove " from first line
		if( forExtensionDb )
			line = in.readLine().replaceAll("\"", "").toLowerCase();
		else
			line = in.readLine();

		if (!line.startsWith("id"))
		{
			in.close();
			out.flush();
			out.close();
			logger.error(LoggerUtility.ConsoleWithException, "Unknown file format, not RF2.");
			throw new Exception("Unknown file format, not RF2.");
		}

		logger.info(LoggerUtility.ConsoleWithLog, "Have patience, transitive closure is being generated for SNOMED CT codes..");

		while ((line = in.readLine()) != null)
		{
			String[] tokens = line.split("\t");

			// In case of Extensions, remove " from start and end of token
			if( forExtensionDb ) {
				int index = 0;
				for( String singleToken : tokens ) {
					tokens[index++] = singleToken.replaceAll("^\"|\"$", "");
				}
			}

			String chd = null;
			String par = null;

			if (tokens.length != 10) {
				in.close();
				out.flush();
				out.close();
				logger.error(LoggerUtility.ConsoleWithException, "Unexpected format of release file {}", tokens.length);
				throw new Exception("Unexpected format of release file "
						+ tokens.length);
			}
			// Skip non isa
			if (!isRel.equals(tokens[7])) {
				continue;
			}
			// Skip inactive
			if (!tokens[2].equals("1"))
			{
				continue;
			}
			// skip non inferred rels
			if (!inferredCharType.equals(tokens[8]))
			{
				in.close();
				out.flush();
				out.close();
				logger.error(LoggerUtility.ConsoleWithException, "Unexpected non inferred relationship");
				throw new Exception("Unexpected non inferred relationship");
			}
			chd = tokens[4];
			par = tokens[5];

			// Add par/chd relationship
			if (par == null || par.isEmpty())
			{
				in.close();
				out.flush();
				out.close();
				logger.error(LoggerUtility.ConsoleWithException, "Empty parent {}", line);
				throw new Exception("Empty parent " + line);
			}
			if (chd == null || chd.isEmpty())
			{
				in.close();
				out.flush();
				out.close();
				logger.error(LoggerUtility.ConsoleWithException, "Empty child {}", line);
				throw new Exception("Empty child " + line);
			}
			if (!parChd.containsKey(par))
			{
				parChd.put(par, new HashSet<>());
			}
			Set<String> children = parChd.get(par);
			children.add(chd);

		}

		// INSERT TO TABLE PARENT CHILD CHILDDESCRIPTION
		for (String code : parChd.keySet())
		{
			Set<String> descs = getAllDescendants(code, new HashSet<>(), parChd);
			Set<String> ImmediateChildrens = getImmediateDescendants(code, parChd);
			for (String desc : descs)
			{
				Set<String> descChindrenCount = getCountOfDescendants(desc, parChd);
				if(setHeader){
					out.print("Parent\t" +"Child\t"+"CountOfChildren\t"+"IsImmediateChildren\r\n");
					setHeader=false;
				}
				if(ImmediateChildrens.contains(desc))
					out.print(code + "\t" + desc +"\t"+descChindrenCount.size()+"\t"+1+"\r\n");
				else
					out.print(code + "\t" + desc +"\t"+descChindrenCount.size()+"\t"+0+"\r\n");
				out.flush();
			}
		}
		in.close();
		out.flush();
		out.close();

		logger.info(LoggerUtility.ConsoleWithLog, "Loading SNOMED CT codes to closure table ....");

		new QueryManager().loadClosureTable(closureTemp);

		logger.info(LoggerUtility.ConsoleWithLog, "Cleaning temp......");

		boolean deleteStatus = closureTemp.delete();
		if( deleteStatus )
			logger.info(LoggerUtility.ConsoleWithLog, "Deleted temporary closure file");
	}
}
