/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.gui.legacy.action.resource.common.monitor.alerts;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.alert.AlertCondition;
import org.rhq.core.domain.alert.AlertConditionCategory;
import org.rhq.core.domain.alert.AlertDampening;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.core.domain.alert.AlertDefinitionContext;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.measurement.MeasurementUnits;
import org.rhq.core.domain.operation.OperationDefinition;
import org.rhq.core.server.MeasurementConverter;
import org.rhq.enterprise.gui.legacy.Constants;
import org.rhq.enterprise.gui.legacy.action.resource.ResourceForm.FormContext;
import org.rhq.enterprise.gui.legacy.beans.AlertConditionBean;
import org.rhq.enterprise.gui.legacy.beans.OptionItem;
import org.rhq.enterprise.gui.legacy.exception.ParameterNotFoundException;
import org.rhq.enterprise.gui.legacy.util.RequestUtils;
import org.rhq.enterprise.gui.util.EnterpriseFacesContextUtility;
import org.rhq.enterprise.server.alert.AlertDefinitionManagerLocal;
import org.rhq.enterprise.server.measurement.util.MeasurementFormatter;
import org.rhq.enterprise.server.operation.OperationManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;

/**
 * Utility class for dealing with rendering alert definition conditions.
 */
public final class AlertDefUtil {
    private static Log log = LogFactory.getLog(AlertDefUtil.class);

    public static long getSecondsConsideringUnits(Integer duration, AlertDampening.TimeUnits units) {
        return (units.getNumberOfSeconds() * duration);
    }

    /**
     * Converts the list of alert conditions into a list of AlertConditionBean objects.
     *
     * @param subject
     * @param conds   @return List of AlertConditionBean objects
     */
    public static List<AlertConditionBean> getAlertConditionBeanList(Subject subject, HttpServletRequest request,
        Set<AlertCondition> conds) {
        List<AlertConditionBean> alertCondBeans = new ArrayList<AlertConditionBean>(conds.size());

        boolean first = true;
        for (AlertCondition cond : conds) {
            String conditionDisplayText = formatAlertConditionForDisplay(cond, request);
            AlertConditionBean alertCondBean = new AlertConditionBean(conditionDisplayText, first);
            alertCondBeans.add(alertCondBean);
            first = false;
        }

        return alertCondBeans;
    }

