package in.cdac.medinfo.csnotk.csnolib.disb.agent;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;

import com.google.gson.Gson;
import com.google.gson.stream.MalformedJsonException;

import in.cdac.medinfo.csnotk.csnolib.commons.Constants;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumProperties;
import in.cdac.medinfo.csnotk.csnolib.disb.api.IDISBAgent;
import in.cdac.medinfo.csnotk.csnolib.disb.common.DISBContants;
import in.cdac.medinfo.csnotk.csnolib.disb.common.EnumSemantic;
import in.cdac.medinfo.csnotk.csnolib.disb.db.HttpConnection;
import in.cdac.medinfo.csnotk.csnolib.disb.model.GenericDrug;
import in.cdac.medinfo.csnotk.csnolib.disb.model.MedicineDetail;
import in.cdac.medinfo.csnotk.csnolib.disb.model.SubstanceDetail;
import in.cdac.medinfo.csnotk.csnolib.util.PropertyReader;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;


/**
 * A concrete implementation of {@link IDISBAgent}
 * Library for application. It provides the basic methods to
 * query the DISB for retrieving JSON for known concept identifiers.
 *
 * @see SubstanceDetail
 * @see MedicineDetail
 * @see GenericDrug
 */
public class DISBAgent implements IDISBAgent {

