package com.ejie.m62fb.i18n;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;

/**
 * <p>
 * Clase para la internacionalización de terminos de manera transparente.
 * Almacena un texto por defecto y sus traducciones a distintos idiomas.
 * </p>
 * 
XXXX
 * @version 1.0, 22/02/2012
 * @since 1.0
 */
@JsonSerialize(using = I18nString.I18nStringJsonSerializer.class)
@JsonDeserialize(using = I18nString.I18nStringJsonDeserializer.class)
public class I18nString implements Serializable {

	/** The Serial Version UID. */
	private static final long serialVersionUID = 791065360551833056L;

	protected static final String DEFAULT_LANG = "default";

	private final HashMap<String, String> translations = new HashMap<String, String>();

	/**
	 * Constructor simple. Crea una instancia con el texto por defecto indicado.
	 * 
	 * @param defaultText
	 *            El texto por defecto.
	 */
	public I18nString(final String defaultText) {
		super();
		this.translations.put(I18nString.DEFAULT_LANG, defaultText);
	}

	/**
	 * Constructor. Crea una instancia con el texto por defecto indicado y las
	 * traducciones proporcionadas.
	 * 
	 * @param defaultText
	 *            El texto por defecto.
	 * @param translations
	 *            Las traducciones a incluir, siendo la clave del Map el
	 *            lenguaje y el valor el texto traducido.
	 */
	public I18nString(final String defaultText,
			final Map<String, String> translations) {
		super();
		this.translations.put(I18nString.DEFAULT_LANG, defaultText);
		this.translations.putAll(translations);
	}

	/**
	 * Establece la traducción para el lenguaje indicado.
	 * 
	 * @param language
	 *            El lenguaje para el que se establece la traducción.
	 * @param text
	 *            La traducción en el lenguaje indicado.
	 */
	public void set(final String language, final String text) {
		this.translations.put(language == null ? I18nString.DEFAULT_LANG
				: language, text);
	}

	/**
	 * Establece la traducción para el lenguaje indicado.
	 * 
	 * @param locale
	 *            El lenguaje para el que se establece la traducción.
	 * @param text
	 *            La traducción en el lenguaje indicado.
	 */
	public void set(final Locale locale, final String text) {
		if (locale == null) {
			set(I18nString.DEFAULT_LANG, text);
		} else {
			set(locale.getLanguage(), text);
		}
	}

	/**
	 * Devuelve el texto por defecto.
	 * 
	 * @return El texto por defecto.
	 */
	@JsonIgnore
	public String getDefaultText() {
		return this.translations.get(I18nString.DEFAULT_LANG);
	}

	/**
	 * Devuelve el texto en el lenguaje actual.
	 * 
	 * @return La traduccion del texto disponible más apropiada para el lenguaje
	 *         actual o el texto por defecto en caso de no encontrar una
	 *         traducción apropiada.
	 */
	@JsonIgnore
	public String get() {
		return this.get(Locale.getDefault());
	}

	/**
	 * Devuelve el texto en el lenguaje indicado.
	 * 
	 * @param language
	 *            El lenguaje en el que se desea la traducción.
	 * @return La traduccion del texto disponible más apropiada para el lenguaje
	 *         indicado o el texto por defecto en caso de no encontrar una
	 *         traducción apropiada.
	 */
	public String get(final String language) {
		return this.translations.get(getLanguage(language));
	}

	/**
	 * Devuelve el texto en el lenguaje indicado.
	 * 
	 * @param locale
	 *            El lenguaje en el que se desea la traducción.
	 * @return La traduccion del texto disponible más apropiada para el lenguaje
	 *         indicado o el texto por defecto en caso de no encontrar una
	 *         traducción apropiada.
	 */
	public String get(final Locale locale) {
		return this.translations.get(getLanguage(locale));
	}

	/**
	 * Devuelve el lenguaje para el que existe traducción más apropiado para el
	 * lenguaje pasado como parametro. En caso de que el lenguaje indicado se
	 * trate de un dialecto se buscará ascendentemente una traducción apropiada,
	 * y en caso de que no exista ninguna se devolvera la clave bajo la que se
	 * almacena el texto por defecto en la tabla de traducciones.
	 * 
	 * @param locale
	 *            El lenguaje deseado.
	 * @return El lenguaje más similar para el que se tiene traducción.
	 */
	protected String getLanguage(final Locale locale) {
		return getLanguage(locale == null ? (String) null : locale
				.getLanguage());
	}