    @SuppressWarnings("deprecation")
    public static String formatAlertConditionForDisplay(AlertCondition cond, HttpServletRequest request) {
        AlertConditionCategory category = cond.getCategory();

        StringBuffer textValue = new StringBuffer();

        // first format the LHS of the operator
        if (category == AlertConditionCategory.CONTROL) {
            try {
                Integer resourceTypeId = cond.getAlertDefinition().getResource().getResourceType().getId();
                String operationName = cond.getName();
                OperationManagerLocal operationManager = LookupUtil.getOperationManager();

                OperationDefinition definition = operationManager.getOperationDefinitionByResourceTypeAndName(
                    resourceTypeId, operationName, false);
                textValue.append(definition.getDisplayName()).append(' ');
            } catch (Exception e) {
                textValue.append(cond.getName()).append(' ');
            }
        } else if (category == AlertConditionCategory.RESOURCE_CONFIG) {
            textValue.append(RequestUtils.message(request, "alert.config.props.CB.Content.ResourceConfiguration"))
                .append(' ');
        } else if (cond.getMeasurementDefinition() != null
            && cond.getMeasurementDefinition().getDataType().equals(DataType.CALLTIME)) {
            textValue.append(cond.getMeasurementDefinition().getDisplayName());
            textValue.append(" (");
            textValue.append(cond.getOption());
            if (cond.getName() != null && !cond.getName().equals("")) {
                textValue.append("; pattern=[");
                textValue.append(cond.getName());
                textValue.append("]");
            }
            textValue.append(") ");
        } else {
            textValue.append(cond.getName()).append(' ');
        }

        // next format the RHS
        if (category == AlertConditionCategory.CONTROL) {
            textValue.append(cond.getOption());
        } else if ((category == AlertConditionCategory.THRESHOLD) || (category == AlertConditionCategory.BASELINE)) {
            textValue.append(cond.getComparator());
            textValue.append(' ');

            MeasurementSchedule schedule = null;

            MeasurementUnits units;
            double value = cond.getThreshold();
            if (category == AlertConditionCategory.THRESHOLD) {
                units = cond.getMeasurementDefinition().getUnits();
            } else // ( category == AlertConditionCategory.BASELINE )
            {
                units = MeasurementUnits.PERCENTAGE;
            }

            String formatted = MeasurementConverter.format(value, units, true);
            textValue.append(formatted);

            if (category == AlertConditionCategory.BASELINE) {
                textValue.append(" of ");
                textValue.append(MeasurementFormatter.getBaselineText(cond.getOption(), schedule));
            }
        } else if (category == AlertConditionCategory.RESOURCE_CONFIG || category == AlertConditionCategory.CHANGE
            || category == AlertConditionCategory.TRAIT) {
            if (cond.getComparator() != null) {
                textValue.append(RequestUtils.message(request, "common.field.value")).append(' ');
                textValue.append(RequestUtils.message(request, "alert.config.props.CB.Content.CalltimeOperators."
                    + cond.getComparator()));
            } else
                textValue.append(RequestUtils.message(request, "alert.current.list.ValueChanged"));

            if (cond.getThreshold() != null) {
                textValue.append(" ").append(RequestUtils.message(request, "alert.config.props.CB.Content.AtLeast"));
                textValue.append(" ").append(
                    MeasurementConverter.format(cond.getThreshold(), MeasurementUnits.PERCENTAGE, true));
            }
        } else if (category == AlertConditionCategory.EVENT) {
            String msgKey = "alert.config.props.CB.EventSeverity";
            List<String> args = new ArrayList<String>(2);

            args.add(cond.getName());
            if ((cond.getOption() != null) && (cond.getOption().length() > 0)) {
                msgKey += ".RegexMatch";
                args.add(cond.getOption());
            }

            textValue = new StringBuffer(RequestUtils.message(request, msgKey, args.toArray()));
        } else if (category == AlertConditionCategory.AVAILABILITY) {
            textValue = new StringBuffer(RequestUtils.message(request, "alert.config.props.CB.Availability",
                new String[] { cond.getOption() }));
        } else if (category == AlertConditionCategory.RANGE) {
            textValue = new StringBuffer();

            String metricName = cond.getName();
            MeasurementUnits units = cond.getMeasurementDefinition().getUnits();
            double loValue = cond.getThreshold();
            String formattedLoValue = MeasurementConverter.format(loValue, units, true);
            String formattedHiValue = cond.getOption();
            try {
                double hiValue = Double.parseDouble(formattedHiValue);
                formattedHiValue = MeasurementConverter.format(hiValue, units, true);
            } catch (Exception e) {
                formattedHiValue = "?[" + formattedHiValue + "]?"; // signify something is wrong with the value
            }
            // < means "inside the range", > means "outside the range" - exclusive
            // <= means "inside the range", >= means "outside the range" - inclusive
            if (cond.getComparator().equals("<")) {
                textValue.append("Metric Value Range: [" + metricName + "] between [" + formattedLoValue + "] and ["
                    + formattedHiValue + "], exclusive");
            } else if (cond.getComparator().equals(">")) {
                textValue.append("Metric Value Range: [" + metricName + "] outside [" + formattedLoValue + "] and ["
                    + formattedHiValue + "], exclusive");
            } else if (cond.getComparator().equals("<=")) {
                textValue.append("Metric Value Range: [" + metricName + "] between [" + formattedLoValue + "] and ["
                    + formattedHiValue + "], inclusive");
            } else if (cond.getComparator().equals(">=")) {
                textValue.append("Metric Value Range: [" + metricName + "] outside [" + formattedLoValue + "] and ["
                    + formattedHiValue + "], inclusive");
            } else {
                textValue.append("BAD COMPARATOR! Report this bug: " + cond.getComparator());
            }
        } else {
            // do nothing
        }

        return textValue.toString();
    }

    /**
     * Sets the following request attributes based on what's contained in the AlertConditionValue.
     *
     * <ul>
     *   <li>enableActionsResource - resource bundle key for display</li>
     *   <li>enableActionsHowLong - how long</li>
     *   <li>enableActionsHowLongUnits - units (i.e. -- ALERT_ACTION_ENABLE_UNITS_WEEKS)</li>
     *   <li>enableActionsHowMany - number of times condition occurs</li>
     * </ul>
     *
     * @param request  the http request
     * @param alertDef
     */
    public static void setAlertDampeningRequestAttributes(HttpServletRequest request, AlertDefinition alertDefinition) {
        AlertDampening alertDampening = alertDefinition.getAlertDampening();

        Integer enableActionsHowLong = alertDampening.getPeriod();
        AlertDampening.TimeUnits enableActionsHowLongUnits = alertDampening.getPeriodUnits();

        Integer enableActionsHowMany = alertDampening.getValue();
        AlertDampening.TimeUnits enableActionsHowManyUnits = alertDampening.getValueUnits();

        String enableActionsResource;
        AlertDampening.Category alertDampeningCategory = alertDampening.getCategory();

        if (alertDampeningCategory == AlertDampening.Category.NONE) {
            enableActionsResource = "alert.config.props.CB.DampenNone";
        } else if (alertDampeningCategory == AlertDampening.Category.ONCE) {
            enableActionsResource = "alert.config.props.CB.DampenOnce";
        } else if (alertDampeningCategory == AlertDampening.Category.CONSECUTIVE_COUNT) {
            enableActionsResource = "alert.config.props.CB.DampenConsecutiveCount";
        } else if (alertDampeningCategory == AlertDampening.Category.PARTIAL_COUNT) {
            enableActionsResource = "alert.config.props.CB.DampenPartialCount";
        } else if (alertDampeningCategory == AlertDampening.Category.INVERSE_COUNT) {
            enableActionsResource = "alert.config.props.CB.DampenInverseCount";
        } else if (alertDampeningCategory == AlertDampening.Category.DURATION_COUNT) {
            enableActionsResource = "alert.config.props.CB.DampenDurationCount";
        } else {
            throw new RuntimeException("Alert dampening category " + alertDampeningCategory
                + " does not have a corresponding message in ApplicationResources.properties");
        }

        request.setAttribute("enableActionsResource", enableActionsResource);
        request.setAttribute("enableActionsHowLong", enableActionsHowLong); // {0}
        request.setAttribute("enableActionsHowLongUnits", enableActionsHowLongUnits); // {1}
        request.setAttribute("enableActionsHowMany", enableActionsHowMany); // {2}
        request.setAttribute("enableActionsHowManyUnits", enableActionsHowManyUnits); // {3}
    }

