package flow.utils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.servlet.ServletContext;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.MappingJsonFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.json.JSONObject;

import com.avaya.ingensg.ce.wa.imrest.common.model.MetricsResponse;
import com.avaya.ingensg.ce.wa.imrest.common.model.Service;
import com.avaya.ingensg.ce.wa.imrest.common.model.ServiceMetrics;
import com.avaya.sce.runtimecommon.ITraceInfo;
import com.avaya.sce.runtimecommon.SCESession;
import com.thoughtworks.xstream.XStream;

import flow.IProjectVariables;
import flow.constants.SSAConstants;
import flow.pojo.Attribute;
import flow.pojo.AttributeValue;
import flow.pojo.SelfService;

/**
 * Utility class that might be used for all others in the application
 */
public class Utils {

	public static Properties getPropertiesFile(ServletContext servletContext, SCESession session) throws IOException {
		Properties properties = new Properties();
	    InputStream ras = null;
	    try {
	        ras = servletContext.getResourceAsStream(SSAConstants.CONF_FILE_PROPERTIES);
	        properties.load(ras);
	    } catch (IOException e) {
			Utils.logInfo("Error loading properties file (" + SSAConstants.CONF_FILE_PROPERTIES + "): " + e.getMessage(), session);
			throw e;
	    } finally {
	        if (ras != null) {
	            try {
	                ras.close();
	            } catch (IOException e) {
	            }
	        }
	    }
		return properties;	
	}
	
	public static SelfService getConfigurationData(ServletContext servletContext, SCESession session) throws FileNotFoundException {
		SelfService result = null;
		XStream xstream = new XStream();
		xstream.alias("selfservice", SelfService.class);
		xstream.alias("attribute", Attribute.class);
		xstream.alias("value", AttributeValue.class);

		URL url = getFile(servletContext, session);
		if (url == null) {
			throw new FileNotFoundException("Error loading XML file (" + SSAConstants.CONF_FILE_XML + ")");
		}
		
		final Object fromXML = xstream.fromXML(url);
		result = (SelfService)fromXML;
		Utils.logInfo("Loaded Self Service configuration successfully", session);
		return result;
	}

	protected static URL getFile(ServletContext servletContext, SCESession session) {
		URL url = null;
		try {
			url = servletContext.getResource(SSAConstants.CONF_FILE_XML);
		}
		catch (Exception e) {
			Utils.logInfo("Error loading XML file (" + SSAConstants.CONF_FILE_XML + "): " + e.getMessage(), session);	
		}
		return url;
	}	
	
	/**
	 * Set value for a simple variable
	 */
	public static void setVariableField(String varName, Object value, com.avaya.sce.runtimecommon.SCESession session) {
		session.getVariableField(varName).setValue(value);
	}
	
	/**
	 * Set value for a complex variable field
	 */
	public static void setVariableField(String varName, String fieldName, Object value, com.avaya.sce.runtimecommon.SCESession session) {
		session.getVariable(varName).getComplexVariable().getField(fieldName).setValue(value);
	}	

	/**
	 * Get the string value of a simple variable
	 */	
	public static Object getVariableField(String varName, com.avaya.sce.runtimecommon.SCESession session) {
		return session.getVariableField(varName).getObjectValue();
	}
	
	/**
	 * Get the string value of a complex variable field
	 */
	public static String getVariableField(String varName, String fieldName,com.avaya.sce.runtimecommon.SCESession session) {
		return session.getVariable(varName).getComplexVariable().getField(fieldName).getStringValue();
	}
	
	/**
	 * Generate the customer attributes/properties string in the following format:
	 * 
	 * 		key:value;key:value
	 * 
	 * This is the format required by the Experience Manager REST Interface when
	 * passing attributes or properties
	 * 
	 */
	public static String generateKeyValueString(Map<String, String> map){
		
		StringBuilder result = new StringBuilder();
		
		for(Map.Entry<String, String> entry: map.entrySet()){
			result.append(entry.getKey());
			result.append(":");
			result.append(entry.getValue());
			result.append(";");
		}
		if(result.length() > 0){
			result.deleteCharAt(result.length() - 1);
		}
		
		return result.toString();
		
	}
	
	/**
	 * Log wrapper
	 */
	public static void logInfo(String info, com.avaya.sce.runtimecommon.SCESession mySession) {
		mySession.getTraceOutput().writeln(ITraceInfo.TRACE_LEVEL_DEBUG, info);
	}
	
