/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.modules.config.ejb;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.modules.config.jxb.Caption;
import com.queplix.core.modules.config.jxb.Captions;
import com.queplix.core.modules.config.jxb.Description;
import com.queplix.core.modules.config.jxb.Descriptions;
import com.queplix.core.modules.config.jxb.Htmlcontent;
import com.queplix.core.modules.config.jxb.Htmlcontents;
import com.queplix.core.modules.config.jxb.LocalizedObject;
import com.queplix.core.modules.config.jxb.LocalizedObjectsId;
import com.queplix.core.modules.config.utils.ConfigPropertyFactory;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.config.utils.LocalizationDAO;
import com.queplix.core.utils.SystemHelper;
import com.queplix.core.utils.cache.Cache;
import com.queplix.core.utils.ejb.AbstractSessionEJB;

import java.io.Serializable;

/**
 * LocalizationManager session ejb component
 *
 * @author [SVM] Maxim Suponya
 * @author [ALB] Baranov Andrey
 * @author Michael Trofimov
 */
public class LocalizationManagerEJB extends AbstractSessionEJB {

    static class LocalizedObjectTypes {
        public static final LocalizedObjectTypes LABEL
                = new LocalizedObjectTypes(0);
        public static final LocalizedObjectTypes FOCUS
                = new LocalizedObjectTypes(1);
        public static final LocalizedObjectTypes TAB = new LocalizedObjectTypes(
                2);
        public static final LocalizedObjectTypes FORM
                = new LocalizedObjectTypes(3);
        public static final LocalizedObjectTypes SERVER
                = new LocalizedObjectTypes(4);
        public static final LocalizedObjectTypes CLIENT
                = new LocalizedObjectTypes(5);
        public static final LocalizedObjectTypes POPUP_MENU
                = new LocalizedObjectTypes(6);
        public static final LocalizedObjectTypes SUB_FOCUS
                = new LocalizedObjectTypes(7);
        public static final LocalizedObjectTypes CONTEXT_MENU
                = new LocalizedObjectTypes(8);
        public static final LocalizedObjectTypes BUTTON
                = new LocalizedObjectTypes(9);
        public static final LocalizedObjectTypes HTML_ELEMENT
                = new LocalizedObjectTypes(10);

        private final int id;

        private LocalizedObjectTypes(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }
    }

    static class LocalizationTypes {
        public static final LocalizationTypes CAPTION = new LocalizationTypes(
                0);
        public static final LocalizationTypes DESCRIPTION
                = new LocalizationTypes(1);
        public static final LocalizationTypes HTML_CONTENT
                = new LocalizationTypes(2);

        private final int id;

        private LocalizationTypes(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }
    }

    static class LocalizedObjectsCacheKey implements Serializable, Comparable {

        private String objectId;
        private Integer objectType;
        private Integer localizationType;

        public LocalizedObjectsCacheKey(String objectId,
                                        LocalizedObjectTypes objectType,
                                        LocalizationTypes localizationType) {

            checkNull(objectId, "id");
            checkNull(objectType, "type");
            checkNull(localizationType, "localizationType");

            this.objectId = objectId;
            this.objectType = objectType.getId();
            this.localizationType = localizationType.getId();
        }

        public String getId() {
            return objectId;
        }

        public Integer getType() {
            return objectType;
        }

        public Integer getLocalizationType() {
            return localizationType;
        }


        /**
         * No javadoc
         *
         * @see Comparable#compareTo(Object)
         */
        public int compareTo(Object o) {
            if(o == this) {
                return 0;
            }

            LocalizedObjectsCacheKey c = (LocalizedObjectsCacheKey) o;
            if(objectId.equals(c.objectId)) {
                if(objectType.equals(c.objectType)) {
                    return localizationType.compareTo(c.localizationType);
                } else {
                    return objectType.compareTo(c.objectType);
                }
            } else {
                return objectId.compareTo(c.objectId);
            }
        }

        /**
         * No javadoc
         *
         * @see Object#hashCode()
         */
        public int hashCode() {
            return objectId.hashCode() | (objectType.hashCode() << 37)
                    + (37 * localizationType.hashCode());
        }

        /**
         * No javadoc
         *
         * @see Object#equals(Object)
         */
        public boolean equals(Object obj) {

            if(obj == null || !(obj instanceof LocalizedObjectsCacheKey)) {
                return false;
            }

            LocalizedObjectsCacheKey c = (LocalizedObjectsCacheKey) obj;
            return c.objectId.equals(objectId) && c.objectType.equals(
                    objectType)
                    && c.localizationType.equals(localizationType);
        }