    /**
     * Retrieve the alert definition from either the request or from the bizapp as necessary. First check to see if the
     * alertDef is already in the request attributes. If it is, return it. If not, look for an "ad" parameter and then
     * get the alert definition from the bizapp and return it.
     */
    @Deprecated
    public static AlertDefinition getAlertDefinition(HttpServletRequest request) throws ServletException {
        AlertDefinition alertDefinition = (AlertDefinition) request.getAttribute(Constants.ALERT_DEFINITION_ATTR);

        if (null == alertDefinition) {
            String alertDefinitionParameter = request.getParameter(Constants.ALERT_DEFINITION_PARAM);

            if (null == alertDefinitionParameter) {
                throw new ParameterNotFoundException(Constants.ALERT_DEFINITION_PARAM);
            } else {
                Subject user = RequestUtils.getSubject(request);
                int alertDefinitionId = Integer.parseInt(alertDefinitionParameter);
                alertDefinition = LookupUtil.getAlertDefinitionManager()
                    .getAlertDefinitionById(user, alertDefinitionId);
                request.setAttribute(Constants.ALERT_DEFINITION_ATTR, alertDefinition);
                request.setAttribute("alertNotifCount", alertDefinition.getAlertNotifications().size());

                AlertDefinitionContext context = AlertDefinitionContext.get(alertDefinition);
                if (context == AlertDefinitionContext.Type) {
                    RequestUtils.setResourceType(request, alertDefinition.getResourceType());
                } else if (context == AlertDefinitionContext.Group) {
                    RequestUtils.setResourceGroup(request, alertDefinition.getResourceGroup());
                } else {
                    RequestUtils.setResource(request, alertDefinition.getResource());
                }

                log.trace("adv.id=" + alertDefinitionId);
            }
        }

        return alertDefinition;
    }

    /**
     * Returns a List of LabelValueBean objects whose labels and values are both set to the string of the control
     * actions for the passed-in resource.
     */
    public static List<OptionItem> getControlActions(Subject subject, Integer id, FormContext context) throws Exception {
        List<OptionItem> operations = new ArrayList<OptionItem>();

        OperationManagerLocal operationManager = LookupUtil.getOperationManager();
        List<OperationDefinition> operationDefinitions = null;

        // need to eager load the definitions so the check against getParametersConfigurationDefinition succeeds below

        if (context == FormContext.Type) {
            operationDefinitions = operationManager.findSupportedResourceTypeOperations(subject, id, true);
        } else if (context == FormContext.Group) {
            operationDefinitions = operationManager.findSupportedGroupOperations(subject, id, true);
        } else if (context == FormContext.Resource) {
            operationDefinitions = operationManager.findSupportedResourceOperations(subject, id, true);
        } else {
            throw new IllegalArgumentException("Unsupported form context: " + context);
        }

        for (OperationDefinition definition : operationDefinitions) {
            // as of right now, you can only trigger no-arg operations when an alert fires
            if (definition.getParametersConfigurationDefinition() != null) {
                continue;
            }

            OptionItem next = new OptionItem(definition.getDisplayName(), String.valueOf(definition.getId()));
            operations.add(next);
        }

        return operations;
    }

    public static String getAlertRecoveryInfo(Alert alert, int resourceId) {
        Subject subject = EnterpriseFacesContextUtility.getSubject();

        if (alert.getRecoveryId() != 0) {
            Integer recoveryAlertId = alert.getRecoveryId();
            AlertDefinitionManagerLocal alertDefinitionManagerLocal = LookupUtil.getAlertDefinitionManager();
            AlertDefinition recoveryAlertDefinition = alertDefinitionManagerLocal.getAlertDefinitionById(subject,
                recoveryAlertId);
            //return recoveryAlertDefinition.getName();
            return "Triggered '<a href=\"/alerts/Config.do?mode=viewRoles&id=" + resourceId + "&ad" + recoveryAlertId
                + "\">" + recoveryAlertDefinition.getName() + "</a>' to be re-enabled";
        }

        if (alert.getWillRecover()) {
            return "This alert caused its alert definition to be disabled";
        }

        return "N/A";
    }

}