	public static void logError(String info, com.avaya.sce.runtimecommon.SCESession mySession) {
		mySession.getTraceOutput().writeln(ITraceInfo.TRACE_LEVEL_ERROR, info);
	}
	
    public static String getContactID(
            com.avaya.sce.runtimecommon.SCESession mySession) {
       
    	String strContactId = "";
        String ucid = mySession.getVariable(IProjectVariables.SESSION)
                .getComplexVariable().getField(
                        IProjectVariables.SESSION_FIELD_UCID).getStringValue();

        if (ucid == null || "".equals(ucid) || "undefined".equals(ucid)) {
            String avpSessionId = mySession.getVariable(
                    IProjectVariables.SESSION).getComplexVariable().getField(
                    IProjectVariables.SESSION_FIELD_SESSIONID).getStringValue();
            
            if (avpSessionId == null || "".equals(avpSessionId)) {
                String uuid = UUID.randomUUID().toString();
                Utils.logInfo("Contact GUID = Random UUID [" + uuid + "]", mySession);
                strContactId = uuid;
            } else {
            	Utils.logInfo("Contact GUID = AVP Session ID [" + avpSessionId + "]", mySession);                
                strContactId = avpSessionId;
            }
        } else {
        	Utils.logInfo("Contact GUID = UCID [" + ucid + "]", mySession);            
            strContactId = ucid;
        }
        
        return strContactId;
    }		
	
    /**
     * Helper method to convert a HttpResponse object into a MetricsResponse. 
     * @param response - An object containing the response
     * @param session 
     * @return MetricsResponse the status and content of the response
     * @throws ResponseDataException when the data cannot be extracted
     */         
    public static MetricsResponse extractMetricsResponse(String response, SCESession session) throws IOException {
        final String methodName = "extractMetricsResponse"; 
        MetricsResponse node = null;
        try {
            JsonParser parser = new MappingJsonFactory().createJsonParser( response );
            node = parser.readValueAs(MetricsResponse.class);
        } catch (JsonParseException e) {
            final String errorMessage = "Parse Error occurred extracting MetricsResponse object";
			Utils.logError(errorMessage + ": " + e.getMessage(), session);
			throw e;
        } catch (IOException e) {
            final String errorMessage = "IO Error occurred extracting MetricsResponse object";
			Utils.logError(errorMessage + ": " + e.getMessage(), session);
			throw e;
        }
        return node; 
    }   	
    
    /**
     * Helper method to convert a serviceMap in JSON format to a Map of Service objects. 
     * @param jsonServiceMap - The incoming JSON serviceMap
     * @param session 
     * @return Map<Integer, Service> the returned Map
     */         
    public static Map<Integer, Service> convertStringToServiceMap(String jsonServiceMap, SCESession session) {    
        final String methodName = "convertStringToServiceMap";
        Map<Integer, Service> services = new LinkedHashMap<Integer, Service>();
        ObjectMapper mapper = new ObjectMapper();
        
        if (!Utils.isStringBlank(jsonServiceMap)) {        
            try {                     
                services = mapper.readValue(jsonServiceMap, 
                        new TypeReference<LinkedHashMap<Integer, Service>>(){});     
            } catch (Exception e) {  
                final String errorMessage = "Error generating ServiceMap from string " + jsonServiceMap;
    			Utils.logInfo(errorMessage + ": " + e.getMessage(), session);               
            }	 
        }        
        return services;
    }    
    
    /**
     * Helper method to convert a serviceMetricsMap in JSON format to a Map of ServiceMetrics objects. 
     * @param jsonServiceMetricsMap - The incoming JSON serviceMetricsMap
     * @param session 
     * @return Map<Integer, ServiceMetrics> the returned Map
     * @throws ResponseDataException when the data cannot be extracted
     */         
    public static Map<Integer, ServiceMetrics> convertStringToServiceMetricsMap(String jsonServiceMetricsMap, SCESession session) {    
        final String methodName = "convertStringToServiceMetricsMap";
        Map<Integer, ServiceMetrics> serviceMetrics = new LinkedHashMap<Integer, ServiceMetrics>();
        ObjectMapper mapper = new ObjectMapper();
     
        if (!isStringBlank(jsonServiceMetricsMap)) {                      
            try {     
                JSONObject jsonMapObj = new JSONObject(jsonServiceMetricsMap);                       
                String serviceMetricsMapValue = (String) jsonMapObj.getString(SSAConstants.WA_SERVICEMAP_TAG);                
                
                serviceMetrics = mapper.readValue(serviceMetricsMapValue, 
                        new TypeReference<LinkedHashMap<Integer, ServiceMetrics>>(){});                      
            } catch (Exception e) {  
                final String errorMessage = "Error generating ServiceMetricsMap from string " + jsonServiceMetricsMap;           
    			Utils.logInfo(errorMessage + ": " + e.getMessage(), session);           
            }
        }
        return serviceMetrics;
    }
    
