/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program is distributed in the hope   *
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.util;

import java.awt.ComponentOrientation;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;

import javax.print.attribute.standard.MediaSize;

import org.compiere.model.MLanguage;
import org.compiere.print.MPrintPaper;

/**
 *  Language Management.
 *
 *  @author     Jorg Janke
 *  @version    $Id: Language.java,v 1.2 2006/07/30 00:52:23 jjanke Exp $
 */
public class Language implements Serializable
{
	/**
	 * generated serial id
	 */
	private static final long serialVersionUID = 7039775951366180267L;

	/**************************************************************************
	 *  Languages
	 *      http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
	 *  Countries
	 *      http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
	 *************************************************************************/

	/** Base Language               */
	private static final String  AD_Language_en_US = "en_US";
	
	/***
	 *  System Languages.<br/>
	 *  If you want to add a language, use the method getLanguage which extends the array
	 **/
	private static List<Language> s_languages = new ArrayList<Language>();

	/** Base Language            */
	private static Language    s_baseLanguage = null;
	private static boolean     isBaseLanguageSet = false;

	static {
		s_languages.add(new Language ("English", AD_Language_en_US,  Locale.US, null, null, MediaSize.NA.LETTER));
		s_baseLanguage = s_languages.get(0);
    }

	/**	Logger			*/
	private static CLogger log = CLogger.getCLogger(Language.class.getName());
	
	/**
	 *  Get Number of Languages
	 *  @return Language count
	 */
	public static int getLanguageCount()
	{
		return s_languages.size();
	}   //  getLanguageCount

	/**
	 *  Get Language
	 *  @param index index
	 *  @return Language
	 */
	public static Language getLanguage (int index)
	{
		if (index < 0 || index >= s_languages.size())
			return s_baseLanguage;
		return s_languages.get(index);
	}   //  getLanguage

	/**
	 *  Get Language.<br/>
	 * 	If language does not exist, create it on the fly assuming that it is valid
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return Name (e.g. Deutsch)
	 */
	public synchronized static Language getLanguage (String langInfo)
	{
		int idxReplace = -1;
		String lang = langInfo;
		if (lang == null || lang.length() == 0)
			lang = System.getProperty("user.language", "");

		//	Search existing Languages
		for (int i = 0; i < s_languages.size(); i++)
		{
			if 	(   lang.equals(s_languages.get(i).getAD_Language())
				 || lang.equals(s_languages.get(i).getLanguageCode())
				 || lang.equals(s_languages.get(i).getName())) {
				if (!s_languages.get(i).m_fromDB && DB.isConnected()) {
					// if language was not get from DB and now we're connected
					idxReplace = i;
					break;
				} else {
					return s_languages.get(i);
				}
			}
		}

		//	Create Language on the fly
		if (lang.length() == 5)		//	standard format <language>_<Country>
		{
			Language ll = null;
			String language = lang.substring(0,2);
			String country = lang.substring(3);
			Locale locale = new Locale(language, country);
			if (DB.isConnected()) {
				// first time connected?
				if (!isBaseLanguageSet) {
					setBaseLanguage();
				}
				MLanguage dblang = MLanguage.get(Env.getCtx(), langInfo);
				if (dblang != null) {
					if (!(   language.equals(dblang.getLanguageISO()) 
						  && country.equals(dblang.getCountryCode())
						 ) && dblang.getLanguageISO() != null
						   && dblang.getCountryCode() != null
						) {
						locale = new Locale(dblang.getLanguageISO(), dblang.getCountryCode());
					}
					MediaSize mediaSize = MediaSize.ISO.A4;
					if (dblang.getAD_PrintPaper_ID() > 0) {
						MPrintPaper pp = MPrintPaper.get(dblang.getAD_PrintPaper_ID());
						mediaSize = pp.getMediaSize();
					}
					ll = new Language(dblang.getPrintName(), langInfo, locale, null, dblang.getDatePattern(), mediaSize);
					ll.m_fromDB = true;
					if (dblang.isBaseLanguage()) {
						idxReplace = 0;
						if (dblang.isSystemLanguage()) {
							// base language is uploaded also as System language, don't use base language but the corresponding translation
							s_baseLanguage = new Language ("no-base", "xx_XX", locale);
						} else {
							s_baseLanguage = ll;
						}
					}
				}
			}
			if (ll == null) {
				ll = new Language (lang, lang, locale);
			}
			if (log.isLoggable(Level.INFO)) {
				StringBuilder msglog = new StringBuilder("Adding Language=").append(language).append(", Country=").append(country).append(", Locale=").append(locale);			
				log.info (msglog.toString());
			}
			if (idxReplace >= 0) {
				s_languages.set(idxReplace, ll);
			} else {
				s_languages.add(ll);
			}
			return ll;
		}
		//	Get the default one
		return s_baseLanguage;
	}   //  getLanguage

