/*
 * 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.alert.ejb;

import com.queplix.core.integrator.security.AccessRightsManager;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.integrator.security.NoSuchGroupException;
import com.queplix.core.integrator.security.User;
import com.queplix.core.modules.alert.AlertBlock;
import com.queplix.core.modules.alert.AlertConstants;
import com.queplix.core.modules.alert.AlertData;
import com.queplix.core.modules.alert.utils.AlertCache;
import com.queplix.core.modules.alert.utils.AlertPropertyFactory;
import com.queplix.core.modules.alert.utils.AlertSelectorCriteria;
import com.queplix.core.modules.alert.utils.AlertVO;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.utils.DateHelper;
import com.queplix.core.utils.ejb.AbstractSessionEJB;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.SortedMap;

/**
 * Alert manager.
 * Performs messaging module (alerts) business logic.
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.3 $ $Date: 2006/07/24 08:24:06 $
 */

public class AlertManagerEJB
        extends AbstractSessionEJB {

    // ----------------------------------------------------- variables

    // Manadatoru attributes
    private String message;
    private int severityId;
    private LogonSession ls;

    // Optional attributes
    private Long alertID;
    private Long creatorID;
    private Integer creatorType;
    private Long senderID;
    private Integer senderType;
    private Date dateposted;
    private AlertData data;

    // ----------------------------------------------------- create method

    /**
     * Initialize bean for sending Alerts.
     *
     * @param message    user alert message
     * @param severityId user alert severity
     * @param ls         LogonSession object
     */
    public void ejbCreate(String message, int severityId, LogonSession ls) {

        // Init.
        init(ls);

        if(message == null) {
            throw new IllegalStateException("Message is NULL");
        }
        this.message = message;

        switch(severityId) {
            case AlertConstants.SEVERITY_CRITICAL:
            case AlertConstants.SEVERITY_SPECIAL:
            case AlertConstants.SEVERITY_HIGH:
            case AlertConstants.SEVERITY_MEDIUM:
            case AlertConstants.SEVERITY_LOW:
                break;
            default:
                throw new IllegalStateException(
                        "Unsupported alert severity: " + severityId);
        }
        this.severityId = severityId;
    }

    /**
     * Initialize bean for working with existing Alerts.
     *
     * @param ls LogonSession object
     */
    public void ejbCreate(LogonSession ls) {
        // Init.
        init(ls);
    }

    // ----------------------------------------------------- setter/getter methods

    //
    // Alert ID
    //

    public void setId(long alertID) {
        this.alertID = new Long(alertID);
    }

    public Long getId() {
        return alertID;
    }

    public void resetId() {
        this.alertID = null;
    }

    //
    // Alert Creator
    //

    public void setCreator(long creatorID, int creatorType) {
        this.creatorID = creatorID;
        this.creatorType = creatorType;
    }

    public long getCreatorID() {
        if(creatorID == null) {
            creatorID = ls.getUser().getUserID();
        }
        return creatorID;
    }

    public int getCreatorType() {
        if(creatorType == null) {
            creatorType = ls.getUser().getAuthenticationType();
        }
        return creatorType;
    }

    //
    // Alert Sender
    //

    public void setSender(long senderID, int senderType) {
        this.senderID = senderID;
        this.senderType = senderType;
    }

    public long getSenderID() {
        if(senderID == null) {
            senderID = ls.getUser().getUserID();
        }
        return senderID;
    }

    public int getSenderType() {
        if(senderType == null) {
            senderType = new Integer(ls.getUser().getAuthenticationType());
        }
        return senderType;
    }

    //
    // Alert Date Posted
    //

    public void setDateposted(Date dateposted) {
        this.dateposted = dateposted;
    }

    public Date getDateposted() {
        if(dateposted == null) {
            dateposted = DateHelper.getNowDate();
        }
        return dateposted;
    }

    //
    // Alert Add-On Data
    //

    public void setData(AlertData data) {
        this.data = data;
    }

    public AlertData getData() {
        return data;
    }

    // ----------------------------------------------------- busines methods

    /**
     * Find alerts using search criteria <code>criteria</code>.
     *
     * @param criteria search criteria
     * @return SortedMap of AlertVO objects
     */
    public SortedMap getAlerts(AlertSelectorCriteria criteria) {

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to find alerts in cache:");
            DEBUG("   Criteria: " + criteria);
        }

        // Do search.
        SortedMap ret = AlertPropertyFactory.getInstance().getAlertSelector()
                .search(getCache(), criteria);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Found " + ((ret == null) ? 0:ret.size()) + " alerts.");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return ret;
    }

    /**
     * Send alert to all
     *
     * @return id of new alert
     */
    public long[] sendAlert() {

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to send alert to All users:");
            DEBUG(toString());
        }

        // Initialization.
        long alertID = -1;

        // Go!
        try {
            // start process
            AlertVO alertVO = startCreate();

            // send To All
            alertVO.setToAllRecipient(true);

            // end process
            endCreate(alertVO);

            // get new alert id
            alertID = alertVO.getAlertID();

        } catch (EQLException ex) {
            throwException(ex);
        }

        if(getLogger().isInfoEnabled()) {
            INFO("Alerts to All added (ID = " + alertID + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return new long[]{alertID};
    }

    /**
     * Send alert to user
     *
     * @param recipientID   ID of recipient of alert
     * @param recipientType Type of recipient of alert
     * @return id of new alert
     */
    public long[] sendAlert(long recipientID, int recipientType) {

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to send alert to User:");
            DEBUG("   Recipient: " + recipientID + ", type: " + recipientType);
            DEBUG(toString());
        }

        // Initialization.
        long alertID = -1;

        // Go!
        try {

            // start process
            AlertVO alertVO = startCreate();

            // send to Recipient
            alertVO.setUserRecipient(recipientID, recipientType);

            // end process
            endCreate(alertVO);

            // get new alert id
            alertID = alertVO.getAlertID();

        } catch (EQLException ex) {
            throwException(ex);
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Alert for user added (ID = " + alertID + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return new long[]{alertID};
    }

    /**
     * Send alert(s) to workgroup
     *
     * @param workgroupID ID of workgroup - recipient of alert
     * @param tier        workgroup tier
     * @param toAll       send alert to all users in workgroup
     * @return array of ids of new alerts
     */
    public long[] sendAlerts(long workgroupID, Integer tier, boolean toAll) {

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to send alert to Workgroup:");
            DEBUG("   Workgroup: " + workgroupID + " [" + tier + "]");
            DEBUG("   To All: " + toAll);
            DEBUG(toString());
        }

        // Initialization.
        long[] alertIDs = null;

        // Go!
        if(toAll) {
            //
            // Add alert for any user in workgroup
            //

            Collection<User> memeberList = getGroupMemebers(workgroupID, tier);
            int size = (memeberList == null) ? 0:memeberList.size();

            if(size > 0) {
                alertIDs = new long[size];
                int counter = 0;
                for(User recipient : memeberList) {
                    try {
                        // start process
                        AlertVO alertVO = startCreate();

                        // send to Recipient
                        alertVO.setUserRecipient(recipient.getUserID(),
                                recipient.getAuthenticationType());

                        // end process
                        endCreate(alertVO);

                        // get new alert id
                        alertIDs[counter++] = alertVO.getAlertID();

                        // Reset ID for next cycle
                        if(size > 1) {
                            resetId();
                        }

                    } catch (EQLException ex) {
                        throwException(ex);
                    }
                }
            }
        } else {

            //
            // Add alert only for workgroup
            //

            alertIDs = new long[1];

            try {

                // start process
                AlertVO alertVO = startCreate();

                // send to Recipient
                alertVO.setWorkgroupRecipient(workgroupID, tier);

                // end process
                endCreate(alertVO);

                // get new alert id
                alertIDs[0] = alertVO.getAlertID();

            } catch (EQLException ex) {
                throwException(ex);
            }
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Alerts for workgroup added:");
            for(int i = 0; i < alertIDs.length; i++) {
                INFO("     " + alertIDs[i]);
            }
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return alertIDs;
    }

    /**
     * Update alerts.
     * WARNING: now it update alert by ID <code>this.alertID</code>
     *
     * @return long[] ID of updated alerts.
     */
    public long[] updateAlert() {

        long time = System.currentTimeMillis();
        Long id = getId();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to update alert:");
            DEBUG(toString());
        }

        // Initialization.
        if(id == null) {
            throw new NullPointerException("Alert ID is not set");
        }
        AlertCache cache = getCache();

        // Get alert by ID.
        AlertVO alertVO = (AlertVO) cache.get(alertID);
        if(alertVO == null) {
            throw new NullPointerException(
                    "Alert #" + alertID + " not found in the cache");
        }

        // Set new message and severity.
        alertVO.setMessage(message);
        alertVO.setSeverity(severityId);

        // Try to update alert.
        AlertPropertyFactory.getInstance().getAlertDAO().updateAlertVO(alertVO);

        // Notify cache that object has been updated.
        cache.put(id, alertVO);

        if(getLogger().isInfoEnabled()) {
            INFO("Alert updated (ID = " + alertID + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return new long[]{id.longValue()};

    }

    /**
     * Delete alerts.
     * WARNING: now it deletes alert by ID <code>this.alertID</code>
     * and for current user <code>ls</code> only.
     *
     * @return long[] ID of deleted alerts.
     */
    public long[] deleteAlerts() {

        long time = System.currentTimeMillis();
        Long id = getId();
        User user = ls.getUser();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to delete alerts:");
            DEBUG("   ID: " + id);
        }
        // Initialization.
        if(id == null) {
            throw new NullPointerException("Alert ID is not set");
        }
        AlertCache cache = getCache();

        // Get alert by ID.
        AlertVO alertVO = (AlertVO) cache.get(id);

        // Try to remove it.
        long[] alertIDs = null;
        if(alertVO != null) {

//            if( SecurityManager.isAdmin( user ) || alertVO.isOwner( user ) || alertVO.isSender( user ) ) {
            if(alertVO.isOwner(user) || alertVO.isSender(user)) {

                // Dedicated alert - remove it
                AlertPropertyFactory.getInstance().getAlertDAO().
                        deleteAlertVO(alertVO);

                // Remove from the cache.
                cache.remove(id);

                if(getLogger().isDebugEnabled()) {
                    DEBUG("Alert: " + alertVO + " has been removed physically");
                }

            } else {
                // Alert sent for workgroup - just create block for target user
                AlertBlock block = new AlertBlock(user.getUserID(),
                        user.getAuthenticationType());
                AlertPropertyFactory.getInstance().getAlertDAO().blockAlertVO(
                        alertVO, block);

                // Add <code>user</code> to blocked list.
                alertVO.addBlock(block);

                // Notify cache that object has been updated.
                cache.put(id, alertVO);

                if(getLogger().isDebugEnabled()) {
                    DEBUG("User: " + user
                            + " has been placed to the block list of alert: " +
                            alertVO);
                }
            }

            // Add to the list.
            alertIDs = new long[1];
            alertIDs[0] = alertID.longValue();
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Alerts removed:");
            int size = (alertIDs == null) ? 0:alertIDs.length;
            for(int i = 0; i < size; i++) {
                INFO("     " + alertIDs[i]);
            }
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }

        return alertIDs;
    }

    /*
     * No javadoc
     * @see Object#toString
     */
    public String toString() {
        return
                "   ID: " + alertID + "\n" +
                        "   Message: " + message + "\n" +
                        "   Severity: " + severityId + "\n" +
                        "   Date: " + dateposted + "\n" +
                        "   Creator: " + creatorID + ", type: " + creatorType
                        + "\n" +
                        "   Sender: " + senderID + ", type: " + senderType
                        + "\n" +
                        "   Data: " + data;
    }

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

    //
    // Initialization.
    //

    private void init(LogonSession ls) {
        if(ls == null) {
            throw new IllegalStateException("Logon session is NULL");
        }
        this.ls = ls;
    }

    /**
     * Alert Cache instance getter.
     *
     * @return AlertCache
     */
    private AlertCache getCache() {

        // Get cache reference.
        AlertCache cache = AlertPropertyFactory.getInstance().getAlertCache();

        if(!cache.isInitialized()) {
            // Init cache.
            long t = System.currentTimeMillis();

            cache.init();

            INFO("Cache initialized! Time(ms): " + (System.currentTimeMillis()
                    - t));
            if(getLogger().isDebugEnabled()) {
                DEBUG("Cache: " + cache);
            }
        }

        return cache;
    }

    //
    // Prepare for Alert creation.
    //
    private AlertVO startCreate()
            throws EQLException {

        // Checking.
        if(message == null) {
            throw new NullPointerException(
                    "Bean AlertManager initialized improperly");
        }

        // Get alert ID.
        long id;
        if(alertID == null) {
            id = AlertPropertyFactory.getInstance().getAlertDAO()
                    .getNextAlertID();
        } else {
            id = alertID.longValue();
        }

        // Create new AlertVO.
        AlertVO alertVO = new AlertVO(id,
                getCreatorID(),
                getCreatorType(),
                getSenderID(),
                getSenderType(),
                message,
                severityId,
                getDateposted());

        // Set Add-On data.
        alertVO.setData(data);

        return alertVO;
    }

    //
    // Create new Alert and store instance in the cache.
    //
    private void endCreate(AlertVO alertVO)
            throws EQLException {

        // Init.
        AlertCache cache = getCache();

        // Save alert.
        AlertPropertyFactory.getInstance().getAlertDAO().storeAlertVO(alertVO);

        // Add to the cache.
        cache.put(alertVO);

        if(getLogger().isDebugEnabled()) {
            DEBUG("New cache instance added: " + alertVO);
            DEBUG("Cache state: " + cache);
        }
    }

    //
    // Get group members
    //
    private Collection<User> getGroupMemebers(long workgroupID, Integer tier) {
        try {
            return AccessRightsManager.getUsersInGroup(workgroupID, tier);
        } catch (NoSuchGroupException e) {
            return Collections.EMPTY_LIST;
        }
    }

} // end of class