    /**
     * Helper method to flag if a String has useful data or not. 
     * @param inputString - An object containing the request
     * @return true/ false
     */         
    public static boolean isStringBlank(String inputString) {
        if(inputString == null || inputString.isEmpty()){
            return true;
        }
        return false;
    }  
     
    /**
     * Helper method to return true if inputString is a number, else false 
     * @param inputString - An object containing the request
     * @return true/ false
     */       
    public static boolean isNumber(String inputString) 
    { 
        for (int i = 0; i < inputString.length(); i++) 
        if (Character.isDigit(inputString.charAt(i)) == false) 
            return false; 
  
        return true; 
    }     
    
	/**
	 * Format of UUI data expected from Oceana looks like:
	 * New call:
	 * PD,00;FA,4AAB07BD5C3369DB
	 * Transferred call:
	 * PD,00;C8,76366D69566A5271545771514A454638667052794C412C564F2C4E;F7,0014;F8,0107;F5,4F6365616E6120496E677265737320;F4,82889596;FA,4AAB05C75BACE75B
	 * The ASAI data looks like:
	 * v6miVjRqTWqQJEF8fpRyLA,VO,N
	 */
	public static String parseUserData(String encodedUUI,
			com.avaya.sce.runtimecommon.SCESession mySession) {
		
		String contextId = "";
		String channel = SSAConstants.UUI_CHANNEL_VOICE;

		try {		
			int start = encodedUUI.indexOf(SSAConstants.UUI_ASAI_START);
			int end = encodedUUI.indexOf(SSAConstants.UUI_ASAI_END, start + SSAConstants.UUI_ASAI_START.length());
	
			//Exit if not found
			if (start < 0) {
				return contextId;
			}
			String encodedASAIUUI = encodedUUI.substring(start + SSAConstants.UUI_ASAI_START.length(), end);
			
		    StringBuilder decodedASAIUUI = new StringBuilder();
		    for (int i = 0; i < encodedASAIUUI.length(); i+=2) {
		        String str = encodedASAIUUI.substring(i, i+2);
		        decodedASAIUUI.append((char)Integer.parseInt(str, 16));
		    }	    
		    mySession.getTraceOutput().writeln(ITraceInfo.TRACE_LEVEL_INFO, "Decoded ASAI UUI: " + decodedASAIUUI);
	
		    if (decodedASAIUUI != null && decodedASAIUUI.length() > 0) {		    
				start = 0;
				end = decodedASAIUUI.indexOf(",");  			    		
				contextId = decodedASAIUUI.substring(start, end);					    
			    channel = decodedASAIUUI.substring(end + 1, end + 3);
			    mySession.getTraceOutput().writeln(ITraceInfo.TRACE_LEVEL_INFO, String.format("ContextID and Channel: %s::%s", contextId, channel));
		    }
		}
		catch (Exception e) {
			mySession.getTraceOutput().writeln(ITraceInfo.TRACE_LEVEL_ERROR, String.format("Error parsing the UUI; unable to extract context id"));
		}		  
	    
	    return contextId;
	}	
	
	/**
	 * As this is a call transferred back to IVR, update the Oceana context for callback. Example:
	 * v6miVjRqTWqQJEF8fpRyLA,VO,N  ==>  v6miVjRqTWqQJEF8fpRyLA,VO,T
	 */
	public static String updateForTransfer(String encodedUUI,
			com.avaya.sce.runtimecommon.SCESession mySession) {	
		
		//Find ',VO,N' and change to ',VO,T'. Ditto for ',WV,N' to ',WV,T' 
		String transferUUI = encodedUUI.replaceAll(SSAConstants.VOICE_ENCODED_N, SSAConstants.VOICE_ENCODED_T);
		transferUUI = transferUUI.replaceAll(SSAConstants.WEB_VOICE_ENCODED_N, SSAConstants.WEB_VOICE_ENCODED_T);
	
		return transferUUI;
	}	
}