	/**
	 * Set base language from AD_Language table
	 */
	private static void setBaseLanguage() {
		isBaseLanguageSet = true;
		String baselang = DB.getSQLValueStringEx(null, "SELECT AD_Language FROM AD_Language WHERE IsActive='Y' AND IsBaseLanguage = 'Y'");
		if (baselang != null) {
			getLanguage(baselang);
		}
	}

	/**
	 * Set baselang as base language
	 * @param baselang
	 */
	public static void setBaseLanguage(String baselang) {
		Language lang = getLanguage(baselang);
		s_baseLanguage = lang;
	}

	/**
	 *  Is it the base language
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return true if base language
	 */
	public static boolean isBaseLanguage (String langInfo)
	{
		if (langInfo == null || langInfo.length() == 0
			|| langInfo.equals(s_baseLanguage.getName())
			|| langInfo.equals(s_baseLanguage.getLanguageCode())
			|| langInfo.equals(s_baseLanguage.getAD_Language()))
			return true;
		return false;
	}   //  isBaseLanguage

	/**
	 *  Get Base Language
	 *  @return Base Language
	 */
	public static Language getBaseLanguage()
	{
		return s_baseLanguage;
	}   //  getBase

	/**
	 *  Get Base Language code. (e.g. en-US)
	 *  @return Base Language
	 */
	public static String getBaseAD_Language()
	{
		return s_baseLanguage.getAD_Language();
	}   //  getBase

	/**
	 *  Get Locale from langInfo
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return Locale
	 */
	public static Locale getLocale (String langInfo)
	{
		return getLanguage(langInfo).getLocale();
	}   //  getLocale

	/**
	 *  Get Language from langInfo
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return AD_Language (e.g. en-US)
	 */
	public static String getAD_Language (String langInfo)
	{
		return getLanguage(langInfo).getAD_Language();
	}   //  getAD_Language

	/**
	 *  Get Language code from locale
	 *  @param locale Locale
	 *  @return AD_Language (e.g. en-US)
	 */
	public static String getAD_Language (Locale locale)
	{
		if (locale != null)
		{
			for (int i = 0; i < s_languages.size(); i++)
			{
				if (locale.getLanguage().equals(s_languages.get(i).getLocale().getLanguage()))
					return s_languages.get(i).getAD_Language();
			}
		}
		return s_baseLanguage.getAD_Language();
	}   //  getLocale

	/**
	 *  Get Language Name from langInfo
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return Language Name (e.g. English)
	 */
	public static String getName (String langInfo)
	{
		return getLanguage(langInfo).getName();
	}   //  getAD_Language

	/**
	 *  Returns true if Decimal Point (not comma)
	 *  @param langInfo either language (en) or locale (en-US) or display name
	 *  @return use of decimal point
	 */
	public static boolean isDecimalPoint(String langInfo)
	{
		return getLanguage(langInfo).isDecimalPoint();
	}   //  getAD_Language

	/**
	 *  Get Display names of supported languages
	 *  @return Array of Language names
	 */
	public static String[] getNames()
	{
		String[] retValue = new String[s_languages.size()];
		for (int i = 0; i < s_languages.size(); i++)
			retValue[i] = s_languages.get(i).getName();
		return retValue;
	}   //  getNames
	
	/**
	 *  Get Current Login Language
	 *  @return login language
	 */
	public static Language getLoginLanguage ()
	{
		return Env.getLanguage(Env.getCtx());
	}   //  getLanguage

	/**
	 *  Set Current Login Language
	 *  @param language language
	 */
	public static void setLoginLanguage (Language language)
	{
		if (language != null)
		{
			Env.setContext(Env.getCtx(), Env.LANGUAGE, language.getAD_Language());
			if (log.isLoggable(Level.CONFIG)) log.config(language.toString());
		}
	}   //  setLanguage
	
	/**
	 *  Define Language
	 *  @param name - displayed value, e.g. English
	 *  @param AD_Language - the code of system supported language, e.g. en_US
	 *  (might be different than Locale - i.e. if the system does not support the language)
	 *  @param locale - the Locale, e.g. Locale.US
	 *  @param decimalPoint true if Decimal Point - if null, derived from Locale
	 *  @param javaDatePattern Java date pattern as not all locales are defined - if null, derived from Locale
	 *  @param mediaSize default media size
	 */
	public Language (String name, String AD_Language, Locale locale,
		Boolean decimalPoint, String javaDatePattern, MediaSize mediaSize)
	{
		if (name == null || AD_Language == null || locale == null)
			throw new IllegalArgumentException ("Language - parameter is null");
		m_name = name;
		m_AD_Language = AD_Language;
		m_locale = locale;
		//
		m_decimalPoint = decimalPoint;
		setDateFormat (javaDatePattern);
		setMediaSize (mediaSize);
	}   //  Language

