/*
 * 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.integrator.security.User;
import com.queplix.core.modules.config.utils.ConfigPropertyFactory;
import com.queplix.core.modules.config.utils.DialogSetting;
import com.queplix.core.modules.config.utils.FormSetting;
import com.queplix.core.modules.config.utils.FormSettingDefImpl;
import com.queplix.core.modules.config.utils.GridSetting;
import com.queplix.core.modules.config.utils.GridSettingDefImpl;
import com.queplix.core.modules.config.utils.UserPropertyID;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.cache.Cache;
import com.queplix.core.utils.ejb.AbstractSessionEJB;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * User properties management session EJB.
 *
 * @author [DAG] Dmitry Gaponenko
 * @author [ALB] Baranov Andrey
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:15 $
 */

public class UserPropertyManagerEJB extends AbstractSessionEJB {
    private static final String FIELDS_FOR_GRID_PROP_PREFIX = "__fieldForGrid_";
    private static final String DIALOG_SETTINGS_PROP_PREFIX
            = "__dialogUISettings_";
    private static final String GRID_SETTINGS_PROP_PREFIX = "__gridSettings_";
    private static final String FORM_SETTINGS_PROP_PREFIX = "__formSettings_";
    private static final String DIVIDER_UI_SETTINGS_CHAR = ":";

    /**
     * Initialize bean.
     */
    public void ejbCreate() {
    }