	private static final long serialVersionUID = 1L;
	private static Gson gson = new Gson();
	private static String protocol="";
	private static String hostname="";
	private static String portNumber="";
	private static String context="";
	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Substance
	 * @return JSON string of {@link SubstanceDetail} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public String getSubstanceDetail(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.SUBSTANCE));
			 	return getResponseString(conn);
	        } catch (Exception e) {
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}

	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Substance
	 * @return {@link SubstanceDetail} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public SubstanceDetail getSubstanceDetailObj(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.SUBSTANCE));
			 	return (SubstanceDetail)getObjectFromJSON(getResponseString(conn),EnumSemantic.SUBSTANCE);
	        } catch (Exception e){
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}


	/**
	 * Used for converting json to java object of {@link SubstanceDetail}, {@link MedicineDetail}, {@link GenericDrug} model
	 * @param responseJSONString
	 * @param semantic
	 * @return object of Substance,Medicine,Generic model
	 * @throws MalformedJsonException
	 */
	private Object getObjectFromJSON(String responseJSONString,EnumSemantic semantic)throws MalformedJsonException{
		if(semantic.getValue().equals(EnumSemantic.SUBSTANCE.getValue())){
		SubstanceDetail[] substanceDetails=gson.fromJson(responseJSONString,SubstanceDetail[].class);
        if(substanceDetails!=null){
        	if(substanceDetails.length>0)
        		return substanceDetails[0];
        		}
		}else if(semantic.getValue().equals(EnumSemantic.MEDICINE.getValue())){
			MedicineDetail[] medicineDetails=gson.fromJson(responseJSONString,MedicineDetail[].class);
	        if(medicineDetails!=null){
	        	if(medicineDetails.length>0)
	        		return medicineDetails[0];
	        }
		}else if(semantic.getValue().equals(EnumSemantic.GENERIC.getValue())){
	 		GenericDrug[] genericDrugDetails=gson.fromJson(responseJSONString,GenericDrug[].class);
	        if(genericDrugDetails!=null){
	        	if(genericDrugDetails.length>0)
	        		return genericDrugDetails[0];
	        }
	     }
		return null;
	}

	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Substance
	 * @return JSON string of {@link MedicineDetail} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public String getMedicineDetail(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.MEDICINE));
			 	return getResponseString(conn);
	        } catch (Exception e) {
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}

	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Medicine
	 * @return {@link MedicineDetail} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public MedicineDetail getMedicineDetailObj(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.MEDICINE));
			 	return (MedicineDetail)getObjectFromJSON(getResponseString(conn),EnumSemantic.MEDICINE);
	        } catch (Exception e){
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}


	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Generic
	 * @return JSON string of {@link GenericDrug} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public String getGenericDetail(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.GENERIC));
			 	return getResponseString(conn);
	        } catch (Exception e) {
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}

	/**
	 * Retrives data from DISB
	 * @param conceptId SNOMED CT concept identifier for Generic
	 * @return {@link SubstanceDetail} model
	 * @throws IllegalArgumentException if invalid concept id is  passed
	 */
	@Override
	public GenericDrug getGenericDetailObj(String conceptId)throws IllegalArgumentException{
        Logger logger = LogManager.getLogger(DISBAgent.class);
		validateConceptId(conceptId);
		HttpURLConnection conn=null;
		 try {
			 	conn=HttpConnection.getHttpURLConnection(getDISBURL(conceptId,EnumSemantic.GENERIC));
			 	return (GenericDrug)getObjectFromJSON(getResponseString(conn),EnumSemantic.GENERIC);
	        } catch (Exception e){
	            logger.error(LoggerUtility.DISBException, LoggerUtility.createErrorMessage(e));
	        }finally{
	        	HttpConnection.disconnectHttpConnection(conn);
	        }
		 return null;
	}


	/**
	 * Method will return response into String.
	 * @param httpUrlConnection {@link HttpURLConnection}
	 * @return {@link String}, containing response
	 * @throws IOException - if error occurs in input/output operation.
	 */
	private String getResponseString(HttpURLConnection httpUrlConnection)throws IOException{
		InputStreamReader in = new InputStreamReader(httpUrlConnection.getInputStream());
        BufferedReader br = new BufferedReader(in);
        String output="";
        while ((output = br.readLine()) != null){
        	return output;
        }
		return output;
	}

	/**
	 * Validating SNOMED CT concept identifier
	 * @param conceptId SNOMED CT concept identifier
	 * @throws IllegalArgumentException
	 */
	private void validateConceptId(String conceptId)throws IllegalArgumentException{
		if(null==conceptId){
			throw new IllegalArgumentException("conceptId cannot be null");
		}else if (!conceptId.matches("[0-9]*")){
			throw new IllegalArgumentException("conceptId: "+conceptId+" cannot be alphanumeric");
		}else if (conceptId.length()<6 || conceptId.length()>18){
			throw new IllegalArgumentException("conceptId: "+conceptId+" is not valid");
		}else if (!(conceptId.substring(conceptId.length()-2,conceptId.length()-1).equals("0"))){
			throw new IllegalArgumentException("conceptId: "+conceptId+" is not valid");
		}
	}

	/**
	 * Return DISB url
	 * @param conceptId SNOMED CT identifier
	 * @param semantic EnumSemantic for Substance, Medicine, Generic
	 * @return {@link String} url of DISB
	 * @throws NumberFormatException
	 * @throws IllegalArgumentException
	 */
	private String getDISBURL(String conceptId,EnumSemantic semantic)throws NumberFormatException,IllegalArgumentException{
		PropertyReader.loadSystemProperties();
		protocol=PropertyReader.getProperty(DISBContants.PROPERTY_PROTOCOL);
		hostname=PropertyReader.getProperty(DISBContants.PROPERTY_HOSTNAME);
		context=PropertyReader.getProperty(DISBContants.PROPERTY_CONTEXT);
		portNumber=PropertyReader.getProperty(DISBContants.PROPERTY_PORT);
		//validate given input
		if(conceptId==null)
			throw new IllegalArgumentException("ConceptId cannot be null");
		if(conceptId.isEmpty())
			throw new IllegalArgumentException("ConceptId cannot be empty");

		//prepare url for repspective semantic tag
			if(portNumber.isEmpty())
				 return protocol+DISBContants.COLON+DISBContants.FORWARDSLASH+DISBContants.FORWARDSLASH+hostname+DISBContants.FORWARDSLASH+context+DISBContants.FORWARDSLASH+semantic.getValue()+DISBContants.FORWARDSLASH+conceptId;
			else
				 return protocol+DISBContants.COLON+DISBContants.FORWARDSLASH+DISBContants.FORWARDSLASH+hostname+DISBContants.COLON+portNumber+DISBContants.FORWARDSLASH+context+DISBContants.FORWARDSLASH+semantic.getValue()+DISBContants.FORWARDSLASH+conceptId;
		}

	/**
	 * The method will configure the properties to default values.<br>
	 * Protocol for sending request to DISB (default protocol: http).<br>
	 * Hostname for remote application DISB (default hostname: localhost).<br>
	 * Port for accessing on port to DISB (default port: 9090).<br>
	 * Context for requesting to DISB (default context: dis).<br>
	 * Location for logs. (default errorLogDir: user directory).<br>
	 * */
	@Override
	public void init(){
		init(DISBContants.DEFAULT_PROTOCOL, DISBContants.DEFAULT_HOSTNAME,DISBContants.DEFAULT_PORT,DISBContants.DEFAULT_CONTEXT, EnumProperties.ERROR_LOG_PATH.getValue());
	}


	/**
	 * The method will configure properties by values provided in parameters
	 * @param protocol for sending request to DISB (default: http)
	 * @param hostname for remote application DISB (default: localhost)
	 * @param port for accessing on port to DISB (default: 9090)
	 * @param context for requesting to DISB (default: dis)
	 * @param errorLogDir location for logs. (default: user directory/ same directory which have given at the time of configuring csnolib)
	 */
	@Override
	public void init(String protocol, String hostname, Integer port ,String context,String errorLogDir){
		PropertyReader.loadSystemProperties();

		if(null==protocol)
			protocol=DISBContants.DEFAULT_PROTOCOL;
		else if(protocol.isEmpty())
			protocol=DISBContants.DEFAULT_PROTOCOL;
		PropertyReader.setProperty(DISBContants.PROPERTY_PROTOCOL,protocol);

		if(null==hostname)
			hostname=DISBContants.DEFAULT_HOSTNAME;
		else if(hostname.isEmpty())
			hostname=DISBContants.DEFAULT_HOSTNAME;
		PropertyReader.setProperty(DISBContants.PROPERTY_HOSTNAME,hostname);

		if(null==port)
			port=DISBContants.DEFAULT_PORT;
		PropertyReader.setProperty(DISBContants.PROPERTY_PORT,port.toString());

		if(null==context)
			context=DISBContants.DEFAULT_CONTEXT;
		else if(context.isEmpty())
			context=DISBContants.DEFAULT_CONTEXT;
		PropertyReader.setProperty(DISBContants.PROPERTY_CONTEXT,context);

		//for taking CSNOLib directory
		if(null==errorLogDir)
			errorLogDir=EnumProperties.ERROR_LOG_PATH.getValue();
		else if(errorLogDir.isEmpty())
			errorLogDir=EnumProperties.ERROR_LOG_PATH.getValue();
		File file=new File(errorLogDir);
		if(!file.exists()){
			errorLogDir=file.getAbsolutePath();
		}
		PropertyReader.setProperty(Constants.ERROR_LOG_DIR, errorLogDir);
        //Setting up system variable 'error_log_dir' for log4j
		System.setProperty("error_log_dir", errorLogDir);

	}

	/**
	 * For testing connnection with DISB on entered init properties.
	 * If init() is being used then it will use default properties for DISB.
	 * @throws IllegalArgumentException
	 */
	public void testDISBConnection()throws IllegalArgumentException{
		PropertyReader.loadSystemProperties();
		if(this.getSubstanceDetail("387517004")==null){
			throw new IllegalArgumentException("Unable to access DISB data, please enter valid protocol,hostname,port,context\n"
					+ "Make sure DISB is in running state and accessible on provided properties\n"
					+ "");
		}

	}

}