	/**
	 *  Define Language with AD_Language and default decimal point and date format
	 *  @param name - displayed value, e.g. English
	 *  @param AD_Language - the code of system supported language, e.g. en_US
	 *  (might be different than Locale - i.e. if the system does not support the language)
	 *  @param locale - the Locale, e.g. Locale.US
	 */
	public Language (String name, String AD_Language, Locale locale)
	{
		this (name, AD_Language, locale, null, null, null);
	}	//	Language

	/**
	 * Copy constructor
	 * @param copy
	 */
	public Language(Language copy)
	{
		this.m_AD_Language = copy.m_AD_Language;
		this.m_dateFormat = copy.m_dateFormat;
		this.m_dbDateFormat = copy.m_dbDateFormat;
		this.m_decimalPoint = copy.m_decimalPoint;
		this.m_fromDB = copy.m_fromDB;
		this.m_leftToRight = copy.m_leftToRight;
		this.m_locale = copy.m_locale;
		this.m_mediaSize = copy.m_mediaSize;
		this.m_name = copy.m_name;
	}

	/**	Name					*/
	private String  m_name;
	/**	Language (key)			*/
	private String  m_AD_Language;
	/** Locale					*/
	private Locale  m_locale;
	//
	private Boolean             m_decimalPoint;
	private Boolean				m_leftToRight;
	private SimpleDateFormat    m_dateFormat;
	private String              m_dbDateFormat;
	private MediaSize 			m_mediaSize = MediaSize.ISO.A4;
	private boolean             m_fromDB = false;

	/**
	 *  Get Language Name.
	 *  e.g. English
	 *  @return name
	 */
	public String getName()
	{
		return m_name;
	}   //  getName

	/**
	 *  Get Application Dictionary Language (system supported).
	 *  e.g. en-US
	 *  @return AD_Language
	 */
	public String getAD_Language()
	{
		return m_AD_Language;
	}   //  getAD_Language

	/**
	 *  Set Application Dictionary Language (system supported).
	 *  @param AD_Language e.g. en-US
	 */
	public void setAD_Language (String AD_Language)
	{
		if (AD_Language != null)
		{
			m_AD_Language = AD_Language;
			if (log.isLoggable(Level.CONFIG)) log.config(toString());
		}
	}   //  getAD_Language

	/**
	 *  Get Locale
	 *  @return locale
	 */
	public Locale getLocale()
	{
		return m_locale;
	}   //  getLocale

	/**
	 *  Overwrite Locale
	 *  @param locale locale
	 */
	public void setLocale (Locale locale)
	{
		if (locale == null)
			return;
		m_locale = locale;
		m_decimalPoint = null;  //  reset
	}   //  getLocale

	/**
	 *  Get Language Code.
	 *  e.g. en - derived from Locale
	 *  @return language code
	 */
	public String getLanguageCode()
	{
		return m_locale.getLanguage();
	}   //  getLanguageCode

	/**
	 *  Component orientation is Left To Right
	 *  @return true if left-to-right
	 */
	public boolean isLeftToRight()
	{
		if (m_leftToRight == null)
			//  returns true if language not iw, ar, fa, ur
			m_leftToRight = Boolean.valueOf(ComponentOrientation.getOrientation(m_locale).isLeftToRight());
		return m_leftToRight.booleanValue();
	}   //  isLeftToRight

	/**
	 *  Returns true if Decimal Point (not comma)
	 *  @return use of decimal point
	 */
	public boolean isDecimalPoint()
	{
		if (m_decimalPoint == null)
		{
			DecimalFormatSymbols dfs = new DecimalFormatSymbols(m_locale);
			m_decimalPoint = Boolean.valueOf(dfs.getDecimalSeparator() == '.');
		}
		return m_decimalPoint.booleanValue();
	}   //  isDecimalPoint

	/**
	 * 	Is This the Base Language
	 * 	@return true if base Language
	 */
	public boolean isBaseLanguage()
	{
		return this.equals(getBaseLanguage());
	}	//	isBaseLanguage

	/**
	 *  Set Date Format Pattern.<br/>
	 *  The date format is not checked for correctness
	 *  @param javaDatePattern for details see java.text.SimpleDateFormat,
	 *  format must be able to be converted to database date format by
	 *  using the upper case function.
	 *  It also must have leading zero for day and month.
	 */
	public synchronized void setDateFormat (String javaDatePattern)
	{
		if (javaDatePattern == null)
			return;
		m_dateFormat = (SimpleDateFormat)DateFormat.getDateInstance
				(DateFormat.SHORT, m_locale);
		try
		{
			m_dateFormat.applyPattern(javaDatePattern);
		}
		catch (Exception e)
		{
			log.severe(javaDatePattern + " - " + e);
			m_dateFormat = null;
		}
	}   //  setDateFormat

