package es.ejie.frmk.infraestructura.internacionalizacion;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.Vector;

import javax.servlet.jsp.PageContext;

import org.apache.commons.beanutils.BasicDynaBean;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.control.arranque.Q70Arrancable;
import es.ejie.frmk.control.arranque.Q70GestorArranqueConfiguracionServidor;
import es.ejie.frmk.control.excepciones.Q70FrameworkException;
import es.ejie.frmk.control.excepciones.Q70GestorDeExcepcionesServidor;
import es.ejie.frmk.infraestructura.poolCache.Q70GestorCacheServidorInterface;
import es.ejie.frmk.infraestructura.tablas.Q70GestorTablasCodigosServidorInterface;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.listeners.base.Q70ListenerUtils;

/**
 * Esta clase gestiona la internacionalización de los mensajes manejados por las aplicaciones y por los componentes de Geremua.
 * <p>
 * Los mensajes estarán asociados a ámbitos de internacionalización, códigos de mensaje, formatos e idiomas, 
 * y éstos marcarán la funcionalidad del juego de métodos que ofrece la clase forzando a acceder a los mensajes de diferente manera,
 * pero siguiendo unas directrices comunes:
 * <ul>
 * <li>si no se dispone de ámbito, se trata de una petición de mensaje de Geremua</li>
 * <li>si el ámbito dado no está en el catálogo de ámbitos, se consulta al catálogo del "Gestor de Tablas de Códigos"</li>
 * <li>si hay argumentos de formato, se formatea el mensaje con los argumentos recibidos</li>
 * </ul>
 */	
	public class Q70GestorMensajesi18n implements Q70GestorMensajes, Q70Arrancable{

	private static final String SEPARADOR = "_";
	private static final String PROPERTIES_TYPE_FILE = ".properties";
	
	private Map ambitos = null;
	private String xmlConfiguracion = null;
	
	private Q70GestorMensajesi18n() {
		super();
	}
	
	/** 
	 * @deprecated Usar este metodo de Struts en lugar de realizar esta llamada: 
	 * 'RequestUtils.message(pageContext, bundle, localeKey, key, args);'";
	 * 
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(javax.servlet.jsp.PageContext, java.lang.String, java.lang.String, java.lang.String, java.lang.Object[])
	 */
	public String getMessage(
		PageContext pageContext,
		String bundle,
		String localeKey,
		String key,
		Object[] args)
		throws Q70GestorMensajesException {
		String mensaje = key;
		
		Q70GestorDeExcepcionesServidor.getSingleton().recogerThrowable(new Q70GestorMensajesException(this.getClass().getName(), "getMessage(PageContext, String, String, String, Object[])", Q70Constantes.ERROR_DEPRECATED, null));

		//Esta clase tiene que estar en el jar de servidor para que sea accesible desde los ear (EJB).
		//Por tanto, hay que desacoplarla de Struts, deprecando este metodo que no aporta nada de Geremua.
		//Usar este metodo de Struts en lugar de realizar esta llamada: 
		//		'RequestUtils.message(pageContext, bundle, localeKey, key, args);'";
		
//		try{
//			RequestUtils.message(pageContext, bundle, localeKey, key, args);			
//		}
//		catch(JspException jspe){
//			if(traza.isTraceActive())
//				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, Q70Constantes.ERROR_RECUPERANDO + key);
//			throw new Q70GestorMensajesException(this.getClass().getName(), "getMessage", Q70Constantes.ERROR_RECUPERANDO + key, jspe);	
//		}		
//		if(traza.isTraceActive())
//			traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.INFO, "Procesada petición de mensaje para codigo " + key);		
		
		return mensaje;

	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getProperties(java.lang.String)
	 */
	public Properties getProperties(String language) throws Q70GestorMensajesException
	{
		String name = Q70Constantes.FICHERO_PROPIEDADES + SEPARADOR + language + PROPERTIES_TYPE_FILE;
		
		InputStream is = null;
		is = Q70GestorArranqueConfiguracionServidor.getGlobalResourceAsStream(name);
		
		Properties props = new Properties();
		try {
			props.load(is);
		} catch (IOException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			
			if(traza.isTraceActive())
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Error al leer el fichero de propiedades " + name);		
			throw new Q70GestorMensajesException(this.getClass().getName(), "getProperties","Error al leer el fichero de propiedades " + name, null);							
		}
		
		return props;
		
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getProperties(java.lang.String, java.lang.String)
	 */
	public Properties getProperties(String aScope, String language) throws Q70GestorMensajesException
	{
		Properties properties = null;
		StringBuffer ficheroPropiedades = new StringBuffer();
		if(!aScope.equals(Q70Constantes.FICHERO_PROPIEDADES))
		{		
			Map mapaAmbitos = (Map)ambitos.get(aScope);
			if ( mapaAmbitos == null ){
				throw new Q70GestorMensajesException(this.getClass().getName(), "getProperties", Q70Constantes.ERROR_AMBITO_PROPERTIES + aScope, null);				
			}
			ficheroPropiedades.append((String)mapaAmbitos.get(Q70Constantes.PATH));
			try {
				properties = Q70ListenerUtils.loadProperties(ficheroPropiedades.toString()+SEPARADOR+language+PROPERTIES_TYPE_FILE, false);
				if ( properties == null ){
					properties = Q70ListenerUtils.loadProperties(ficheroPropiedades.toString()+PROPERTIES_TYPE_FILE, false);
				}
			} catch (RuntimeException e) {
				throw new Q70GestorMensajesException(this.getClass().getName(), "getProperties", Q70Constantes.ERROR_RECUPERANDO_PROPERTIES + aScope, e);
			}
		}		
		else
		{		
			properties = getProperties(language);			
		}	
		
		return properties;
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.lang.String)
	 */
	public String getMessage(String code){
		return getMessage(code, null);
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.lang.String, java.lang.Object[])
	 */
	public String getMessage(String code, Object[] args){
		return getMessage(code, Locale.getDefault().getLanguage(), args);
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.lang.String, java.lang.String, java.lang.Object[])
	 */
	public String getMessage(String code, String languaje, Object[] args){
		String message = null;
		try{
			message = getMessageFromProperty(code, Q70GestorArranqueConfiguracionServidor.PATH_CONFIGURACION_GLOBAL + Q70Constantes.FICHERO_PROPIEDADES, languaje);			
		}
		catch(MissingResourceException mre){
			try{
				message = getMessageFromTable(code, Q70Constantes.TABLA_PROPIEDADES, Locale.getDefault().getLanguage());				
			}
			catch(Q70GestorMensajesException gme){
				return code;
			}
		}
		if(args != null){
			java.text.MessageFormat mf = new java.text.MessageFormat(escape(message));			
			return mf.format(args);						
		}
		else
		{
			return message;
		}
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.lang.String, java.lang.String, java.lang.String)
	 */
	public String getMessage(String aScope, String code, String languaje)
		throws Q70GestorMensajesException {
		return getMessage(aScope, code, languaje, null)	;	
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.lang.String, java.lang.String, java.lang.String, java.lang.Object[])
	 */
	public String getMessage(String aScope, String code, String languaje, Object[] args)
		throws Q70GestorMensajesException {
		List listMessages = new ArrayList(1);
		listMessages.add(aScope);
		return getMessage(listMessages, code, languaje, args);
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.internacionalizacion.Q70GestorMensajes#getMessage(java.util.List, java.lang.String, java.lang.String, java.lang.Object[])
	 */
	public String getMessage(List aListaScope, String code, String languaje, Object[] args)
		throws Q70GestorMensajesException {
		String retorno = code;
		//Si el ambito no existe en la tabla de ambitos, asumo que puede ser una tabla
		// del componente de Gestor de tablas de codigo
		Q70TrazaInterface traza = Q70Factoria.getTraza();
		traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.INFO, "cambios para aScope... " + code);
		String retornoAux ="";
		boolean encontrado=false;
		
		if (aListaScope.isEmpty()){
			retorno = getMessage0(null, code, languaje, args, retorno);
			if (!code.equals(retorno))
				encontrado = true;
		}
		else{
			Iterator iter = aListaScope.iterator();
			while (iter.hasNext()) {
				retornoAux = getMessage0((String) iter.next(), code, languaje, args, retorno);
				if (!retornoAux.equals(retorno)){
					retorno = retornoAux;
					encontrado=true;
					break;
				}
					
			}
		}
		
		Properties pr = null;
		if (!encontrado){
			pr = Q70ListenerUtils.loadProperties(Q70GestorArranqueConfiguracionServidor.PATH_CONFIGURACION_GLOBAL + Q70Constantes.FICHERO_PROPIEDADES+SEPARADOR+languaje+PROPERTIES_TYPE_FILE, false);
			if ( pr == null ){
				pr = Q70ListenerUtils.loadProperties(Q70GestorArranqueConfiguracionServidor.PATH_CONFIGURACION_GLOBAL + Q70Constantes.FICHERO_PROPIEDADES+PROPERTIES_TYPE_FILE, false);
			}
		}

		if(pr != null && (String)pr.get(code) != null)	
			retorno = (String)pr.get(code);	
		
		if(traza.isTraceActive())
			traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.INFO, "Procesada petición de mensaje para codigo " + code);		
		
		if(args != null){
			java.text.MessageFormat mf = new java.text.MessageFormat(escape(retorno));			
			return mf.format(args);						
		}
		else
		{
			return retorno;
		}
	}

	private String getMessage0(String aScope, String code, String languaje, Object[] args, String retorno) throws Q70GestorMensajesException {
		if((aScope!= null) && (aScope.equals(Q70Constantes.FICHERO_PROPIEDADES))){
			retorno = getMessage(code, languaje, args);	
		}else{
			if (aScope == null || aScope.equals("")){
				retorno = getMessage(code, languaje, args);		
			}else if(ambitos.get(aScope) != null){
				if(new Boolean((String)((Map)ambitos.get(aScope)).get(Q70Constantes.CACHE)).booleanValue())				
					retorno = getMessageFromPropertyCached(code, (String)((Map)ambitos.get(aScope)).get(Q70Constantes.PATH), languaje);
				else
					retorno = getMessageFromProperty(code, (String)((Map)ambitos.get(aScope)).get(Q70Constantes.PATH), languaje);														
			}else{
				Q70GestorTablasCodigosServidorInterface gt = (Q70GestorTablasCodigosServidorInterface)Q70Factoria.getGestorTablasCodigos();
				if(gt.tableExist(aScope)){
					retorno = getMessageFromTable(code, aScope, languaje);
					if (retorno.equals(code)){
						retorno = getMessage(code, languaje, args);
					}
				}else{
					Q70TrazaInterface traza = Q70Factoria.getTraza();
					if(traza.isTraceActive())
						traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, Q70Constantes.ERROR_AMBITO + aScope);		
					throw new Q70GestorMensajesException(this.getClass().getName(), "getMessage", Q70Constantes.ERROR_AMBITO + aScope, null);				
				}
			}	
		}
		return retorno;
	}
	
	/**
	 * Establece los ambitos (tablas y ficheros de propiedades) de trabajo
	 * @return <code>Map</code> relación de ámbitos de trabajo
	 */		
	protected void setScopes(Map aScope){
		ambitos = aScope;
	}
	
	/**
	 * Devuelve el mensaje asociado a un código cuando se trata de un ámbito
	 * relativo a un fichero de propiedades
	 * @param <code>String</code> codigo del mensaje solicitado
	 * @param <code>String</code> fichero de propiedades
	 * @param <code>String</code> identificador de idioma
	 * @return <code>String</code> Q70Mensaje asociado al codigo enviado   
	 */		
	private String getMessageFromProperty(String code, String propertyFile, String languaje){
		String retorno = code;
		Properties pr = Q70ListenerUtils.loadProperties(propertyFile+SEPARADOR+languaje+PROPERTIES_TYPE_FILE, false);
		if ( pr == null ){
			pr = Q70ListenerUtils.loadProperties(propertyFile+PROPERTIES_TYPE_FILE, false);
		}

		if(pr != null && (String)pr.get(code) != null)	
			retorno = (String)pr.get(code);	
		return retorno;  
	}
	
	/**
	 * Devuelve el mensaje asociado a un código cuando se trata de un ambito
	 * relativo a un fichero de propiedades en cache
	 * @param <code>String</code> codigo del mensaje solicitado
	 * @param <code>String</code> fichero de propiedades
	 * @param <code>String</code> identificador de idioma
	 * @return <code>String</code> Q70Mensaje asociado al codigo enviado   
	 * @throws Q70GestorMensajesException 
	 */		
	private String getMessageFromPropertyCached(String code, String propertyFile, String languaje) throws Q70GestorMensajesException{
		String objectName = propertyFile + SEPARADOR +languaje;
		String retorno = code;
		Q70GestorCacheServidorInterface gestorCache = (Q70GestorCacheServidorInterface)Q70Factoria.getGestorCache();
		Properties pr = (Properties)gestorCache.getElement(es.ejie.frmk.infraestructura.poolCache.Q70Constantes.CACHE_I18N_PROPERTIES, objectName);
		if(pr == null){
			pr = Q70ListenerUtils.loadProperties(propertyFile+SEPARADOR+languaje+PROPERTIES_TYPE_FILE, false);
			if ( pr == null ){
				pr = Q70ListenerUtils.loadProperties(propertyFile+PROPERTIES_TYPE_FILE, false);
				if ( pr == null ){
					throw new Q70GestorMensajesException(this.getClass().getName(), "getMessageFromPropertyCached", Q70Constantes.ERROR_RECUPERANDO_PROPERTIES + propertyFile, null);
				}
			}
			gestorCache.addElement(es.ejie.frmk.infraestructura.poolCache.Q70Constantes.CACHE_I18N_PROPERTIES, objectName, pr); 
		}
		
		if(pr != null && (String)pr.get(code) != null)	
			retorno = (String)pr.get(code);	
		return retorno;  	
	}
	
	/**
	 * Devuelve el mensaje asociado a un código cuando se trata de un ambito
	 * relativo a una tabla
	 * @param <code>String</code> codigo del mensaje solicitado
	 * @param <code>String</code> nombre de tabla
	 * @param <code>String</code> identificador de idioma  
	 * @return <code>String</code> Q70Mensaje asociado al codigo enviado 
	 * @throws <code>Q70GestorMensajesException</code>
	 */		
	private String getMessageFromTable(String code, String aTable, String languaje)throws Q70GestorMensajesException{
		String resultado = code;
		Q70GestorTablasCodigosServidorInterface gt = (Q70GestorTablasCodigosServidorInterface)Q70Factoria.getGestorTablasCodigos();
		if(!gt.tableExist(aTable))
			throw new Q70GestorMensajesException(this.getClass().getName(), "getMessageForTable",Q70Constantes.ERROR_AMBITO + aTable, null);		
		try
		{	Vector columna = new Vector();
			StringBuffer condicion = new StringBuffer();
			Map infoTable = gt.getInfoTable(aTable);
			Map internacionalizacion = (Map)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.I18N);
			condicion.append(internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.CODIGO));
			condicion.append(" = ");
			if (((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.TIPO_CODIGO)).equals("string"))
				condicion.append("'" + code + "'");
			else
				condicion.append(code);
			if (infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.MULTILENGUAJE)!=null){
				if(((String)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.MULTILENGUAJE)).equals(es.ejie.frmk.infraestructura.tablas.Q70Constantes.MULTILENGUAJE_VERTICAL)){
					columna.add(((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.LITERAL)).toLowerCase());
				}
				else{
					if(((String)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.MULTILENGUAJE)).equals(es.ejie.frmk.infraestructura.tablas.Q70Constantes.MULTILENGUAJE_HORIZONTAL)){
						if (null == ((Map)((Map)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.GRUPO_LOCALES_COLUMNA)).get((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.LITERAL))).get(languaje))
							columna.add(((String)((Map)((Map)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.GRUPO_LOCALES_COLUMNA)).get((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.LITERAL))).get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.IDIOMA_DEFECTO)).toLowerCase());
						else
							columna.add(((String)((Map)((Map)infoTable.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.GRUPO_LOCALES_COLUMNA)).get((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.LITERAL))).get(languaje)).toLowerCase());
					}
				}
			}
			else{
				columna.add(((String)internacionalizacion.get(es.ejie.frmk.infraestructura.tablas.Q70Constantes.LITERAL)).toLowerCase());
			}
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			try{
				if(traza.isTraceActive())
					traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.DEBUG, "i18n solicita a tablas de codigo " + condicion.toString() + " a la tabla " + aTable);						
				resultado = (String)((BasicDynaBean)((ArrayList)gt.selectRecordsFromTable(aTable, condicion.toString(), languaje, columna)).iterator().next()).get((String)columna.get(0));
			}catch(Exception e){
				if(traza.isTraceActive())
					traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.WARN, "Tablas de codigo lanza excepción" + e.toString());										
			}
		}
		catch(Exception gte)
		{
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if(traza.isTraceActive())
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Error preparando petición a tablas de codigo " + gte.toString());									
			throw new Q70GestorMensajesException(this.getClass().getName(), "getMessageFromTable", Q70Constantes.ERROR_TABLA + aTable, gte);
		}
		return resultado;
	}
	
	/**
	  * Proporciona acceso al Gestor de Mensajes i18n
	  * @return Instancia única del Gestor de Mensajes i18n
	  */
	public static Q70GestorMensajesi18n getSingleton() {
		
		
		Q70GestorMensajesi18n instancia =
			(Q70GestorMensajesi18n)Q70GestorArranqueConfiguracionServidor.getAttribute(Q70Constantes.ID_COMPONENTE);
		if (instancia == null)
		{
			instancia = new Q70GestorMensajesi18n();
			Q70GestorArranqueConfiguracionServidor.setAttribute(Q70Constantes.ID_COMPONENTE, instancia);

		}
		return instancia;
	}
			
	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#afterCreate(java.lang.String)
	 */
	public void afterCreate(String xml) throws Q70FrameworkException {
		try {
			xmlConfiguracion = xml;
			Q70Configurador conf = new Q70Configurador(xml);
			conf.lanzarConfigurador();
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método afterCreate de la clase Q70GestorMensajesi18n");
			}
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"afterCreate","Se ha producido un error en el método afterCreate.",e);
		}
	}
		
	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#beforeDestroy()
	 */
	public void beforeDestroy(){
	}
		
	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#reConfigure()
	 */
	public void reConfigure() throws Q70FrameworkException{
		try {
			ambitos = null;
			afterCreate(xmlConfiguracion);
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método lanzarConfigurador de la clase Q70GestorMensajesi18n");
			}
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"reConfigure","Se ha producido un error en el método reConfigure.",e);
		}
	}	
	
	/**
	 * Escape any single quote characters that are included in the specified
	 * message string.
	 *
	 * @param string The string to be escaped
	 */
	private String escape(String string) {

		if ((string == null) || (string.indexOf('\'') < 0))
			return (string);
		int n = string.length();
		StringBuffer sb = new StringBuffer(n);
		for (int i = 0; i < n; i++) {
			char ch = string.charAt(i);
			if (ch == '\'')
				sb.append('\'');
			sb.append(ch);
		}
		return (sb.toString());
	}
}