    /**
     * Delete user property.
     *
     * @param propId property id
     * @param user   User object
     * @return UserPropertyID
     */
    public UserPropertyID deleteUserProperty(String propId, User user) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to delete user property" +
                    ". Id: " + propId +
                    ". User: " + user + ".");
        }

        // Delete record.
        UserPropertyID key = new UserPropertyID(propId, user.getUserID(),
                user.getAuthenticationType());
        ConfigPropertyFactory.getInstance().getUserPropertyConfigDAO()
                .deleteUserProperty(key);

        // Remove from cache.
        Cache cache = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigCache();
        cache.remove(key);

        // Ok.
        return key;
    }

    /**
     * Create user property.
     *
     * @param propId property id
     * @param value  some value
     * @param user   User object
     */
    public void createUserProperty(String propId, String value, User user) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to create user property" +
                    ". Id: " + propId +
                    ". User: " + user +
                    ". Value: " + value + ".");
        }

        // Insert new record.
        UserPropertyID key = new UserPropertyID(propId, user.getUserID(),
                user.getAuthenticationType());
        ConfigPropertyFactory.getInstance().getUserPropertyConfigDAO()
                .createUserProperty(key, value);

        // Store in cache.
        Cache cache = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigCache();
        cache.put(key, value);
    }

    /**
     * Set user property.
     *
     * @param propId property id
     * @param value  some value
     * @param user   User object
     * @return UserPropertyID
     */
    public UserPropertyID setUserProperty(String propId, String value,
                                          User user) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to update user property" +
                    ". Id: " + propId +
                    ". User: " + user +
                    ". Value: " + value + ".");
        }

        // Update record.
        UserPropertyID key = new UserPropertyID(propId, user.getUserID(),
                user.getAuthenticationType());
        boolean areEqual = false;

        Cache cache = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigCache();
        if(cache.containsKey(key)) {
            String cachedValue = (String) cache.get(key);
            if(cachedValue != null) {
                if(cachedValue.equals(value)) {
                    areEqual = true;
                }
            } else if(value == null) {
                areEqual = true;
            }
        }
        if(!areEqual) {
            int rows = ConfigPropertyFactory.getInstance()
                    .getUserPropertyConfigDAO().updateUserProperty(key, value);

            if(rows == 0) {
                // insert record
                createUserProperty(propId, value, user);
            } else {
                // store in cache
                cache.put(key, value);
            }
        }

        return key;
    }

    /**
     * Get user property.
     *
     * @param propId property id
     * @param user   User object
     * @return some value
     */
    public String getUserProperty(String propId, User user) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get user property" +
                    ". Id: " + propId +
                    ". User: " + user + ".");
        }

        // Try to find out in cache.
        UserPropertyID key = new UserPropertyID(propId, user.getUserID(),
                user.getAuthenticationType());
        String value;

        Cache cache = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigCache();
        if(!cache.containsKey(key)) {
            // Load property from database.
            value = ConfigPropertyFactory.getInstance()
                    .getUserPropertyConfigDAO().loadUserProperty(key);

            // Store in cache.
            cache.put(key, value);

        } else {
            // Found in cache!
            if(getLogger().isDebugEnabled()) {
                DEBUG("Property '" + key + "' found in cache.");
            }
            value = (String) cache.get(key);
        }

        return value;
    }

    /**
     * Get all user properties as a map
     *
     * @param user User object
     * @return map of properties
     */
    public Map getAllUserProperties(User user) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get all user properties" +
                    ". User: " + user + ".");
        }

        // Load properties from database.
        return ConfigPropertyFactory.getInstance().getUserPropertyConfigDAO()
                .loadAllUserProperty(user);
    }

    /**
     * Retrieves collections of fields to represent in grid
     *
     * @param user   User object
     * @param entity Entity name
     * @return Array of fields names
     */
    public String[] getFieldsForGrid(User user, String entity) {
        String[] fields = null;
        String fieldsValue = getUserProperty(
                FIELDS_FOR_GRID_PROP_PREFIX + entity, user);
        if(fieldsValue != null) {
            fields = StringHelper.split(fieldsValue, ";");
        }
        return fields;
    }

    /**
     * Sets collections of fields represented in grid
     *
     * @param user   User object
     * @param entity Entity name
     * @param fields Fields names to set.
     */
    public UserPropertyID setFieldsForGrid(User user, String entity,
                                           String[] fields) {
        String fieldsValue = StringHelper.join(fields, ";");
        return setUserProperty(FIELDS_FOR_GRID_PROP_PREFIX + entity,
                fieldsValue, user);
    }

    /**
     * Delete all dialog ui settings for the given user
     *
     * @param user user
     */
    public void deleteDialogUISettings(User user) {
        deletePropsStartsWith(user, DIALOG_SETTINGS_PROP_PREFIX);
    }

    /**
     * Updates the given dialog ui settings without any cleaning for the given user.
     *
     * @param user           user
     * @param settingsToSave settings to save
     */
    public void updateDialogUISettings(User user,
                                       DialogSetting settingsToSave) {
        setUserProperty(getSerizlizedDialogSettingKey(settingsToSave),
                getSerializedDialogSettingValue(settingsToSave), user);
    }

    /**
     * We delete all user settings and recreate it again to optimize future all props loading.
     *
     * @param user           user
     * @param settingsToSave settings to save
     */
    public void resetDialogUISettings(User user,
                                      Collection<DialogSetting> settingsToSave) {
        deleteDialogUISettings(user);
        for(DialogSetting dialogSetting : settingsToSave) {
            setUserProperty(getSerizlizedDialogSettingKey(dialogSetting),
                    getSerializedDialogSettingValue(dialogSetting), user);
        }
    }

    public Collection<DialogSetting> loadDialogsUISettings(User user) {
        Collection<DialogSetting> ret = new LinkedList<DialogSetting>();
        Map<String, String> uiProps = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigDAO().
                loadUserPropertiesLike(user, DIALOG_SETTINGS_PROP_PREFIX);

        for(String uiElementId : uiProps.keySet()) {
            DialogSetting setting = deserializeDialogSettings(uiElementId,
                    uiProps.get(uiElementId));
            if(setting != null) {
                ret.add(setting);
            }
        }

        return ret;
    }

    public void deleteGridSettings(User user) {
        deletePropsStartsWith(user, GRID_SETTINGS_PROP_PREFIX);
    }

    public void updateGridSettings(User user, GridSetting settingsToSave) {
        setUserProperty(getSerizlizedGridSettingKey(settingsToSave),
                getSerializedGridSettingValue(settingsToSave), user);
    }

    public Collection<GridSetting> loadGridSettings(User user) {
        Collection<GridSetting> ret = new LinkedList<GridSetting>();
        Map<String, String> uiProps = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigDAO().
                loadUserPropertiesLike(user, GRID_SETTINGS_PROP_PREFIX);

        for(String uiElementId : uiProps.keySet()) {
            GridSetting setting = deserializeGridSettings(uiElementId,
                    uiProps.get(uiElementId));
            if(setting != null) {
                ret.add(setting);
            }
        }

        return ret;
    }

    private void deletePropsStartsWith(User user, String propStarting) {
        ConfigPropertyFactory.getInstance().getUserPropertyConfigDAO()
                .deleteUserPropertiesLike(user, propStarting);
        //clear all cache for user, because we need to be sure, that procedure updateUserProperty(key, value) works properly and store it in DB.
        Cache cache = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigCache();
        Collection<UserPropertyID> toDelete = new LinkedList<UserPropertyID>();
        for(Object objKey : cache.keys()) {
            UserPropertyID key = (UserPropertyID) objKey;
            if(key.getUserId() == user.getUserID() && key.getId().startsWith(
                    propStarting)) {
                toDelete.add(key);
            }
        }
        for(UserPropertyID key : toDelete) {
            cache.remove(key);
        }
    }

    private String getSerializedDialogSettingValue(
            DialogSetting dialogSetting) {
        return dialogSetting.getLeft() + DIVIDER_UI_SETTINGS_CHAR
                + dialogSetting.getTop() +
                DIVIDER_UI_SETTINGS_CHAR + dialogSetting.getWidth()
                + DIVIDER_UI_SETTINGS_CHAR
                + dialogSetting.getHeight();
    }

    private String getSerizlizedDialogSettingKey(DialogSetting dialogSetting) {
        return DIALOG_SETTINGS_PROP_PREFIX + dialogSetting.getFormId()
                + DIVIDER_UI_SETTINGS_CHAR + dialogSetting.getFieldId();
    }

    private DialogSetting deserializeDialogSettings(String uiElementId,
                                                    String uiElementValue) {
        String formAndField = uiElementId.substring(
                DIALOG_SETTINGS_PROP_PREFIX.length());
        String formAndFieldArr[] = formAndField.split(DIVIDER_UI_SETTINGS_CHAR);
        String sizes[] = uiElementValue.split(DIVIDER_UI_SETTINGS_CHAR);

        DialogSetting ret = null;
        if((formAndFieldArr.length >= 2) && (sizes.length >= 4)) {
            try {
                int left = Integer.parseInt(sizes[0]);
                int top = Integer.parseInt(sizes[1]);
                int width = Integer.parseInt(sizes[2]);
                int height = Integer.parseInt(sizes[3]);
                ret = new DialogSettingImpl(formAndFieldArr[0],
                        formAndFieldArr[1], left, top, width, height);
            } catch (Exception e) {
                //do nothing just return the null value
            }
        }
        return ret;
    }

    private String getSerializedGridSettingValue(GridSetting settings) {
        StringBuffer ret = new StringBuffer(Boolean.toString(
                settings.isCounterOn()) + DIVIDER_UI_SETTINGS_CHAR
                + Integer.toString(settings.getPageSize())
                + DIVIDER_UI_SETTINGS_CHAR);
        Map<String, Integer> columnWidths = settings.getColumnWidths();
        if(columnWidths != null) {
            for(String columnName : columnWidths.keySet()) {
                ret.append(columnName).append(DIVIDER_UI_SETTINGS_CHAR).append(
                        columnWidths.get(columnName)).append(
                        DIVIDER_UI_SETTINGS_CHAR);
            }
        }
        return ret.toString();
    }

    private String getSerizlizedGridSettingKey(GridSetting setting) {
        return GRID_SETTINGS_PROP_PREFIX + setting.getFormID();
    }

    private GridSetting deserializeGridSettings(String gridElementId,
                                                String gridElementValue) {
        String formId = gridElementId.substring(
                GRID_SETTINGS_PROP_PREFIX.length());
        String params[] = gridElementValue.split(DIVIDER_UI_SETTINGS_CHAR);
        GridSettingDefImpl setts = null;
        if(params.length >= 2) {
            try {
                boolean isCounter = Boolean.parseBoolean(params[0]);
                int pageSize = Integer.parseInt(params[1]);
                Map<String, Integer> columnsWidth
                        = new HashMap<String, Integer>();
                for(int i = 2; i < params.length - 1; i = i + 2) {
                    String columnName = params[i];
                    String columnWidth = params[i + 1];
                    Integer width = Integer.parseInt(columnWidth);
                    columnsWidth.put(columnName, width);
                }
                setts = new GridSettingDefImpl(formId, isCounter, pageSize,
                        columnsWidth);
            } catch (Exception e) {
                //do nothing just return the null value
            }
        }
        return setts;
    }

    private static class DialogSettingImpl implements DialogSetting {
        private String formId;
        private String fieldId;

        private int left;
        private int top;
        private int width;
        private int height;

        public DialogSettingImpl(String formId, String fieldId, int left,
                                 int top, int width, int height) {
            this.formId = formId;
            this.fieldId = fieldId;
            this.left = left;
            this.top = top;
            this.width = width;
            this.height = height;
        }

        public String getFormId() {
            return formId;
        }

        public String getFieldId() {
            return fieldId;
        }

        public int getLeft() {
            return left;
        }

        public int getTop() {
            return top;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }
    }

    public void deleteFormSettings(User user) {
        deletePropsStartsWith(user, FORM_SETTINGS_PROP_PREFIX);
    }

    private String getSerializedFormSettingValue(FormSetting formSetting) {
        return Boolean.toString(formSetting.isMinimized());
    }

    public void updateFormSettings(User user, FormSetting settingsToSave) {
        setUserProperty(getSerizlizedFormSettingKey(settingsToSave),
                getSerializedFormSettingValue(settingsToSave), user);
    }

    private String getSerizlizedFormSettingKey(FormSetting formSetting) {
        return FORM_SETTINGS_PROP_PREFIX + formSetting.getFormID();
    }

    public Collection<FormSetting> loadFormSettings(User user) {
        Collection<FormSetting> ret = new LinkedList<FormSetting>();
        Map<String, String> uiProps = ConfigPropertyFactory.getInstance()
                .getUserPropertyConfigDAO().
                loadUserPropertiesLike(user, FORM_SETTINGS_PROP_PREFIX);

        for(String uiElementId : uiProps.keySet()) {
            FormSetting setting = deserializeFormSettings(uiElementId,
                    uiProps.get(uiElementId));
            if(setting != null) {
                ret.add(setting);
            }
        }

        return ret;
    }

    private FormSetting deserializeFormSettings(String formElementId,
                                                String formElementValue) {
        String formId = formElementId.substring(
                GRID_SETTINGS_PROP_PREFIX.length());
        String params[] = formElementValue.split(DIVIDER_UI_SETTINGS_CHAR);
        FormSettingDefImpl setts = null;
        if(params.length > 0) {
            try {
                setts = new FormSettingDefImpl(formId, Boolean.parseBoolean(
                        params[0]));
            } catch (Exception e) {
                //do nothing just return the null value
            }
        }
        return setts;
    }

}
