/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.portal.mop.i18n;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.chromattic.api.annotations.Create;
import org.chromattic.api.annotations.MappedBy;
import org.chromattic.api.annotations.MixinType;
import org.chromattic.api.annotations.OneToOne;
import org.chromattic.api.annotations.Owner;
import org.exoplatform.commons.utils.I18N;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;

/**
 * @author <a href="mailto:khoi.nguyen@exoplatform.com">Nguyen Duc Khoi</a>
 * Apr 15, 2011
 */

/**
 * The entry point for carrying the information and can be attached to mop entities
 */
@MixinType(name = "gtn:i18nized")
public abstract class I18Nized {
    /** . */
    private static final Logger log = LoggerFactory.getLogger(I18Nized.class);

    private static Locale parent(Locale locale) {
        if (locale.getVariant() != null && !locale.getVariant().isEmpty()) {
            return new Locale(locale.getLanguage(), locale.getCountry());
        } else if (locale.getCountry() != null && !locale.getCountry().isEmpty()) {
            return new Locale(locale.getLanguage());
        } else {
            return null;
        }
    }

    @Create
    protected abstract LanguageSpace createLanguageSpace();

    @OneToOne
    @Owner
    @MappedBy("gtn:languages")
    protected abstract LanguageSpace getLanguageSpace();

    protected abstract void setLanguageSpace(LanguageSpace languageSpace);

    public <M> Resolution<M> resolveMixin(Class<M> mixinType, Locale wantedLocale) {
        if (mixinType == null) {
            throw new NullPointerException("No null mixin type accepted");
        }
        if (wantedLocale == null) {
            throw new NullPointerException("No null wanted locale accepted");
        }
        for (Locale current = wantedLocale; current != null; current = parent(current)) {
            M mixin = getMixin(mixinType, current, false);
            if (mixin != null) {
                return new Resolution<M>(current, mixin);
            }
        }
        return null;
    }

    public <M> Map<Locale, M> getMixins(Class<M> mixinType) {
        if (mixinType == null) {
            throw new NullPointerException("No null mixin type accepted");
        }
        Map<Locale, M> mixins = new HashMap<Locale, M>();
        LanguageSpace languageSpace = getLanguageSpace();
        if (languageSpace != null) {
            for (Map.Entry<String, Language> entry : languageSpace.getChildren().entrySet()) {
                M mixin = entry.getValue().getMixin(mixinType, false);
                if (mixin != null) {
                    String lang = entry.getKey();
                    try {
                        Locale locale = I18N.parseTagIdentifier(lang);
                        mixins.put(locale, mixin);
                    } catch (IllegalArgumentException e) {
                        log.debug("Skipping locale " + lang + " from retrieved locales for mixin " + mixinType.getName());
                    }
                }
            }
        }
        return mixins;
    }

    public <M> M getMixin(Class<M> mixinType, Locale locale, boolean createMixin) throws NullPointerException,
            IllegalArgumentException {
        if (mixinType == null) {
            throw new NullPointerException("No null mixin type accepted");
        }
        if (locale == null) {
            throw new NullPointerException("No null locale accepted");
        }
        if (locale.getLanguage().length() == 0) {
            throw new IllegalArgumentException("No language set on locale");
        }
        if (locale.getVariant().length() > 0) {
            throw new IllegalArgumentException("No variant cab be set on locale");
        }
        LanguageSpace languageSpace = getLanguageSpace();
        if (languageSpace == null && createMixin) {
            languageSpace = createLanguageSpace();
            setLanguageSpace(languageSpace);
        }
        if (languageSpace != null) {
            return languageSpace.getLanguage(mixinType, I18N.toTagIdentifier(locale), createMixin);
        } else {
            return null;
        }
    }

    public <M> Collection<Locale> removeMixin(Class<M> mixinType) {
        if (mixinType == null) {
            throw new NullPointerException("No null mixin type accepted");
        }
        Collection<Locale> locales = Collections.emptyList();
        LanguageSpace languageSpace = getLanguageSpace();
        if (languageSpace != null) {
            for (Language language : languageSpace.getChildren().values()) {
                if (language.removeMixin(mixinType)) {
                    String lang = language.getName();
                    try {
                        Locale locale = I18N.parseTagIdentifier(lang);
                        if (locales.isEmpty()) {
                            locales = new ArrayList<Locale>();
                        }
                        locales.add(locale);
                    } catch (IllegalArgumentException e) {
                        log.debug("Skipping locale " + lang + " from removed locales for mixin " + mixinType.getName());
                    }
                }
            }
        }
        return locales;
    }
}