        /**
         * No javadoc
         *
         * @see Object#toString()
         */
        public String toString() {
            return "id=" + objectId + ", type=" + objectType
                    + ", localizationType=" + localizationType;
        }

        private void checkNull(Object o, String name) {
            if(o == null) {
                throw new NullPointerException(name);
            }
        }
    }

    public void ejbCreate() {
    }

    /**
     * Fill Efield captions
     *
     * @param entityName Entity name attribute
     * @param efieldName Efield name attribute
     * @param captions   Captions object
     * @return id of Captions object
     */
    public String fillEfieldCaptions(String entityName, String efieldName,
                                     Captions captions) {
        String efieldId = EntityHelper.getFieldId(entityName, efieldName);
        fillCaptions(efieldId, LocalizedObjectTypes.LABEL, captions);
        return efieldId;
    }

    /**
     * Fill Focus captions
     *
     * @param focusId  focus id attribute
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillFocusCaptions(String focusId, Captions captions) {
        fillCaptions(focusId, LocalizedObjectTypes.FOCUS, captions);
        return focusId;
    }

    /**
     * Fill SubFocus captions
     *
     * @param subFocusId subfocus id attribute
     * @param captions   Captions object
     * @return id of Captions object
     */
    public String fillSubFocusCaptions(String subFocusId, Captions captions) {
        fillCaptions(subFocusId, LocalizedObjectTypes.SUB_FOCUS, captions);
        return subFocusId;
    }

    /**
     * Fill Tab captions
     *
     * @param tabId    tab id attribute
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillTabCaptions(String tabId, Captions captions) {
        fillCaptions(tabId, LocalizedObjectTypes.TAB, captions);
        return tabId;
    }

    /**
     * Fill Form captions
     *
     * @param formId   form id attribute
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillFormCaptions(String formId, Captions captions) {
        fillCaptions(formId, LocalizedObjectTypes.FORM, captions);
        return formId;
    }

    /**
     * Fill Button captions
     *
     * @param buttonId button id attribute
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillButtonCaptions(String buttonId, Captions captions) {
        fillCaptions(buttonId, LocalizedObjectTypes.BUTTON, captions);
        return buttonId;
    }

    /**
     * Fill server messages
     *
     * @param id       message id
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillServerMessages(String id, Captions captions) {
        fillCaptions(id, LocalizedObjectTypes.SERVER, captions);
        return id;
    }

    /**
     * Fill PopupMenu captions
     *
     * @param captionId id
     * @param captions  Captions object
     * @return id of Captions object
     */
    public String fillPopupMenuCaptions(String captionId, Captions captions) {
        fillCaptions(captionId, LocalizedObjectTypes.POPUP_MENU, captions);
        return captionId;
    }

    /**
     * Fill MenuItem captions
     *
     * @param menuId   id
     * @param captions Captions object
     * @return id of Captions object
     */
    public String fillMenuItemCaptions(String menuId, Captions captions) {
        fillCaptions(menuId, LocalizedObjectTypes.CONTEXT_MENU, captions);
        return menuId;
    }

    /**
     * Get Efield caption
     *
     * @param languageId language id
     * @param entityName Entity name attribute
     * @param efieldName Efield name attribute
     * @return localized Caption
     */
    public String getEfieldCaption(String languageId, String entityName,
                                   String efieldName) {
        String efieldId = EntityHelper.getFieldId(entityName, efieldName);
        return getLocalizedCaption(languageId, efieldId,
                LocalizedObjectTypes.LABEL);
    }

    /**
     * Get Focus caption
     *
     * @param languageId language id
     * @param focusId    focus id attribute
     * @return localized Caption
     */
    public String getFocusCaption(String languageId, String focusId) {
        return getLocalizedCaption(languageId, focusId,
                LocalizedObjectTypes.FOCUS);
    }

    /**
     * Get SubFocus caption
     *
     * @param languageId language id
     * @param subFocusId Subfocus id attribute
     * @return localized Caption
     */
    public String getSubFocusCaption(String languageId, String subFocusId) {
        return getLocalizedCaption(languageId, subFocusId,
                LocalizedObjectTypes.SUB_FOCUS);
    }

    /**
     * Get Tab caption
     *
     * @param languageId language id
     * @param tabId      tab id attribute
     * @return localized Caption
     */
    public String getTabCaption(String languageId, String tabId) {
        return getLocalizedCaption(languageId, tabId, LocalizedObjectTypes.TAB);
    }

