content: // This file is part of AceWiki.
  // Copyright 2008-2013, AceWiki developers.
  // 
  // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU
  // Lesser General Public License as published by the Free Software Foundation, either version 3 of
  // the License, or (at your option) any later version.
  // 
  // AceWiki 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
  // Lesser General Public License for more details.
  // 
  // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If
  // not, see http://www.gnu.org/licenses/.
  
  package ch.uzh.ifi.attempto.acewiki.gf;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Locale;
  import java.util.Set;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.common.base.Splitter;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  
  import ch.uzh.ifi.attempto.acewiki.AceWikiApp;
  import ch.uzh.ifi.attempto.acewiki.core.AbstractLanguageHandler;
  import ch.uzh.ifi.attempto.acewiki.core.AceWikiConfig;
  import ch.uzh.ifi.attempto.acewiki.core.EditorController;
  import ch.uzh.ifi.attempto.acewiki.core.GeneralTopic;
  import ch.uzh.ifi.attempto.acewiki.core.LexiconChanger;
  import ch.uzh.ifi.attempto.acewiki.core.Ontology;
  import ch.uzh.ifi.attempto.acewiki.core.Sentence;
  import ch.uzh.ifi.attempto.acewiki.core.SentenceSuggestion;
  import ch.uzh.ifi.attempto.acewiki.core.TopicChanger;
  import ch.uzh.ifi.attempto.base.PredictiveParser;
  import ch.uzh.ifi.attempto.base.TextContainer;
  import ch.uzh.ifi.attempto.base.TextOperator;
  import ch.uzh.ifi.attempto.echocomp.EchoThread;
  import ch.uzh.ifi.attempto.echocomp.LocaleResources;
  
  /**
   * This is a language handler for languages written in GF.
   * 
   * @author Kaarel Kaljurand
   */
  public class GfHandler extends AbstractLanguageHandler {
  
  	// This map is here to override the Java Locale-constructor.
  	// Note that we need to provide also the country code for the UI localization to work,
  	// TODO maybe this can be made more flexible.
  	public static final ImmutableMap<String, Locale> ISO3_TO_LOCALE =
  			new ImmutableMap.Builder<String, Locale>()
  			.put("Dut", new Locale("nl"))
  			.put("Eng", new Locale("en", "US"))
  			.put("Est", new Locale("et"))
  			.put("Fin", new Locale("fi"))
  			.put("Fre", new Locale("fr"))
  			.put("Ger", new Locale("de", "DE"))
  			.put("Ron", new Locale("ro"))
  			.put("Spa", new Locale("es", "ES"))
  			.put("Tha", new Locale("th"))
  			.build();
  
  	private static final Splitter LOCALE_SPLITTER = Splitter.onPattern("[-_]").trimResults().omitEmptyStrings();
  
  	private final Logger mLogger = LoggerFactory.getLogger(GfHandler.class);
  
  	private final String mLanguage;
  	private final String mLanguageName;
  	private final Locale mLocale;
  	private final GfGrammar mGfGrammar;
  
  	private TextOperator mTextOperator;
  
  	// We use an editor controller which shows the largest categories in the
  	// grammar and labels them in a language sensitive way.
  	// TODO: we currently do not update that editor controller, even though
  	// the sizes and labels of the categories can change since the grammar is
  	// editable in the general case.
  	private final EditorController mEditorController;
  
  	/**
  	 * Creates a new GF handler for the given language.
  	 * 
  	 * @param language The name of the language.
  	 * @param gfGrammar The grammar object.
  	 */
  	public GfHandler(String language, GfGrammar gfGrammar) {
  		mLanguage = language;
  		mGfGrammar = gfGrammar;
  
  		Locale locale = guessLocale(language, gfGrammar);
  		String languageName = getLocaleDisplayLanguage(locale);
  
  		// Some ACE-specific overriding
  		if (mLanguage.endsWith("Ace")) {
  			languageName = "ACE";
  			if (locale != null) locale = ISO3_TO_LOCALE.get("Eng");
  		} else if (mLanguage.endsWith("Ape")) {
  			languageName = "ACE+lex";
  			if (locale != null) locale = ISO3_TO_LOCALE.get("Eng");
  		}
  
  		if (locale == null) {
  			locale = LocaleResources.defaultLocale;
  		}
  
  		if (languageName == null) {
  			languageName = mLanguage;
  		}
  
  		// Support for disambiguation languages (as in MOLTO Phrasebook),
  		// which have a name in the form "DisambPhrasebookEng".
  		if (languageName != mLanguage && mLanguage.startsWith(GfGrammar.PREFIX_DISAMB)) {
  			languageName += " (disamb.)"; // TODO: make the "disamb." localizable
  		}
  
  		// Capitalize first letter:
  		languageName = languageName.substring(0, 1).toUpperCase() + languageName.substring(1);
  
  		mLocale = locale;
  		mLanguageName = languageName;
  		mEditorController = GfEditorControllerFactory.createFromCats(mGfGrammar, mLanguage);
  
  		mLogger.info("Locale: {} -> {} -> {}", language, mLocale, mLanguageName);
  	}
  
  
  	public String getLanguage() {
  		return mLanguage;
  	}
  
  	public String getLanguageName() {
  		return mLanguageName;
  	}
  
  	public Locale getLocale() {
  		return mLocale;
  	}
  
  	public void init(Ontology ontology) {
  		mTextOperator = new GfTextOperator(ontology);
  	}
  
  	public TextOperator getTextOperator() {
  		return mTextOperator;
  	}
  
  	public List<Sentence> extractSentences(TextContainer tc, PredictiveParser parser) {
  		String tokenText = GfGrammar.GF_TOKEN_JOINER.join(tc.getTokens());
  		List<Sentence> l = new ArrayList<Sentence>();
  		// TODO: if the ACE linearization contains a question mark, then ...
  		if (tokenText.contains("?")) {
  			l.add(new GfQuestion(mGfGrammar, mLanguage, tokenText));
  		} else {
  			l.add(new GfDeclaration(mGfGrammar, mLanguage, tokenText));
  		}
  		return l;
  	}
  
  	public PredictiveParser getPredictiveParser() {
  		return new GfPredictiveParser(mGfGrammar, mLanguage);
  	}
  
  	public EditorController getEditorController() {
  		return mEditorController;
  	}
  
  	/**
  	 * If there is an issue with the sentence (e.g. if it is ambiguous)
  	 * then we can return a suggestion to ask the user to immediately
  	 * deal with the issue.
  	 *
  	 * TODO: We currently don't do anything, e.g. ambiguity can be dealt with
  	 * later as well.
  	 */
  	public SentenceSuggestion getSuggestion(Sentence sentence) {
  		/*
  		if (sentence.getNumberOfParseTrees() > 1) {
  			return new DisambiguationSuggestion(sentence);
  		}
  		 */
  		return null;
  	}
  
  	public LexiconChanger getLexiconChanger(String type) {
  		if (type.equals(GeneralTopic.NORMAL_TYPE)) {
  			return new TopicChanger();
  		} else if (type.equals(TypeGfModule.INTERNAL_TYPE)) {
  			AceWikiConfig config = ((AceWikiApp) EchoThread.getActiveApplication()).getConfig();
  			if (config.isGrammarIntegrationEnabled()) {
  				return new GfModuleChanger();
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * Determines the locale on the basis of the concrete language and the grammar.
  	 */
  	private static Locale guessLocale(String language, GfGrammar grammar) {
  		// The grammar can explicitly define the locale, if it does then we use this locale.
  		Set<String> locales = grammar.getLocales(language);
  		if (locales != null && ! locales.isEmpty()) {
  			// For some reason the locale set can contain more than one element,
  			// we just take the first one.
  			String code = locales.iterator().next();
  			Locale locale = null;
  
  			List<String> localeSplits = ImmutableList.copyOf(LOCALE_SPLITTER.split(code));
  			switch (localeSplits.size()) {
  			case 1: locale = new Locale(localeSplits.get(0)); break;
  			case 2: locale = new Locale(localeSplits.get(0), localeSplits.get(1)); break;
  			case 3: locale = new Locale(localeSplits.get(0), localeSplits.get(1), localeSplits.get(2)); break;
  			}
  			if (locale != null && locale.toString().length() > 0) {
  				return locale;
  			}
  		}
  
  		// Otherwise we guess the locale on the basis of the concrete language name.
  		// We assume that the name contains the language information in the last 3 chars.
  		// If this 3-letter sequence is not found in our map, then we assume it is
  		// a 3-letter ISO language code (capitalized).
  		int len = language.length();
  		if (len >= 3) {
  			String iso3 = language.substring(len-3, len);
  			Locale locale = ISO3_TO_LOCALE.get(iso3);
  			if (locale == null && Character.isUpperCase(iso3.charAt(0))) {
  				locale = new Locale(iso3);
  			}
  			if (locale != null && locale.toString().length() > 0) {
  				return locale;
  			}
  		}
  		// Guessing failed, return null
  		return null;
  	}
  
  
  	private static String getLocaleDisplayLanguage(Locale locale) {
  		if (locale == null) {
  			return null;
  		}
  		return locale.getDisplayLanguage(locale);
  	}
  }
  