/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.apache.ambari.server.api.query.render;

import java.util.Set;

import org.apache.ambari.server.api.query.QueryInfo;
import org.apache.ambari.server.api.services.Request;
import org.apache.ambari.server.api.services.Result;
import org.apache.ambari.server.api.services.ResultImpl;
import org.apache.ambari.server.api.services.ResultPostProcessor;
import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
import org.apache.ambari.server.api.util.TreeNode;
import org.apache.ambari.server.api.util.TreeNodeImpl;
import org.apache.ambari.server.controller.internal.AlertResourceProvider;
import org.apache.ambari.server.controller.internal.ResourceImpl;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.state.AlertState;
import org.apache.ambari.server.state.MaintenanceState;

/**
 * The {@link AlertSummaryRenderer} is used to format the results of queries to
 * the alerts endpoint. Each item returned from the query represents an
 * individual current alert which is then aggregated into a summary structure
 * based on the alert state.
 * <p/>
 * The finalized structure is:
 *
 * <pre>
 * {
 *   "href" : "http://localhost:8080/api/v1/clusters/c1/alerts?format=summary",
 *   "alerts_summary" : {
 *     "CRITICAL" : {
 *       "count" : 3,
 *       "maintenance_count" : 1,
 *       "original_timestamp" : 1415372828182
 *     },
 *     "OK" : {
 *       "count" : 37,
 *       "maintenance_count" : 0,
 *       "original_timestamp" : 1415375364937
 *     },
 *     "UNKNOWN" : {
 *       "count" : 1,
 *       "maintenance_count" : 0,
 *       "original_timestamp" : 1415372632261
 *     },
 *     "WARN" : {
 *       "count" : 0,
 *       "maintenance_count" : 0,
 *       "original_timestamp" : 0
 *     }
 *   }
 * }
 * </pre>
 * <p/>
 * The nature of a {@link Renderer} is that it manipulates the dataset returned
 * by a query. In the case of alert data, the query could potentially return
 * thousands of results if there are thousands of nodes in the cluster. This
 * could present a performance issue that can only be addressed by altering the
 * incoming query and modifying it to instruct the backend to return a JPA SUM
 * instead of a collection of entities.
 */
public class AlertSummaryRenderer extends BaseRenderer implements Renderer {

  /**
   * {@inheritDoc}
   */
  @Override
  public TreeNode<Set<String>> finalizeProperties(
      TreeNode<QueryInfo> queryTree, boolean isCollection) {

    QueryInfo queryInfo = queryTree.getObject();
    TreeNode<Set<String>> resultTree = new TreeNodeImpl<Set<String>>(
        null, queryInfo.getProperties(), queryTree.getName());

    copyPropertiesToResult(queryTree, resultTree);

    boolean addKeysToEmptyResource = true;
    if (!isCollection && isRequestWithNoProperties(queryTree)) {
      addSubResources(queryTree, resultTree);
      addKeysToEmptyResource = false;
    }

    ensureRequiredProperties(resultTree, addKeysToEmptyResource);

    // ensure that state and original_timestamp are on the request since these
    // are required by the finalization process of this renderer
    Set<String> properties = resultTree.getObject();
    addRequiredAlertProperties(properties);

    return resultTree;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ResultPostProcessor getResultPostProcessor(Request request) {
    // simply return the native rendering
    return new ResultPostProcessorImpl(request);
  }

  /**
   * {@inheritDoc}
   * <p/>
   * This will iterate over all of the nodes in the result tree and combine
   * their {@link AlertResourceProvider#ALERT_STATE} into a single summary
   * structure.
   */
  @Override
  public Result finalizeResult(Result queryResult) {
    TreeNode<Resource> resultTree = queryResult.getResultTree();
    AlertStateSummary alertSummary = new AlertStateSummary();

    // iterate over all returned flattened alerts and build the summary info
    for (TreeNode<Resource> node : resultTree.getChildren()) {
      Resource resource = node.getObject();
      AlertState state = (AlertState) resource.getPropertyValue(AlertResourceProvider.ALERT_STATE);
      Long originalTimestampObject = (Long) resource.getPropertyValue(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
      MaintenanceState maintenanceState = (MaintenanceState) resource.getPropertyValue(AlertResourceProvider.ALERT_MAINTENANCE_STATE);

      // NPE sanity
      if (null == state) {
        state = AlertState.UNKNOWN;
      }

      // NPE sanity
      long originalTimestamp = 0;
      if (null != originalTimestampObject) {
        originalTimestamp = originalTimestampObject.longValue();
      }

      // NPE sanity
      boolean isMaintenanceModeEnabled = false;
      if (null != maintenanceState && maintenanceState != MaintenanceState.OFF) {
        isMaintenanceModeEnabled = true;
      }

      final AlertStateValues alertStateValues;
      switch (state) {
        case CRITICAL: {
          alertStateValues = alertSummary.Critical;
          break;
        }
        case OK: {
          alertStateValues = alertSummary.Ok;
          break;
        }
        case WARNING: {
          alertStateValues = alertSummary.Warning;
          break;
        }
        default:
        case UNKNOWN: {
          alertStateValues = alertSummary.Unknown;
          break;
        }
      }

      if (isMaintenanceModeEnabled) {
        alertStateValues.MaintenanceCount++;
      } else {
        alertStateValues.Count++;
      }

      if (originalTimestamp > alertStateValues.Timestamp) {
        alertStateValues.Timestamp = originalTimestamp;
      }
    }

    Result summary = new ResultImpl(true);
    Resource resource = new ResourceImpl(Resource.Type.Alert);
    TreeNode<Resource> summaryTree = summary.getResultTree();
    summaryTree.addChild(resource, "alerts_summary");
    resource.setProperty("alerts_summary", alertSummary);

    return summary;
  }

  /**
   * Adds properties to the backend request that are required by this renderer.
   * This method currently adds {@link AlertResourceProvider#ALERT_STATE} and
   * {@link AlertResourceProvider#ALERT_ORIGINAL_TIMESTAMP}.
   *
   * @param properties
   *          the properties collection to add to.
   */
  protected void addRequiredAlertProperties(Set<String> properties) {
    properties.add(AlertResourceProvider.ALERT_STATE);
    properties.add(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
    properties.add(AlertResourceProvider.ALERT_MAINTENANCE_STATE);
  }
}