    /**
     * Get Form caption
     *
     * @param languageId language id
     * @param formId     form id attribute
     * @return localized Caption
     */
    public String getFormCaption(String languageId, String formId) {
        return getLocalizedCaption(languageId, formId,
                LocalizedObjectTypes.FORM);
    }

    /**
     * Get Button caption
     *
     * @param languageId language id
     * @param buttonId   button id attribute
     * @return localized Caption
     */
    public String getButtonCaption(String languageId, String buttonId) {
        return getLocalizedCaption(languageId, buttonId,
                LocalizedObjectTypes.BUTTON);
    }

    /**
     * Get Popup Menu caption
     *
     * @param languageId language id
     * @param captionId  id
     * @return localized Caption
     */
    public String getPopupMenuCaption(String languageId, String captionId) {
        return getLocalizedCaption(languageId, captionId,
                LocalizedObjectTypes.POPUP_MENU);
    }

    /**
     * Get server message
     *
     * @param languageId language id
     * @param id         message id
     * @return localized Caption
     */
    public String getServerMessage(String languageId, String id) {
        return getLocalizedCaption(languageId, id, LocalizedObjectTypes.SERVER);
    }

    /**
     * Get menu item captions
     *
     * @param languageId language id
     * @param menuId     menu item id
     * @return localized Caption
     */
    public String getMenuItemCaption(String languageId, String menuId) {
        return getLocalizedCaption(languageId, menuId,
                LocalizedObjectTypes.CONTEXT_MENU);
    }

    /**
     * Fill form's descriptions
     *
     * @param formId       form id attribute
     * @param descriptions Descriptions object
     * @return id of Descriptions object
     */
    public String fillFormDescriptions(String formId,
                                       Descriptions descriptions) {
        fillDescriptions(formId, LocalizedObjectTypes.FORM, descriptions);
        return formId;
    }

    /**
     * Get form's descriptions
     *
     * @param languageId language id
     * @return localized Caption
     */
    public String getFormDescription(String languageId, String formId) {
        return getLocalizedDescription(languageId, formId,
                LocalizedObjectTypes.FORM);
    }

    /**
     * Fill html element's content
     *
     * @param htmlElementId html element id attribute
     * @param htmlContents  {@link Htmlcontents} object
     * @return id of Htmlcontents object
     */
    public String fillHtmlElementContents(String htmlElementId,
                                          Htmlcontents htmlContents) {
        fillHtmlContents(htmlElementId, LocalizedObjectTypes.HTML_ELEMENT,
                htmlContents);
        return htmlElementId;
    }

    /**
     * Get html element's content
     *
     * @param languageId language id
     * @return localized html element's content
     */
    public String getHtmlElementContent(String languageId,
                                        String htmlElementId) {
        return getLocalizedHtmlContent(languageId, htmlElementId,
                LocalizedObjectTypes.HTML_ELEMENT);
    }

    //---------------------------------------------------------------- private methods

    private void fillLocalizedObjects(LocalizedObjectsId objectsId,
                                      LocalizedObject[] objects) {
        // Clear cache.
        Cache cache = ConfigPropertyFactory.getInstance()
                .getLocalizationCache();
        cache.clear();

        LocalizationDAO dao = ConfigPropertyFactory.getInstance()
                .getLocalizationDAO();

        // Clear localized objects.
        dao.deleteLocalizedObjects(objectsId);

        // Create Caption object.
        dao.saveLocalizedObjects(objectsId, objects);

    }

    private void setupLocalizedObjectsId(LocalizedObjectsId objectId, String id,
                                         LocalizedObjectTypes objectType,
                                         LocalizationTypes localizationType) {

        objectId.setId(id);
        objectId.setType(objectType.getId());
        objectId.setLocalizationType(localizationType.getId());
    }

    //
    // fill captions
    //
    private void fillCaptions(String captionsId,
                              LocalizedObjectTypes objectType,
                              Captions captions) {

        setupLocalizedObjectsId(
                captions, captionsId, objectType, LocalizationTypes.CAPTION);

        fillLocalizedObjects(captions, captions.getCaption());
    }

    //
    // fill descriptions
    //
    private void fillDescriptions(String descriptionsId,
                                  LocalizedObjectTypes objectType,
                                  Descriptions descriptions) {

        setupLocalizedObjectsId(
                descriptions, descriptionsId, objectType,
                LocalizationTypes.DESCRIPTION);

        fillLocalizedObjects(descriptions, descriptions.getDescription());
    }

    //
    // fill html content
    //
    private void fillHtmlContents(String htmlContentsId,
                                  LocalizedObjectTypes objectType,
                                  Htmlcontents htmlContents) {

        setupLocalizedObjectsId(
                htmlContents, htmlContentsId, objectType,
                LocalizationTypes.HTML_CONTENT);

        fillLocalizedObjects(htmlContents, htmlContents.getHtmlcontent());
    }