	/**
	 *  Get (Short) Date Format.
	 *  @return date format MM/dd/yyyy - dd.MM.yyyy
	 */
	public synchronized SimpleDateFormat getDateFormat()
	{
		if (m_dateFormat == null)
		{
			m_dateFormat = (SimpleDateFormat)DateFormat.getDateInstance
				(DateFormat.SHORT, m_locale);
			String sFormat = m_dateFormat.toPattern();
			//	some short formats have only one M and/or d (e.g. ths US)
			if (sFormat.indexOf("MM") == -1 || sFormat.indexOf("dd") == -1)
				{
				sFormat = sFormat.replaceFirst("d+", "dd");
				sFormat = sFormat.replaceFirst("M+", "MM");
			//	log.finer(sFormat + " => " + nFormat);
				m_dateFormat.applyPattern(sFormat);
			}
			//	Unknown short format => use JDBC
			if (m_dateFormat.toPattern().length() != 8)
				m_dateFormat.applyPattern("yyyy-MM-dd");

			//	4 digit year
			if (m_dateFormat.toPattern().indexOf("yyyy") == -1)
			{
				sFormat = m_dateFormat.toPattern();
				StringBuilder nFormat = new StringBuilder();
				for (int i = 0; i < sFormat.length(); i++)
				{
					if (sFormat.charAt(i) == 'y')
						nFormat.append("yy");
					else
						nFormat.append(sFormat.charAt(i));
				}
				m_dateFormat.applyPattern(nFormat.toString());
			}
			m_dateFormat.setLenient(true);
		}
		return (SimpleDateFormat) m_dateFormat.clone();
	}   //  getDateFormat

	/**
	 * 	Get Date Time Format.
	 *  @return Date Time format MMM d, yyyy h:mm:ss a z -or- dd.MM.yyyy HH:mm:ss z
	 *  -or- j nnn aaaa, H' ?????? 'm' ????'
	 */
	public SimpleDateFormat getDateTimeFormat()
	{
		SimpleDateFormat retValue = (SimpleDateFormat)DateFormat.getDateTimeInstance
			(DateFormat.MEDIUM, DateFormat.LONG, m_locale);
		return retValue;
	}	//	getDateTimeFormat

	/**
	 * 	Get Time Format.
	 *  @return Time format h:mm:ss z or HH:mm:ss z
	 */
	public SimpleDateFormat getTimeFormat()
	{
		return (SimpleDateFormat)DateFormat.getTimeInstance
			(DateFormat.LONG, m_locale);
	}	//	getTimeFormat

	/**
	 *  Get Database Date Pattern.<br/>
	 *  Derive from date pattern (make upper case and replace month as word with MM)
	 *  @return date pattern
	 */
	public String getDBdatePattern()
	{
		if (m_dbDateFormat == null)
		{
			m_dbDateFormat = getDateFormat().toPattern().toUpperCase(m_locale);
			// IDEMPIERE-3888 - temporary hack - a better solution would be to implement AD_Language.DBDatePattern
			m_dbDateFormat = m_dbDateFormat.replaceFirst("MMM*", "MM");
		}
		return m_dbDateFormat;
	}   //  getDBdatePattern

	/**
	 * 	Get default MediaSize
	 * 	@return media size
	 */
	public MediaSize getMediaSize()
	{
		return m_mediaSize;
	}	//	getMediaSize

	/**
	 * 	Set default MediaSize
	 * 	@param size media size
	 */
	public void setMediaSize (MediaSize size)
	{
		if (size != null)
			m_mediaSize = size;
	}	//	setMediaSize

	/**
	 *  String Representation
	 *  @return string representation
	 */
	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder("Language=[");
		sb.append(m_name).append(",Locale=").append(m_locale.toString())
			.append(",AD_Language=").append(m_AD_Language)
			.append(",DatePattern=").append(getDBdatePattern())
			.append(",DecimalPoint=").append(isDecimalPoint())
			.append("]");
		return sb.toString();
	}   //  toString

	/**
	 * 	Hash Code
	 * 	@return hashcode
	 */
	@Override
	public int hashCode()
	{
		return m_AD_Language.hashCode();
	}	//	hashcode

	/**
	 * 	Equals.
	 *  Two languages are equal, if they have the same AD_Language
	 * 	@param obj compare
	 * 	@return true if AD_Language is the same
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (obj instanceof Language)
		{
			Language cmp = (Language)obj;
			if (cmp.getAD_Language().equals(m_AD_Language))
				return true;
		}
		return false;
	}	//	equals
	
}   //  Language