	/**
	 * Devuelve el lenguaje para el que existe traducción más apropiado para el
	 * lenguaje pasado como parametro. En caso de que el lenguaje indicado se
	 * trate de un dialecto se buscará ascendentemente una traducción apropiada,
	 * y en caso de que no exista ninguna se devolvera la clave bajo la que se
	 * almacena el texto por defecto en la tabla de traducciones.
	 * 
	 * @param language
	 *            El lenguaje deseado.
	 * @return El lenguaje más similar para el que se tiene traducción.
	 */
	protected String getLanguage(final String language) {
		String tmp = language;
		if (tmp == null) {
			tmp = I18nString.DEFAULT_LANG;
		} else if (!this.translations.containsKey(tmp)) {
			while (tmp.contains("-")) {
				tmp = tmp.substring(0, tmp.indexOf('-'));
				if (this.translations.containsKey(tmp)) {
					break;
				}
			}
			if (!this.translations.containsKey(tmp)) {
				tmp = I18nString.DEFAULT_LANG;
			}
		}
		return tmp;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * Devuelve el texto por defecto.
	 * </p>
	 */
	@Override
	public String toString() {
		return this.getDefaultText();
	}

	/**
	 * <p>
	 * Serializador para convertir un objeto I18nString en un objeto JSON.
	 * </p>
	 * 
XXXX
	 */
	public static class I18nStringJsonSerializer extends
			JsonSerializer<I18nString> {

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.codehaus.jackson.map.JsonSerializer#serialize(java.lang.Object,
		 * org.codehaus.jackson.JsonGenerator,
		 * org.codehaus.jackson.map.SerializerProvider)
		 */
		@Override
		public void serialize(I18nString value, JsonGenerator jgen,
				SerializerProvider provider) throws IOException,
				JsonProcessingException {
			final Map<String, String> values = value.translations;
			jgen.writeStartObject();
			final Iterator<Map.Entry<String, String>> it = values.entrySet()
					.iterator();
			while (it.hasNext()) {
				final Map.Entry<String, String> entry = it.next();
				jgen.writeObjectField(entry.getKey(), entry.getValue());
			}
			jgen.writeEndObject();
		}
	}

	/**
	 * <p>
	 * Deserializador para convertir un valor JSON en un objeto I18nString.
	 * </p>
	 * 
XXXX
	 */
	public static class I18nStringJsonDeserializer extends
			JsonDeserializer<I18nString> {

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.codehaus.jackson.map.JsonDeserializer#deserialize(org.codehaus
		 * .jackson.JsonParser, org.codehaus.jackson.map.DeserializationContext)
		 */
		@Override
		public I18nString deserialize(JsonParser jp, DeserializationContext ctxt)
				throws IOException, JsonProcessingException {
			I18nString value = null;
			if (jp.hasCurrentToken()) {
				JsonToken ct = jp.getCurrentToken();
				if (ct == JsonToken.START_OBJECT) {
					String currentField = null;
					final HashMap<String, String> translations = new HashMap<String, String>();

					do {
						ct = jp.nextToken();
						if (ct == JsonToken.END_OBJECT) {
							break;
						} else if (ct == JsonToken.FIELD_NAME) {
							currentField = jp.getText();
						} else if (ct == JsonToken.VALUE_NULL) {
							translations.put(currentField, null);
							currentField = null;
						} else if (ct == JsonToken.VALUE_STRING) {
							translations.put(currentField, jp.getText());
							currentField = null;
						} else {
							throw new JsonParseException("Unexpected token.",
									jp.getTokenLocation());
						}
					} while (jp.hasCurrentToken());

					String defaultText = translations
							.get(I18nString.DEFAULT_LANG);
					if (defaultText == null) {
						if (translations.size() > 0) {
							defaultText = translations.values().iterator()
									.next();
						}
					}
					value = new I18nString(defaultText, translations);
				} else if (ct == JsonToken.VALUE_STRING) {
					value = new I18nString(jp.getText());
				} else if (ct == JsonToken.VALUE_NULL) {
					value = null;
				} else {
					throw new JsonParseException("Unexpected token.",
							jp.getTokenLocation());
				}
			}
			jp.nextToken();
			return value;
		}
	}
}