    @SuppressWarnings("unchecked")
    private <C extends LocalizedObjectsId, T extends LocalizedObject> C getLocalizedObjects(
            String objectsId, String langId, LocalizedObjectTypes objectType,
            LocalizationTypes localizationType,
            Class<C> objectsContainerClass, Class<T> objectClass) {

        if(objectsId == null) {
            throw new NullPointerException("Localized objects id is null");
        }

        // Build unique key.
        LocalizedObjectsCacheKey objectsKey = new LocalizedObjectsCacheKey(
                objectsId, objectType, localizationType);

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get localized objects: " + objectsKey);
        }

        // Try to find out in cache.
        Cache cache = ConfigPropertyFactory.getInstance()
                .getLocalizationCache();
        C objectsContainer = (C) cache.get(objectsKey);

        if(objectsContainer == null) {
            // Load record from database.
            try {
                objectsContainer = objectsContainerClass.newInstance();
            } catch (InstantiationException e) {
                throw new GenericSystemException(
                        "InstantiationException: " + e.getMessage(), e);
            } catch (IllegalAccessException e) {
                throw new GenericSystemException(
                        "IllegalAccessException: " + e.getMessage(), e);
            }

            setupLocalizedObjectsId(
                    objectsContainer, objectsId, objectType, localizationType);

            T[] objects = ConfigPropertyFactory.getInstance()
                    .getLocalizationDAO()
                    .loadLocalizedObjects(objectsContainer, objectClass);
            for(T object : objects) {
                objectsContainer.putObject(langId, object);
            }

            // Store in cache.
            cache.put(objectsKey, objectsContainer);
        } else {
            // Found in cache!
            if(getLogger().isDebugEnabled()) {
                DEBUG("Localized object: " + objectsKey
                        + " found in the cache.");
            }
        }
        return objectsContainer;
    }

    /**
     * Returns localized content by given language.
     *
     * @param objectsContainer Container objects that contains collection of localized contents
     * @param langId           Language id.
     * @return Localized content
     */
    private String getLocalizedContent(LocalizedObjectsId objectsContainer,
                                       String langId) {
        // Get localized object for requested language.
        LocalizedObject object = null;
        if(objectsContainer != null) {
            object = (LocalizedObject) objectsContainer.getObject(langId);
            if(object == null) {
                // ... try to find for 'default' language
                object = (LocalizedObject) objectsContainer.getObject(
                        SystemHelper.DEFAULT_LANGUAGE);
            }
        }
        if(object == null) {
            DEBUG("Localized content for object [" + objectsContainer.getId()
                    + "], type [" + objectsContainer.getLocalizationType()
                    + "] not found");
            return "<" + objectsContainer.getId() + ">";
        }

        return object.getContent();
    }

    private String getLocalizedCaption(String langId,
                                       String captionId,
                                       LocalizedObjectTypes objectType) {

        Captions captions = getLocalizedObjects(captionId, langId, objectType,
                LocalizationTypes.CAPTION, Captions.class, Caption.class);

        // Complete captions container object
        for(String key : captions.getObjectNames()) {
            Caption caption = (Caption) captions.getObject(key);
            captions.addCaption(caption);
        }

        return getLocalizedContent(captions, langId);
    }

    private String getLocalizedDescription(String langId,
                                           String descriptionId,
                                           LocalizedObjectTypes objectType) {

        Descriptions descriptions = getLocalizedObjects(descriptionId, langId,
                objectType,
                LocalizationTypes.DESCRIPTION, Descriptions.class,
                Description.class);

        // Complete descriptions container object
        for(String key : descriptions.getObjectNames()) {
            Description description = (Description) descriptions.getObject(key);
            descriptions.addDescription(description);
        }

        return getLocalizedContent(descriptions, langId);
    }

    private String getLocalizedHtmlContent(String langId,
                                           String htmlContentId,
                                           LocalizedObjectTypes objectType) {

        Htmlcontents htmlContents = getLocalizedObjects(htmlContentId, langId,
                objectType,
                LocalizationTypes.HTML_CONTENT, Htmlcontents.class,
                Htmlcontent.class);

        // Complete descriptions container object
        for(String key : htmlContents.getObjectNames()) {
            Htmlcontent htmlContent = (Htmlcontent) htmlContents.getObject(key);
            htmlContents.addHtmlcontent(htmlContent);
        }

        return getLocalizedContent(htmlContents, langId);
    }

}
